Java最高

开始使用Spring 5和Spring Boot 2,通过学习的春天课程:

>>看看这个课程

1.类加载器简介

类加载器负责在运行时将Java类动态加载到JVM(Java虚拟机)。此外,它们也是JRE (Java运行时环境)的一部分。因此,由于类装入器的存在,JVM不需要了解底层文件或文件系统就可以运行J金宝搏官网188beava程序。

此外,这些Java类不是一次性加载到内存中,而是在应用程序需要时加载。这就需要用到类加载器了。它们负责将类加载到内存中。

在本教程中,我们将讨论不同类型的内置类装入器,它们是如何工作的,并介绍我们自金宝搏官网188be己的自定义实现。

进一步阅读:

了解Java中的内存泄漏

了解Java中的内存泄漏是什么,如何在运行时识别它们,导致它们的原因,以及防止它们的策略。

ClassNotFoundException vs NoClassDefFoundError

了解Clas金宝搏官网188besNotFoundException和NoClassDefFoundError之间的区别。

2.内置类装入器的类型

让我们通过一个简单的例子来学习如何使用不同的类加载器加载不同的类:

public void printClassLoaders() throws ClassNotFoundException {System.out。println("这个类的Classloader:" + PrintClassLoader.class.getClassLoader());system . out。println("Classloader of Logging:" + Logging.class. getclassloader ());system . out。+ ArrayList.class. getclassloader ();}

当执行上述方法时打印:

该类的类装入器:(电子邮件保护)日志的类加载器:(电子邮件保护)ArrayList的类装入器:null

正如我们所看到的,这里有三个不同的类装入器;应用程序、扩展和引导程序(显示为)。

应用程序类装入器装入包含示例方法的类。应用程序或系统类装入器在类路径中装入我们自己的文件。

然后,扩展加载日志记录类。扩展类装入器装入标准核心Java类的扩展类。

最后,引导程序加载ArrayList类。引导或原始类装入器是所有其他类装入器的父类。

但是,我们可以看到最后一个,因为ArrayList它显示出来在输出。这是因为引导类装入器是用本机代码编写的,而不是Java——因此它不会显示为Java类。由于这个原因,Bootstrap类加载器的行为跨JVMS不同。

现在让我们更详细地讨论这些类装入器。金宝搏官网188be

2.1。引导类装入器

Java类由一个实例加载java.lang.ClassLoader。然而,类装入器本身就是类。因此,问题是,谁加载java.lang.ClassLoader本身吗?

这就是引导程序或原始类装入器发挥作用的地方。

通常,它主要负责加载JDK内部类rt.jar和其他核心库位于$ JAVA_HOME / jre / lib目录中。此外,Bootstrap类加载器用作所有其他的父级ClassLoader.实例

这个引导类装入器是核心JVM的一部分,是用本机代码编写的如上面的例子所指出的那样。不同的平台可能具有此特定类加载器的不同实现。

2.2。扩展类加载器

扩展类装入器是引导类装入器的子代,负责装入标准核心Java类的扩展这样它对所有在平台上运行的应用程序都可用。

扩展类加载器从JDK Extensions目录加载,通常$ JAVA_HOME / lib / ext目录中提到的任何其他目录java.ext.dirs.系统属性。

2.3。系统类装入器

另一方面,系统或应用程序类装入器负责将所有应用程序级类装入JVM。它加载classpath环境变量中找到的文件,类路径或者- cp命令行选项。此外,它是Extensions类加载器的一个子类。

3.类加载器如何工作?

类装入器是Java运行时环境的一部分。当JVM请求一个类时,类装入器尝试定位类,并使用完全限定的类名将类定义加载到运行时中。

java.lang.ClassLoader.loadClass ()方法负责将类定义加载到运行时。它尝试基于完全限定名加载类。

如果类还没有装入,它将请求委托给父类装入器。这个过程是递归的。

最终,如果父类装入器没有找到这个类,那么子类将调用java.net.URLClassLoader.findClass ()方法在文件系统本身中查找类。

如果最后一个子类装入器也不能装入这个类,它就抛出java.lang.noclassdeffounderror.或者java.lang.ClassNotFoundException。

让我们看一个抛出ClassNotFoundException时的输出示例。

java.lang.ClassNotFoundException: com.金宝搏188体育baeldung.classloader.SampleClassLoader at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at java.lang.ClassLoader.loadClass(ClassLoader.java:357)java.lang.Class.forName(Class.java:348)

如果我们按事件顺序从呼叫开始java.lang.Class.forName (),我们可以理解,它首先尝试通过父类加载器加载类,然后java.net.URLClassLoader.findClass ()寻找类本身。

当它仍然没有找到类时,它抛出ClassNotFoundException。

类装入器有三个重要的特性。

3.1。委托模型

类装入器遵循委托模型根据查找类或资源的请求,aClassLoader.实例将类或资源的搜索委托给父类装入器

假设我们有一个请求将应用程序类加载到JVM中。系统类加载器首先将该类的加载到其父扩展类加载器中,否则将其委托给Bootstrap类加载器。

只有在引导程序和扩展类装入器装入类失败时,系统类装入器才会尝试装入类本身。

3.2。独特的类

作为委托模型的结果,它很容易确保独特的类,因为我们总是试图向上委托

如果父类装入器不能找到类,只有此时当前实例自己才会尝试这样做。

3.3。可见性

此外,子类装入器对父类装入器装入的类是可见的

例如,由系统类装入器装入的类可以看到由扩展类装入器和Bootstrap类装入器装入的类,反之则不然。

为了说明这一点,如果类A是由应用程序类装入器装入的,类B是由扩展类装入器装入的,那么就应用程序类装入器装入的其他类而言,A类和B类都是可见的。

B类,仍然是唯一可以看到的唯一可见的类,就像扩展类加载器加载的其他类都关注。

4.自定义类加载器

对于文件已经在文件系统中的大多数情况,内置的类装入器就足够了。

然而,在需要从本地硬盘驱动器或网络加载类的情况下,我们可能需要使用自定义类加载器。

在本节中,我们将介绍自定义类装入器的其他一些用例,并演示如何创建一个。

4.1。自定义类装入器用例

自定义类加载器不仅有助于在运行时加载类,一些用例可能包括:

  1. 帮助修改现有的字节码,例如编织代理
  2. 创建动态适合用户需求的类。例如,在JDBC中,通过动态类加载完成不同驱动程序实现之间的切换。
  3. 在加载具有相同名称和包的类的类时,在加载不同的字节码时实现类版本控制机制。这可以通过URL类加载器(通过URL负载jars)或自定义类加载器来完成。

有更多具体示例,自定义类装载机可能会派上用场。

例如,浏览器使用自定义类加载器来从网站加载可执行内容。浏览器可以使用不同的类加载器从不同的网页加载applet。用于运行applet的applet查看器包含一个ClassLoader.访问远程服务器上的网站,而不是查看本地文件系统。

然后通过HTTP加载原始字节码文件,并将它们转换为JVM中的类。即使这些applet具有相同的名称,如果被不同的类加载器加载,则被认为是不同的组件

既然我们理解了为什么自定义类装入器是相关的,那么让我们实现ClassLoader.扩展和总结JVM如何加载类的功能。

4.2。创建我们的自定义类加载器

为了便于说明,我们假设需要使用自定义类装入器从文件中装入类。

我们需要延长ClassLoader.类并重写findClass ()方法:

