类加载子系统,只负责加载Class文件,不负责是否可以运行,是否运行是由执行引擎决定的。
从上图可以看出来,类加载器可以将Class文件转换成Class对象
类加载子系统的三个阶段
(1)加载阶段
- 通过一个类的全限定名获取定义此类的二进制字符流。
- 将这个字符流代表的静态数据结构转化为方法区运行时数据结构。
- 在内存中生成一个代表这个类的Class对象,作为方法区这个类的各种数据的访问入口。
(2)链接阶段
- 验证:
确保Class文件是合法的(是否为Java规定的Class文件),不会危害虚拟机。
四种验证方式:文件格式验证、元数据验证、字节码验证、符号引用验证 - 准备:
为类变量 分配内存并设置默认值。
final修饰的类变量(static),在编译时就会分配,准备阶段仅仅是显式初始化。
准备阶段不会为实例变量分配内存及初始化。
类变量分配在方法区中,实例变量是分配到Java堆中。 - 解析
将常量池内的符号引用转换为直接引用的过程。
什么是符号引用?什么是直接引用?
(3)初始化阶段
- 初始化阶段会执行类构造器方法
<clinit>()
过程。 - 类构造器方法不需要定义,是Javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。
- JVM保证子类的
<clinit>()
执行前,必须等父类的<clinit>()
执行完毕。 - 虚拟机必须保证一个类的
<clinit>()
方法在多线程下被同步加锁。
加载阶段的涉及到类加载器,有哪些类加载器?
- Bootstrap ClassLoader:只加载核心类库。rt.jar、resources.jar、sun.boot.class.path下内容
- Ext ClassLoader:只加载包名为Java、Javax、sun等开头的类以及jre/lib/ext子目录。
- User ClassLoader:用户自定义
为什么需要用户自定义呢?
(1)隔离加载类
(2)修改类加载方式
(3)扩展加载源
(4)防止源码泄露
如何自定义加载器?
继承 java.lang.ClassLoader
双亲委派机制
(1)一个类加载器收到加载请求,它并不会加载,而是向上委托直到Bootstrap ClassLoader级别。如父类加载成功,则返回。父类加载失败,则向下加载。
(2)优势
- 避免类的重复加载
- 保护程序安全,防止核心API被随意篡改
自定义一个java.lang.String类
package java.lang;
/**
* @author shang
* @PackageName:java.lang
* @ClassName: String
* @Description: 自定义java。lang.String类
* @date 2020/9/9 12:39
*/
public class String {
static {
System.out.println("自定义的String类");
}
}
测试代码,看是否使用到了我们自定义的java.lang.String类。显然没有。(双亲委派机制)
package com.shang.jvm;
/**
* @author shang
* @PackageName:com.shang.jvm
* @ClassName: Test1
* @Description:
* @date 2020/9/9 12:42
*/
public class Test1 {
public static void main(String[] args) {
java.lang.String s = new java.lang.String();
System.out.println("程序运行完毕");
}
}
明明代码中有main方法,却报错找不到。这也说明没有加载自定义的String类。
package java.lang;
/**
* @author shang
* @PackageName:java.lang
* @ClassName: String
* @Description:
* @date 2020/9/9 12:39
*/
public class String {
static {
System.out.println("自定义的String类");
}
public static void main(String[] args) {
String s = new String(); //在类 java.lang.String 中找不到 main 方法
}
}
沙箱安全机制
不允许自定义JDK的包。
package java.lang;
/**
* @author shang
* @PackageName:java.lang
* @ClassName: MyTest
* @Description:
* @date 2020/9/9 12:57
*/
public class MyTest {
static {
System.out.println("java.lang下的,我的测试类");
}
}
禁止包名为 java.lang
package com.shang.jvm;
/**
* @author shang
* @PackageName:com.shang.jvm
* @ClassName: Test2
* @Description:
* @date 2020/9/9 12:58
*/
public class Test2 {
public static void main(String[] args) {
MyTest myTest = new MyTest(); // (禁止)Prohibited package name: java.lang
System.out.println("程序运行完毕");
}
}
JVM中,表示两个Class对象是否为同一个对象,有两个条件
- 类的完整类名必须一致。
- 加载这个类的ClassLoader必须一致。
类的主动使用与被动使用
(1)主动使用
- 创建类的实例
- 访问某个类或者接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射(eg:Class.forName(""))
- 初始化一个类的子类
- Java虚拟机启动时被标明为启动类的类
- JDK7开始提供的动态语言支持
除了以上七种,其它都看做对类的被动使用,都不会导致类的初始化。