一、类加载的作用
把class文件中的类加载到虚拟机中。什么时候加载呢?触发时机包括以下几种:
- 使用静态字段或静态方法,包括new的时候;
- 使用java.lang.reflect包的方法对类进行反射调用时;
- 初始化一个类时,首先确保初始化其父类;
- 虚拟机启动时,加载主类;
- 使用动态语言支持时
二、类加载的流程
- 加载
获取二进制字节流,转换成方法区的运行时数据结构,在内存生成一个class对象 - 链接
- 验证
- 准备
分配内存,赋初始值 - 解析
符号引用替换成直接引用
- 初始化
执行<clinit>() - 使用
- 卸载
需要:所有对象被gc,类对象没有被引用,类加载器被gc
三、类加载模型
双亲委派模型,先给parent load,load不到才会自己load
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
使用这种模型的意义是:
- 防止混乱,避免同一个类被各种加载
- 保证虚拟机的安全,自己写一个java.开头的类自己加载,会抛出SecurityException
四、类加载器
- BootstrapClassLoader
c++实现,虚拟机的一部分,负责加载运行时需要的核心类库。例如存放在$JAVA_HOME/lib中并且能被虚拟机识别的类(如rt.jar包中的java.开头的类) - ExtClassLoader
加载$JAVA_HOME/lib/ext中的jar包,实现在sun.misc.Launcher中,他的parent是null,代表BootstrapClassLoader - AppClassLoader
加载用户路径(ClassPath)上指定的类库,也就是用户实现的类。同样实现在sun.misc.Launcher中,他的parent是ExtClassLoader
五、一些著名的例子
- 线程上下文加载器
起因是例如JNDI服务这样的服务接口作为java标准服务是由BootstrapClassLoader加载的,而其实现由独立厂商提供(SPI,Service Provider Interface)放在classpath下,BootstrapClassLoader无法加载,所以只好使用线程上下文加载器开个后门,默认为AppClassLoader
网友评论