ClassLoader介绍
ClassLoader在Java 1.0的时候就有了,为了满足Java Applet运行时远程加载Java类的需要。
所谓ClassLoader,就是将.java文件编译之后产生的.class字节文件加载到运行时数据区(JVM的方法区)中的过程。而这个加载过程一般都是按需加载的,就是第一次用到某Class的时候JVM才会去加载其对应的.class文件。
系统提供三个ClassLoader:
- Bootstrap ClassLoader
负责加载java核心类库(位于<JAVA_HOME>/jre/lib
的内容),由本地代码(如C
)编写。 - Extensions ClassLoader
负责加载java核心类库(位于<JAVA_HOME>/jre/ext
的内容),由sun.misc.Launcher$ExtClassLoader
实现。 - System ClassLoader (App ClassLoader)
负责加载应用类,一般通过java.class.path
或CLASSPATH
环境变量来加载 Java 类,由sun.misc.Launcher$ExtClassLoader
实现。
自定制类加载器
用户可通过继承java.lang.ClassLoader
自行实现定制的类加载器。自定制类加载器可以做如下工作:
- 运行时装载或卸载类,这常用于:
实现脚本语言
用于bean生成器
允许用户定义的扩展性
允许命名空间之间的通信。这是CORBA / RMI协议的基础。- 改变Java字节码的装入,例如,可用于Java类字节码的加密装入。
- 修改已装入的字节码weaving of aspects when using aspect-oriented programming)。
自定制类加载器实现示例
public class FileClassLoader extends ClassLoader {
private String rootDir;
public FileClassLoader(String rootDir) {
this.rootDir = rootDir;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
}
return defineClass(name, classData, 0, classData.length);
}
private byte[] getClassData(String className) {
String path = classNameToPath(className);
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while ((bytesNumRead = ins.read(buffer)) != -1)
baos.write(buffer, 0, bytesNumRead);
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private String classNameToPath(String className) {
return rootDir + File.separatorChar
+ className.replace('.', '/') + ".class";
}
}
java.lang.ClassLoader
类的方法loadClass()
封装了前面提到的代理模式的实现。该方法会首先调用findLoadedClass()
方法来检查该类是否已经被加载过;如果没有加载过的话,会调用父类加载器的loadClass()
方法来尝试加载该类;如果父类加载器无法加载该类的话,就调用findClass()
方法来查找该类。因此,为了保证类加载器都正确实现代理模式,在开发自己的类加载器时,最好不要覆写loadClass()
方法,而是覆写findClass()
方法。
双亲委派机制:
某个类加载器接到加载类的请求时,会递归地将请求委托给父类加载器,只有父类加载器无法加载的时候自己才会去加载该类。该机制的作用是防止同一个类被加载多次。
自定製类加载器的雙親是System ClassLoader,System ClassLoader的雙親是Extensions ClassLoader,Extensions ClassLoader的雙親是Bootstrap ClassLoader。
Namespace
每个类加载器加载的类都會被分配一个装载其的ClassLoader对应的唯一namspace。namespace就像一道墙,不同namespace被加载的类彼此之间一般无法交互,甚至彼此都不知道彼此的存在。
如果要求某个ClassLoader去加载某个类型,最终另外一个被委派的ClassLoader加载并返回了这个类型,那么这两个ClassLoader共享该类型。这就是用户可以无视namespace无障碍调用java api的原因。
加载流程
- loading
Classloader通过双亲委派机制查找.class文件(TYPE)并载入 - linking 分三步
2.1. verification
检查引入TYPE文件正确性
2.2. preparation
给class变量分配内存(在方法区)并赋值default value:(boolean:false, int:0, reference:null)
2.3. resolution
将symbolic reference转为direct reference ,通常延后触发。所谓symbolic reference就是fully qualified name(全限定名),拿在com.mylib
包下的Utility
类来说,其全限定名是com.mylib.Utility
。 - initialization
触发代码提供的赋值语句,通常延后触发。
加载流程示例:
public class Hello{
public int age;
public String name;
public static String species;
{
age = 10;
name = "Jack";
System.out.println("Non-static block");
}
static{
species = "human";
System.out.println("Static block");
}
public Hello(){
System.out.println("initialization");
}
public static void testStatic() {
System.out.println("static method invoke");
}
public void test() {
System.out.println("instance method invoke");
}
}
测试类:
import java.lang.reflect.Method;
public class LoadClass {
public static void main(String [] args) throws Exception {
LoadClass lc = new LoadClass();
ClassLoader cl = lc.getClass().getClassLoader();
System.out.println(cl.getClass().getName());
Class<Hello> HL = (Class<Hello>) cl.loadClass("Hello");
System.out.println("Hello loaded");
Method m =HL.getMethod("testStatic", null);
m.invoke(null, null);
Hello hl = HL.newInstance();
}
}
Console打印结果:
sun.misc.Launcher$AppClassLoader
Hello loaded
Static block
static method invoke
Non-static block
initialization
由上例看出加载自定义的类的默认ClassLoader是AppClassLoader
。在类Hello
被加载结束后并没有给class变量赋值;在调用static method的时候static block才被触发;在调用normal method的时候non-static block才被触发。
Reference
https://zh.wikipedia.org/wiki/Java类加载器
https://blog.csdn.net/sureyonder/article/details/5564181
https://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html