jvm之java类加载机制和类加载器(ClassLoader)的详解
目录
类加载过程
类的生命周期:
类的加载过程:虚拟机把描述类的数据从class文件加载到内存中,并通过验证、准备、解析和初始化之后,最终形成JVM可以直接使用的Java对象。
- 加载
将类的class文件读入到内存中,并为之创建一个class对象。当程序使用任何类的时候,系统都会为其创建一个class对象。加载工作通过类加载器来完成。
通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有以下几种来源:
- 从本地文件系统中加载class文件
- 从JAR、WAR包加载class文件
- 通过网络加载class文件,例如applet
- 把一个Java源文件动态编译,并进行加载
- 验证
确保class文件的字节流中包含的信息符合虚拟机的要求,并且不会危害虚拟机自身的安全。主要包括:文件格式校验;元数据校验;字节码校验;符号引用校验。
- 文件格式校验:字节流是否符合class文件格式规范,并且能被当前虚拟机加载处理
- 元数据校验:对字节码描述的信息进行分析,是否符合java语言语法规范
- 字节码校验:分析数据流和控制,确定语义是否合法,符合逻辑。保证类的方法在运行时不会有危害出现
- 符号引用校验:保证引用一定会被访问到,不会出现类等无法访问的问题。
- 准备
为类的静态变量分配内存,并设置默认初始值。如果是静态常量就设置为表达式所定义的值,而不是默认初始值。a=0;
- 解析
将类的二进制数据中的符号引用替换成直接引用。
符号引用:符号引用是以一组符号来描述所引用的目标,符号引用可以是任何字面形式的字面量,只要不会冲突能够定位到就行,布局和内存无关。
直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄,该引用和内存布局有关,并且一定被加载进来的。
- 初始化
为静态变量赋予正确的初始值。a=10;
类的加载时机
- 创建类的实例,也就是new一个对象的时候
- 访问某个类或接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 当要使用类的class对象进行反射调用的时候
- 当初始化一个类的时候,其父类没有初始化,先初始化这个父类
- 启动虚拟机的时候,用户需要指定一个要执行的主类(main()方法所在的类),虚拟机会先初始化这个类
以下情况不会初始化:
- 通过子类引用父类的静态字段,子类不会初始化
- 通过数组定义来引用类,此类不会初始化
类加载器
两个类相等:需要类本身相等,并且使用同一个类加载器进行加载。类用其类加载器和类全限定名作为类的唯一标识。
应用程序启动时,先把基础类一次性加载到JVM中,其它类等到JVM用到的时候再加载,节省内存空间。
类加载器分类:
- 启动类加载器(Bootstrp loader):使用C++实现,是虚拟机自身的一部分。jre/lib,
Xbootclasspath,jre/classes中的类。负责装载JRE的核心类库
- 扩展类加载器(ExtClassLoader ):jre/lib/ext,
ava.ext.dirs系统变量中的类。负责装载JRE扩展目录ext下的jar类包
- 应用程序类加载器(AppClassLoader ):classpath下的类。默认使用应用程序类加载器加载应用程序的类。
全盘负责委托机制
- 全盘负责:当一个当一个ClassLoder装载一个类时,除非显示的使用另一个ClassLoder,该类所依赖及引用的类也由这个ClassLoder载入。
- 委托机制:是指先委托父类加载器寻找目标,在父类找不到的情况下才在自己的类路径中查找并装载目标类。
安全考虑:创建基础类Integer,并加载,会引起严重后果
使用双亲委派的原因:避免重复加载;安全(例子基础类Integer)
自定义类加载器:继承ClassLoader,重写findclass方法