文章目录
jvm将类的数据从class文件加载到内存中、形成可以被jvm直接使用的Class对象的过程成为类加载机制。
类的生命周期中的加载、验证、准备、解析、初始化属于类加载机制。
类加载的过程
1. 加载
通过类的全限定名获取二进制字节流、字节流转为方法区的运行时数据结构、在内存中生成代表这个类的java.lang.Class对象
2. 验证
确保Class文件的字节流包含的信息符合jvm的要求
3. 准备
为类中的静态变量分配内存并设置初始值
4. 解析
将常量池中的符号引用替换为直接引用
5. 初始化
真正执行编写的java程序代码,对类的静态变量进行初始化。与3中的初始化不同:3将静态变量初始化为默认值,例如整型则初始化为0;5将依据声明时指定的初始值、静态初始化块对静态变量进行初始化。
初始化的时机:
1. 创建类的实例时:通过new创建、通过反射创建、通过反序列化创建
2. 调用类的静态方法、访问或修改类的静态变量
3. 使用java.lang.reflect包的方法对类型进行反射调用时
4. 初始化某个类的子类,其所有父类都会被初始化
5. 直接使用java.exe运行某个主类,虚拟机会先初始化这个主类。
6. 当接口中包含default方法,如果这个接口的实现类进行了初始化,则这个接口要在其前被初始化
以上6种触发类初始化的方法成为主动引用。除此之外,所有引用类型的方式都不会触发初始化,称为被动引用:
- 通过子类引用父类的静态字段,不会导致子类初始化
- 通过数组定义引用类,不会触发此类的初始化
- 常量在编译阶段会进入类的常量池,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化
类加载器
类加载器负责将class文件加载到内存中,并为其生成对应的java.lang.Class对象。共有4类类加载器:
- Bootstrap ClassLoader:启动类加载器
- Extension ClassLoader:拓展类加载器
- Application ClassLoader:应用类加载器
- User ClassLoader: 自定义类加载器
类加载机制:
- 全盘负责
当一个类加载器负责加载某个Class时,该Class所依赖和引用的Class也由该类加载器负责,除非显式地使用另一个类加载器 - 双亲委托
先让父加载器试图加载该Class,只有在父类加载器无法加载该Class时才尝试从自己的路径下加载 - 缓存机制
所有加载过的类都会被缓存,当程序需要某个Class时,类加载器先从缓存中搜寻该Class,当不存在该Class时才进行加载。这也是修改Class后必须重新启动JVM,修改才生效的原因。
双亲委托的好处
-
沙箱安全机制:自己写的核心类和拓展类不会被加载,可以防止核心API被随意篡改
-
避免类的重复加载:当父亲已经加载了该类时,子类加载器不会重新加载一次
相关问题
1. new 一个对象背后发生了什么
2. 下面这段代码能运行吗?为什么
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println("string");
}
}
不能。报错如下:
java.lang.NoSuchMethodError: main
Exception in thread "main"
原因在于类加载机制中的双亲委托机制。根据双亲委托,在加载该类时会由Bootstrap ClassLoader进行加载且加载的是java核心库中的java.lang.String并非自定义的java.lang.String。核心库中的java.lang.String并没有Main方法所以会抛出NoSuchMethodError。
3. 能不能自定义一个类叫java.lang.System
看了第2题你可能会说不能,但是其实也能,通过自定义类加载器绕过双亲委托机制。