Java中的类加载器

类加载器负责运行时(Runtime)把class文件动态的加载到虚拟机(JVM)中。同时,类加载器也是JRE的一部分,所以JRE可以不关心底层文件系统,就可以运行Java程序。此外,这些 Java 类不是一次性加载到内存中的,而是在应用程序需要时加载到内存中。

类加载器简介

首先我们通过一段代码来看一下Java中的类加载器:

public class ClassLoaderTest {

@Test

public void testClassLoaders() {

System.out.println("Classloader of this class:"

+ ClassLoaderTest.class.getClassLoader());

System.out.println("Classloader of Logging:"

+ Logging.class.getClassLoader());

System.out.println("Classloader of ArrayList:"

+ ArrayList.class.getClassLoader());

// 打印结果

// Classloader of this class:sun.misc.Launcher$AppClassLoader@18b4aac2

// Classloader of Logging:sun.misc.Launcher$ExtClassLoader@6325a3ee

// Classloader of ArrayList:null

}

}

从打印结果来看,包含3种类加载器:应用类加载器(Application ClassLoader), 扩展类加载器(Extension ClassLoader)和 一个启动类加载器(展示位null)。

  • 应用类加载器
    在类路径中加载我们自己的文件,它是扩展类加载器的子类。
  • 扩展类加载器加载的类是核心 Java 类的扩展,默认放在/lib/ext目录中,或者被java.ext.dirs系统变量所指定的路径中的所有类库。它是启动类加载器的子类。
  • 启动类加载器是所有其他类的父类,默认放在/lib目录中,或者-Xbootclasspath参数所指定的路径中。是虚拟机识别的类库,用户无法直接使用。对于ArrayList为什么打印出来的是null,那是因为启动类加载器都是用本地方法(native)写的,而不是Java,所以不会显示类。

类加载器是如何工作的

上面我们已经知道,类加载器是JRE的一部分。当 JVM 尝试加载一个类时,类加载器尝试定位该类并使用完全限定的类名将类信息加载进入runtime。
java.lang.ClassLoader.loadClass()
负责将类加入runtime,如果runtime中没有该类,会首先从父类加载,如果父类没有加载,然后尝试从子类加载,这就是双亲委派模型

。如果最后都没有加载到该类,则会抛出ClassNotFoundException异常。

双亲委派模型: 当一个类加载器收到类加载请求时,它首先不会自己去加载这个类的信息,而是把该请求转发给父类加载器,依次向上。所以所有的类加载请求都会被传递到父类加载器中,只有当父类加载器中无法加载到所需的类,子类加载器才会自己尝试去加载该类。

在大部分情况下,内置的几种类加载器已经够用了,但是在需要从本地硬盘或网络加载类的场景中,我们可能需要使用自定义类加载器。

public class CustomClassLoader extends ClassLoader {

@Override

public Class findClass(String name) {

byte[] b = loadClassFromFile(name);

return defineClass(name, b, 0, b.length);

}

private byte[] loadClassFromFile(String fileName) {

InputStream inputStream = getClass().getClassLoader().getResourceAsStream(

fileName.replace('.', File.separatorChar) + ".class");

byte[] buffer;

ByteArrayOutputStream byteStream = new ByteArrayOutputStream();

int nextValue = 0;

try {

while ( (nextValue = inputStream.read()) != -1 ) {

byteStream.write(nextValue);

}

} catch (IOException e) {

e.printStackTrace();

}

buffer = byteStream.toByteArray();

return buffer;

}

}

loadClass方法

public Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {}

这里需要注意的是loadClass的第二个参数,当需要解析类的时候需要将该值设置为true,然而,我们并不总是需要解析一个类。 如果我们只需要确定类是否存在,那么 resolve 参数设置为 false。

如class.forName加载类时resolve就为true;而classLoader.loadClass时默认resolve为false。


分享到:


相關文章: