不知为何,写 RocketMQ 没人看, JVM 到是有人看,虽然也不多吧
目录:
四种类加载器
启动类加载器Bootstrap ClassLoader:加载JACA_HOMElibrt.jar,或者被-Xbootclasspath参数限定的类
扩展类加载器Extension ClassLoader:加载libext,或者被java.ext.dirs系统变量指定的类
应用程序类加载器Application ClassLoader:加载ClassPath中的类库
自定义类加载器,通过继承ClassLoader实现:一般是加载我们的自定义类
确定类的唯一性
全限定名(完整包名和类名)+同一个类加载器加载
双亲委派模型
所谓双亲委派是指每次收到类加载请求时,先将请求委派给父类加载器完成(所有加载请求最终会委派到顶层的Bootstrap ClassLoader加载器中),如果父类加载器无法完成这个加载(该加载器的搜索范围中没有找到对应的类),子类尝试自己加载。如果已经加载了,则不会加载此类了。
String
如果在应用中写一个java.lang.String,放到 ClassPath 中会怎么样?
首先,ApplicationClassLoader 会尝试让ExtClassLoader 加载,ExtClassLoader 找不到,尝试让 BootClassLoader 找,问题是 rt.jar 已经被 BootClassLoader 加载了,所以classPath 中的 String 永远不会被加载。此为双亲委派模型。
类的生命周期
加载->链接(验证->准备->解析)->初始化->使用->卸载
加载
加载二进制流
创建 class 元数据 存储于持久区或者 MetaSpace
创建 class 对象于 JavaHeap,可以以此访问持久区的元数据,也是反射的基础。
准备
假设存在static int value = 3,此阶段将把 value 赋值为0。
假设存在 static final int value = 3,这将直接赋值为3。
初始化
所有类变量初始化语句和静态代码块都会在编译时被前端编译器放在收集器里头,存放到一个特殊的方法中,这个方法就是<clinit>方法,即类/接口初始化方法,该方法只能在类加载的过程中由JVM调用;
编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量;
如果超类还没有被初始化,那么优先对超类初始化,但在<clinit>方法内部不会显示调用超类的<clinit>方法,由JVM负责保证一个类的<clinit>方法执行之前,它的超类<clinit>方法已经被执行。
JVM必须确保一个类在初始化的过程中,如果是多 线程 需要同时初始化它,仅仅只能允许其中一个线程对其执行初始化操作,其余线程必须等待,只有在活动线程执行完对类的初始化操作之后,才会通知正在等待的其他线程。(所以可以利用静态内部类实现线程安全的单例模式)
如果一个类没有声明任何的类变量,也没有静态代码块,那么可以没有类<clinit>方法;
初始化方法的触发也有可能
为一个类型创建一个新的对象实例时(比如new、反射、序列化)
调用一个类型的 静态方法 时(即在字节码中执行invokestatic指令)
调用一个类型或接口的静态字段,或者对这些静态字段执行赋值操作时(即在字节码中,执行getstatic或者putstatic指令),不过用final修饰的静态字段除外,它被初始化为一个编译时常量表达式
调用JavaAPI中的反射方法时(比如调用java.lang.Class中的方法,或者java.lang.reflect包中其他类的方法)
初始化一个类的派生类时(Java虚拟机规范明确要求初始化一个类时,它的超类必须提前完成初始化操作,接口例外)
JVM启动包含main方法的启动类时。
<init> 和 <clinit>
init 对象构造
clinit 类构造
如果调用一个类的静态方法,没有 new 过,那么 clinit 一定执行过,init 则没执行过。
下一篇,对象的创建