public class CustomClassLoader extends ClassLoader {@Override public class findClass(String name) throws ClassNotFoundException {byte[] b = loadClassFromFile(name);return defineClass(name, b, 0, b.length);} private byte[] loadClassFromFile(String fileName) {InputStream InputStream = getClass(). getclassloader (). getclassloader()。getResourceAsStream (fileName.replace”。', File.separatorChar) + ".class");byte[]缓冲区;ByteArrayOutputStream byteStream = new ByteArrayOutputStream();int nextValue = 0;try {while ((nextValue = inputStream.read()) != -1) {byteStream.write(nextValue);}}捕获(IOException e) {e. printstacktrace ();} buffer = byteStream.toByteArray();返回缓冲区; } }

在上面的例子中,我们定义了一个自定义类装入器,它扩展了默认类装入器,并从指定的文件装入一个字节数组。

5.理解java.lang.ClassLoader

让我们讨论一些基本的方法java.lang.ClassLoader类来更清楚地了解它是如何工作的。

5.1。的loadClass ()方法

公开课< ?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

此方法负责加载给定名称参数的类。name参数引用完全限定类名。

Java虚拟机调用loadClass ()方法来解析设置resolve为的类引用真正的。然而,并不总是需要解析一个类。如果只需要确定类是否存在,则将resolve参数设置为

此方法用作类装入器的入口点。

我们可以试着理解loadClass ()的源代码中的java.lang.ClassLoader:

保护类< ?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)){//首先,检查类是否已经加载class  c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();Try {if (parent != null) {c = parent。loadClass(名称、假);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e){//如果类没有找到,则抛出ClassNotFoundException} if (c == null){//如果仍然没有找到,则调用findClass来找到类。c = findClass(名称);}} if (resolve) {resolveClass(c);}返回c;}}

该方法的默认实现按以下顺序搜索类:

  1. 调用findLoadedClass(字符串)方法查看类是否已经加载。
  2. 调用loadClass(字符串)在父类装入器上。
  3. 调用findClass(字符串)方法查找类。

5.2。的defineClass ()方法

受保护的最后一节课< ?> defineClass(String name, byte[] b, int off, int len)抛出ClassFormatError

此方法负责将字节数组转换为类的实例。在使用这个类之前,我们需要解析它。

如果数据没有包含有效的类,它将抛出ClassFormatError。

另外,我们不能重写这个方法,因为它被标记为final。

5.3。的findClass ()方法

保护类< ?> findClass(String name)抛出ClassNotFoundException异常

此方法查找具有完全限定名作为参数的类。我们需要在遵循装入类的委托模型的自定义类装入器实现中重写此方法。

同时,loadClass ()如果父类装入器找不到所请求的类,则调用此方法。

默认实现抛出ClassNotFoundException如果没有类加载程序的父级找到类。

5.4。的getParent ()方法

public final ClassLoader getParent()

此方法返回委托的父类装入器。

一些实现,如前面在第2节中看到的。使用来表示引导类装入器。

5.5。的getResource ()方法

public URL getResource(String name)

此方法尝试查找具有给定名称的资源。

它首先将资源委托给父类加载器。如果父母是,搜索内置在虚拟机中的类加载器的路径。

如果失败,则该方法将调用findResource(字符串)查找资源。指定为输入的资源名可以是相对于类路径的,也可以是绝对的。

它返回一个URL对象来读取资源,如果找不到资源,或者调用者没有足够的权限返回资源,则返回null。

需要注意的是,Java从类路径加载资源。

最后,Java中的资源加载被认为是位置无关的由于代码运行的位置并不重要,只要环境设置为查找资源即可。

6.上下文类加载器

通常,上下文类加载器为J2SE引入的类加载委派方案提供了一种替代方法。

就像我们之前学到过,JVM中的类装入器遵循分层模型,这样每个类装入器都只有一个父类,引导类装入器除外。

但是,有时当JVM核心类需要动态加载应用程序开发人员提供的类或资源时,我们可能会遇到问题。

例如,在JNDI中,核心功能是由中引导类实现的rt.jar。但这些JNDI类可以加载由独立供应商实现的JNDI提供程序(部署在应用程序类路径中)。此方案调用引导类加载程序(父类加载器)来加载应用程序加载器(子类Loader)可见的类。

J2SE委托在这里不起作用,为了解决这个问题,我们需要找到类装入的替代方法。它可以使用线程上下文加载器来实现。

java.lang.Thread类有一种方法getContextClassLoader ()返回的ContextClassLoader对于特定的线程。的ContextClassLoader加载资源和类时,由线程的创建者提供。

如果未设置该值,则默认为父线程的类装入器上下文。

7.结论

类装入器对于执行Java程序是必不可少的。在本文中,我们提供了一个很好的介绍。

我们讨论了不同类型的金宝搏官网188be类装入器,即引导、扩展和系统类装入器。Bootstrap作为所有这些类的父类,负责加载JDK内部类。另一方面,扩展和系统分别从Java扩展目录和类路径加载类。

然后我们讨论了类装入器的工作方金宝搏官网188be式,讨论了一些特性,如委托、可见性和惟一性,然后简要说明了如何创建一个自定义类装入器。最后,我们介绍了Context类装入器。

和往常一样,可以找到代码示例在GitHub

Java底部

开始使用Spring 5和Spring Boot 2,通过学习的春天课程:

>>看看这个课程
对这篇文章的评论关闭!