类加载子系统
一、类加载子系统简介
类加载器子系统分为三个阶段:
- 加载阶段
- 链接阶段
- 初始化阶段
1.类加载子系统负责从文件系统或者网络中加载class文件,class文件在文件开头有特定的文件标识(16进制CA TE BA BE);
2.ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine来决定
3.加载后的Class类信息存放于一块成为方法区的内存空间。除了类信息之外,方法区还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
4.如果调用构造器实例化对象,则其实例存放在堆区
二、安装ClassViewer插件
环境:IDEA
安装class文件读取插件jclasslib is a bytecode viewer
对类进行编译
显示class字节码文件
二、类加载过程
1.加载
1.通过一个类的全限定明获取定义此类的二进制字节流;
2.将这个字节流所代表的的静态存储结构转化为方法区的运行时数据;
3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
2.链接
1.验证
- 确保Class文件的字节流包含信息符合虚拟机 要求和安全性,保证被加载类的正确性
- 包括四种验证,文件格式验证,源数据验证,字节码验证,符号引用验证。
2.准备
- 为类变量分配内存并且设置该类变量的默认初始值,为零值
- final修饰的staic变量为常量,编译时已经分配值
- 不为实例变量分配初始化,类变量会在方法区中,而实例变量会随着对象一起分配到java堆中
3.解析
-
将常量池内的符号引用转换为直接引用的过程。
-
事实上,解析操作网晚会伴随着jvm在执行完初始化之后再执行
-
符号引用就是一组符号来描述所引用的目标。符号应用的字面量形式明确定义在《java虚拟机规范》的class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄
-
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT_Class_info/CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。
3、初始化
1.初始化阶段时执行类构造器方法()的过程
2.此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来(比如静态变量和静态代码块)
3.构造器方法中指令按语句在源文件中的出现的执行顺序
public class Test1 {
public static int num = 10;
static {
num = 20;
number = 10;//在prepared阶段就已经初始化number = 0,所以可以
System.out.println(num);//0->10->20
//System.out.println(number);//不能向前引用,因为定义变量是在后面的
}
public static int number = 20;//0->10->20
}
4.如果没有静态变量c,那么字节码文件中就不会有()方法
5.()方法就是一个类声明以后的构造方法
6.若一个类有父类,则父类先执行()然后子类再执行
7.虚拟机必须保证一个类的()方法在多线程下被同步加锁(也就是说,如果已经被加载还没执行,别的线程就不能去加载)
三、类加载器的分类
1.JVM支持两种类型的加载器,分别为引导类加载器(BootStrap ClassLoader,C/C++实现)和自定义类加载器(User-Defined ClassLoader由Java实现)
2.从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是java虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。
图中的加载器划分关系为包含关系,并不是继承关系
除了BootStrap ClassLoader,其他都是继承ClassLoader类
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
}
}
1.引导类加载器
引导类加载器(启动类加载器,bootstrap classloader)
-
使用C/C++实现,嵌套在JVM内部
-
它用来加载java的核心库(JAVA_HOME/jre/lib/rt.jar/resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类
-
并不继承自java.lang.ClassLoader,没有父加载器
-
加载拓展类和应用程序类加载器,并指定为他们的父加载器,即ClassLoader
-
出于安全考虑,BootStrap启动类加载器只加载包名为java、javax、sun等开头的类
2.扩展类加载器
-
java语言编写 ,由sun.misc.Launcher$ExtClassLoader实现。
-
派生于ClassLoader类
-
父类加载器为启动类加载器
-
从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会由拓展类加载器自动加载
3.应用程序类加载器
1.java语言编写, 由sun.misc.Launcher$AppClassLoader实现。
2.派生于ClassLoader类
3.它负责加载环境变量classpath或系统属性 java.class.path指定路径下的类库
4.该类加载器是程序中默认的类加载器,一般来说,java应用的类都是由它来完成加载
5.通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器
4.用户自定义类加载器
为什么要定义类加载器?
1.隔离加载类
2.修改类加载的方式
3.拓展加载源
4.防止源码泄漏
实现步骤
继承java.lang.ClassLoader类,重写findClass方法
四、双亲委派机制和沙箱安全机制
1.双亲委派机制
简而言之,就是如果需要加载一个类,会以递归的方式向上委托直到顶层引导类加载器,如果加载不了再层层下放给子类加载器来加载,只要有一个加载成功,加载完成。
例如:
自定义一个java.lang.String,启动main方法,却没有任何输出,直接报错,是因为将加载请求委派给了引导类加载器的String,但是里面没有main方法,所以加载不到这里就已经把String加载完了。
优点:
-
避免类的重复加载
-
这样做明显提高了安全性,保证了核心代码的安全可用,防止api被随意篡改
2.沙箱安全机制
就像在上个例子中说到,自定义String类是先使用引导类加载器进行加载,先加载自带的String,进而保护核心源代码,这就是沙箱安全机制。
五.补充
1.JVM中两个class对象是否为同一个
JVM中两个class对象是否为同一个的两个条件
1.类的完整类名必须一致,包括包名
2.加载这个类的ClassLoader必须一致
如果一个类型由用户类加载器加载的,那么jvm会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。
2.类的主动使用和被动使用
主动使用,分为七种情况:
1.创建类的实例
2.访问某各类或接口的静态变量,或者对静态变量赋值
3.调用类的静态方法
4.反射 比如Class.forName(com.dsh.jvm.xxx)
5.初始化一个类的子类
6.java虚拟机启动时被标明为启动类的类
7.JDK 7 开始提供的动态语言支持:java.lang.invoke.MethodHandle实例的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic句柄对应的类没有初始化,则初始化
除了以上七种情况,其他使用java类的方式都被看作是对类的被动使用,都不会导致类的初始化。
主要就是在加载过程的这个初始化阶段,()方法是否被调用,如果被调用就是完成了初始化,就是主动使用