本篇内容:
- 类加载过程
- 类加载器分类
- 双亲委托机制
- 沙箱安全机制
类加载过程
类加载器子系统负责将从文件系统或者网络中加载Class文件到内存中。jvm通过按需加载的方式加载class文件,并且只加载一次。加载的类信息存放在方法区的内存空间,除了类信息外,方法区还会存放运行时常量池信息,可能还包括字符串字面值或数字常量(这部分常量信息是Class文件中常量池部分的内存映射)。
类的加载过程:加载->链接(验证、准备、解析)->初始化
- 加载:通过类加载器将.class文件加载进内存,在内存中生成一个Class对象(java.lang.Class),作为方法区这个类的各种数据的访问入口。
虚拟机在加载阶段做了三件事情:(1)通过一个类的全类名来获取此类的二进制字节流。(2)将这个字节流所代表的静态存储结构转化成方法区的运行时数据结构。(3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口 - 验证:确保Class文件中的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机。
主要完成4个检验动作:(1)文件格式验证:验证字节流是否符合Class文件格式的规范,能被当前版本虚拟机处理。主要目的是保证输入的字节流能正确的解析并存储在方法区上,通过验证后,字节流才能进入内存的方法区进行存储,后面的是那个阶段全部是基于方法区的存储结构进行的,不会再直接操作字节流了。(2)元数据验证:对类的元数据信息进行语义校验,保证描述的信息符合java语言规范。(3)字节码验证:第二阶段对元数据信息中的数据类型做完校验后,接着就是对类的方法体进行校验分析,保证被校验类的方法在运行时不会危害虚拟机。(4)符号引用校验:发生在将符号引用转化成直接引用的时候,也就是解析阶段。注意:验证阶段是非常重要的,但不是必要的,如果确保代码没问题,可以通过设置参数-Xverify:none参数来关闭大部分验证措施,来缩短虚拟机类加载时间。 - 准备:为类变量分配内存并设置初始值,基本类型的初始值为0,也能用类型的初始值为null。这些变量所使用的内存都将在方法区进行分配,另外这里分配的类变量,而不是实例变量,实例变量是在对象实例化后随对象一起分配到java堆内存中。通常情况下初始值为0,还有特殊情况,在类字段的字段属性中存在ContantValue属性的话,在准备阶段变量的值就会被初始化成指定的值。static final修饰的字段就会在编译期生成ContantValue属性,在类加载的准备阶段直接把constantValue的值赋给该字段。
- 解析:将常量池内的符号引用替换成直接引用的过程。符号引用:是任意形式的字面量,用一组符号来描述引用的目标,只要无歧义的定位到目标即可,与内存布局无关。直接引用:是直接指向模具表的指针,相对偏移量或是一个能间接定位到目标的句柄,与内存布局有关。
- 初始化:执行类构造器
()方法的过程,对static修饰的变量进行初始化,没有类变量的情况下。 是对象构造器,对非静态变量解析初始化,而 是类构造器对静态变量,对静态代码块进行初始化。
示例:
public class Test {
private int num_1 = 11;
private static int num_2 = 22;
private static final int num_3 = 33;
private int num_4;
public Test(){
this.num_4 = 44;
}
}
类加载器分类
- 引导类加载器(启动类加载器,Bootstrap ClassLoader)
(1)使用c/c++实现,嵌套于jvm内部,
(2)用来加载java核心类库,提供jvm自身需要的类(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路径下的内容)
(3)不继承java.lang.ClassLoader,没有父加载器
(4)加载扩展类和应用程序加载器,并指定为他们的父类加载器
(5)Bootstrap加载器只加载包名为java、javax、sun等开头的类 - 扩展类加载器(Extension ClassLoader)
(1)java语言编写,由sun.misc.Launcher$extClassLoader实现
(2)派生于ClassLoader抽象类
(3)父类加载器为引导类加载器
(4)从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库,如果用户创建的jar放在此目录,也会自动由扩展类加载器加载。 - 应用程序类加载器(系统类加载器 AppClassLoader)
(1)java语言编写,由sun.misc.Launcher$AppClassLoader实现
(2)派生于ClassLoader抽象类
(3)负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
(4)是程序默认的类加载器,一般来说,java应用都是由应用程序类加载器来加载
//类加载器
public class ClassLoaderTest {
public static void main(String[] args) {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//获取其上层:扩展类加载器
ClassLoader extClassLoader = systemClassLoader.getParent();
System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d
//获取其上层:获取不到引导类加载器
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println(bootstrapClassLoader);//null
//对于用户自定义类来说:默认使用系统类加载器进行加载
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//String类使用引导类加载器进行加载的。---> Java的核心类库都是使用引导类加载器进行加载的。
ClassLoader classLoader1 = String.class.getClassLoader();
System.out.println(classLoader1);//null
}
}
输出结果:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@61bbe9ba
null
sun.misc.Launcher$AppClassLoader@18b4aac2
null
Process finished with exit code 0
//类加载器的加载路径
public class ClassLoaderTestPath {
public static void main(String[] args) {
System.out.println("**********启动类加载器**************");
//获取BootstrapClassLoader能够加载的api的路径
URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (URL element : urLs) {
System.out.println(element.toExternalForm());
}
//从上面的路径中随意选择一个类,来看看他的类加载器是什么:引导类加载器
ClassLoader classLoader = Provider.class.getClassLoader();
System.out.println(classLoader);
System.out.println("***********扩展类加载器*************");
String extDirs = System.getProperty("java.ext.dirs");
for (String path : extDirs.split(";")) {
System.out.println(path);
}
System.out.println("***********应用程序类加载器*************");
//从上面的路径中随意选择一个类,来看看他的类加载器是什么:扩展类加载器
ClassLoader classLoader1 = CurveDB.class.getClassLoader();
System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@1540e19d
}
}
输出结果:
**********启动类加载器**************
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/sunrsasign.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jfr.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/classes
null
***********扩展类加载器*************
/Users/cuiqingdong/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java
***********应用程序类加载器*************
sun.misc.Launcher$ExtClassLoader@355da254
双亲委派机制
工作原理:
(1)如果一个类加载器收到类加载的请求,它并不会自己先去加载,而是将这个请求委托给父类的加载器去执行
(2)如果父类加载器还存在父类加载器,则进一步向上委托,依次递归,请求最终到达顶层的引导类加载器
(3)如果父类加载器可以完成类加载任务,就成功返回,如果父类加载器无法完成加载任务,子加载器才会尝试自己去加载,这就是双亲委托机制。
优势:
(1)避免类的重复加载
(2)防止核心api被随意篡改
沙箱安全机制
自定义String类,但是在加载自定义String类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带的文件(rt.jar包中java/lang/String.class),报错信息说没有main方法,就是因为加载的是rt.jar包中的String类,这样保证对java核心源代码的保护,就是沙箱安全机制。