类加载
类何时被加载器加载
1. 调用类构造器.
2. 调用类中的静态变量或静态方法.
复制代码
类加载器的类型
1. 启动类加载器 BootstrapClassLoader
2. 扩展类加载器 ExtClassLoader
3. 系统加载器 AppClassLoader
复制代码
类加载过程
装载
- ClassLoader通过一个类的全限定名(包名+类名)来查找
.class
文件,并生成二进制字节流.注: 字节码的来源可以是.class文件、jar包、zip包、来源于网络的字节流.
- 将
.class
文件的各个部分分别解析为JVM内部特定的数据结构,并存储在方法区中. - 在内存中创建一个
java.lang.Class
类型的对象.注:程序在运行过程中所有对该类的访问都是通过这个类对象
.
JVM是什么时候转载某.class文件呢?
- 隐式装载: 使用new等方式生成对象时,系统会隐式调用ClassLoader去装载对应的class到内存中.
- 显示装载: 使用Class.forName()等反射方法时.
链接
验证
为了确保.class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危及虚拟机本身的安全.
- 文件格式检验: 验证文件字节流是否符合.class文件格式的规范,并且能被当前版本的虚拟机处理.
- 元数据检验: 对字节码描述的信息进行语义分析,以确保其描述的内容符合Java语言规范的要求.
- 字节码检验: 通过数据流和控制流分析,确定程序语义是合法、符合逻辑的.
- 符号引用检验: 对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验.
准备
为类中的静态变量分配内存并为其设置”0值“(默认值)
- 基本类型(int、long、short、byte、char、boolean、float、double)的默认值为0,
- 引用类型默认值是null.
注:如果为静态常量,则在准备阶段分配内存后直接赋值为常量值.
解析
将常量池中的符号引用转换为直接引用,也就是具体的内存地址.
-
符号引用和直接引用的类比
比如使用微信聊天时,在微信好友列表中,保存的是好友的名称或者别名,此时可以看做是符号引用.当我们给某个好友发消息时,会根据好友的名称找到对应好友的IP地址,此时可以看做是直接引用.
初始化
执行类构造器的<cinit>方法的过程,并真正初始化类变量,即为准备阶段时赋值为"0值"的静态变量赋予真正的值.
初始化的时机?
- 主动引用:
- 虚拟机启动时,初始化包含main方法的主类.
- 遇到new指令创建对象实例时,如果目标对象没有被初始化则进行初始化操作.
- 遇到访问静态方法或静态字段的指令时,如果目标对象没有被初始化则进行初始化操作.
- 子类的初始化过程如果发现其父类还没有初始化,则需要先触发其父类的初始化.
- 使用反射时,如果目标对象没有被初始化则进行初始化操作.
- 第一次调用java.lang.invoke.MethodHandle实例时,需要初始化MethodHandle指向方法所在的类.
初始化类变量
在初始化阶段,只会初始化与类相关的静态赋值语句和静态语句,即static
关键字修饰的信息,而没有static
修饰的语句块在实例化对象的时候才会执行.
被动引用
被动引用不会出发class的初始化.
在子类中调用父类的静态变量.
只有直接定义这个字段的类才会被初始化.通过子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化.
class对象初始化和对象的创建顺序
使用new创建一个类的实例对象时,类中的静态代码块、非静态代码块、构造函数之间的执行顺序.
静态变量/静态代码块 —> 非静态代码块 —> 构造函数
- 父类静态变量和静态代码块
- 子类静态变量和静态代码块
- 父类非静态成员变量和非静态代码块
- 父类的构造函数
- 子类的非静态成员变量和非静态代码块
- 子类的构造函数
类加载机制(双亲委派)
向上委托、向下加载
当类加载器收到加载类或资源的请求时,通常都是先委托给父类加载器加载,只有当父类加载器找不到指定类或资源时,自身才会执行实际的加载过程.
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; } } ``` 复制代码
- 首先会判断该Class是否已经加载,如果已加载则直接将该Class返回.
- 如果该Class没有加载过,则判断父类加载器是否为null,如果不是则将加载任务交给父类加载器.
- 如果为空则将加载任务交给启动类加载器去加载.
- 如果父类加载器和启动类加载器都没有加载成功,则调用当前类的
findClass
方法继续尝试加载.
自定义类加载器
- 继承
ClassLoader
. - 重写
findClass
方法. - 调用
defineClass
将字节码转换成Class对象并返回.
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END