虚拟机类加载机制
大约 5 分钟JvmJavaJvm
虚拟机类加载机制
1 类加载过程
加载-验证-准备-解析-初始化-使用-卸载。
1.1 加载
1.1.1 流程
- 根据类的全限定名获取二进制字节流。
- 将字节流加载到内存中转换为方法区的运行时数据结构。
- 生成Class对象,访问方法区的数据。
1.1.2 数组类型的加载
数组由java虚拟机直接在内存中动态创建,但数组的元素类型(数组去掉所有维度)仍然由类加载器完成加载。
- 如果数组的组件类型(数组去掉一个维度)为引用类型,递归通过加载过程加载这个类型,数组将被表示在加载该组件的类加载器的 类命名空间上。
- 如果数组的组件类型不是引用类型,则 把数组标记为引导类加载器关联。
- 可访问性与组件类一致。
1.2 验证
确保Class文件的字节流中的信息符合java虚拟机规范。
- 文件格式验证
- 魔数开头、主次版本号等
- 元数据验证, 符合java语言规范
- 是否有父类
- 是否继承了不应该继承的类
- 如果不是抽象类,是否实现了父类中的所有方法,
- 方法是否与父类矛盾
- 字节码验证,对类的方法体Code属性分析,确定程序的语义合法
- 数据类型一致
- 跳转位置合法
- 符号引用验证
- 符号引用描述的全限定名是否能找到对应的类
1.3 准备
- 为类变量在方法区分配内存,设置初始值。
- 静态变量的初始值为零值, 但对于静态常量会直接初始化为指定的初始值。
1.4 解析
将符号引用转为直接引用。
符号引用:符号引用是用一组符号描述引用的目标,与内存布局无关。
直接引用:指向目标的指针或者句柄等,与虚拟机内存直接相关。
解析阶段也有可能在准备阶段之后。
1.5 初始化
java虚拟机规范规定了有且只有六种情况必须对类初始化:
- 遇到new、getstatic、setstatic、invokestatic四条字节码指令时
- 使用new实例化对象
- 读取设置静态字段(final除外)
- 调用静态方法
- 使用reflect包对类型反射调用。
- 初始化的时候,先初始化父类,但是一个接口初始化的时候并不要求其父接口全部完成了初始。化,只有在真正使用到父接口的时候(如引用接口中定义的常量)才会初始化。
- 虚拟机启动的时候指定的主类。
- jdk动态语言支持 ?
- 接口定义了默认方法, 在实现类初始化之前初始化。
反例:
- 子类调用父类的静态方法, 只会初始化父类。
- 数组引用类,不会触发类初始化 Object[] o = new Object[10]; 只会生成一个LObject的类,并初始化。
- 子类引用父类的常量,会在编译阶段加入到常量池中。
1.5.1 流程
初始化阶段就是执行类构造器 <cinit>()
方法的过程, 由编译器自动生成。
cinit()
- 由编译器自动收集类中的所有类变量的赋值动作和静态语句块 static{}中的语句合并产生的,顺序由源文件中代码出现的顺序决定的。
- java虚拟机会保证父类的
<cinit>()
方法先与子类执行。 - java虚拟机保证个类的
<cinit>()
方法在多线程环境中被正确加锁同步 ,多个线程同时初始化一个类时,只有一个线程去执行这个类的<cinit>()
方法,其他线程需要阻塞等待,如果<cinit>()
耗时比较长就会造成多个线程阻塞。
1.6 使用
1.7 卸载
2. 类加载器
类加载器用户实现类的加载动作,任何一个类都由加载它的类和这个类本身一起共同决定在虚拟中的唯一性。
2.1 启动类加载器
HotSpot中由C++实现的,是虚拟机内部的一部分。
将<JAVA_HOME>\lib 目录下的,指定文件名的类库加载到虚拟机的类库中。
2.2 扩展类加载器
sun.misc.Launcher.ExtClassLoader
将<JAVA_HOME>\lib\ext 目录下的,指定文件名的类库加载到虚拟机的类库中。
2.3 应用程序类加载器
sun.misc.Launcher.AppClassLoader
可以通过 ClassLoader.getSystemClassLoader()获得,用来加载用户类路径ClassPath上的所有类。
2.4 双亲委派模型

除了启动类加载器外,所有的的类加载器都应该有自己的父类加载器,如果一个类收到了类加载的请求,首先把这个请求委托给父类加载器完成,当父加载器无法完成加载请求时,自类加载器会尝试自己完成加载。
// ClassLoader中的loadClass,类加载逻辑
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;
}
}
2.5 自定义类加载器
- 继承java.lang.ClassLoader,重写它的findClass方法。
public class MyClassLoader extends ClassLoader
{
public MyClassLoader(ClassLoader parent)
{
super(parent);
}
protected Class<?> findClass(String name) throws ClassNotFoundException
{
try
{
byte[] bytes = Utils.getBytes(name);
Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
return c;
}
catch (Exception e)
{
e.printStackTrace();
}
return super.findClass(name);
}
}
// 测试自定义的类加载器
public class TestMyClassLoader
{
public static void main(String[] args) throws Exception
{
MyClassLoader mcl = new MyClassLoader();
Class<?> c1 = Class.forName("com.*", true, mcl);
Object obj = c1.newInstance();
System.out.println(obj);
System.out.println(obj.getClass().getClassLoader());
}
}
2.5.1 应用场景
- 资源隔离 Tomcat , 每个WebApp有自己的ClassLoader,加载每个WebApp的ClassPath路径上的类,一旦遇到Tomcat自带的Jar包就委托给CommonClassLoader加载。
- 字节码加密解密,保护代码。
- 热加载、热部署: OSGi。