JVM类加载概述
1.类的生命周期
1.1-类的加载过程
- 装载
在装载阶段,java虚拟机将物理磁盘上的.class文件加载到虚拟机内存中- 堆中
在堆中创建class对象对应的实例,堆中分配该class文件的内存空间(不是对象实例的空间而是代表这个类的class实例空间) - 方法区中
方法区中保存该类对应的二进制数据结构,JVM将类加载到方法区中,提取类型信息,保存对应的类数据结构 - 栈中
栈中保存对堆中该class的引用
- 堆中
- 链接
- 验证
验证字节码文件中的符号引用是否合理 - 准备
验证通过后则进行准备阶段,在这个阶段会为类的静态变量赋初始值,如int类型赋值0。特例:如果是全局常量即static final修饰的属性,在准备阶段就会显式赋值(如果赋值依赖于方法体或构造方法的执行的情况除外)。 - 解析
准备就绪后,解析class文件的符号引用,将符号引用替换为直接引用
符号引用和直接引用的区别:符号引用和实际内存地址无关,其是字节码文件中表明引用的符号,直接引用反应实际的内存地址
- 验证
- 初始化
如果在类定义时有为变量赋值,则在初始化阶段会为变量显示赋值。
i.对于非static修饰的变量,使用init方法进行显式赋值
ii.对于static修饰的类变量,使用clinit进行显式赋值
尼
iii.static常量的赋值也可能使用clinit方法,只要赋值过程设计方法或构造器的使用
1.2-类的使用
类的使用分为两种情况
- 主动使用
在主动使用时,如通过new方法创建类的实例,明确使用了该类的静态变量,静态方法,通过反射获取该类对象,初始化该类的子类等,会触发该类的初始化 - 被动使用
在被动使用时,不一定会触发类的初始化,如明确使用的是父类的静态变量,则此时子类会被虚拟机加载但不会初始化
1.3-类的卸载
类的卸载依赖JVM的垃圾回收机制,当代表类的Class对象不在被引用时,即不可触及时,Class对象就会结束生命周期,类在方法区内的数据也会被卸载。由Java虚拟机自带的类加载器所加载的类不会被卸载,因为Java虚拟机本身会始终引用这些类加载器。
2.类的加载器
2.1-加载器分类
- Bootstrap ClassLoader
启动类加载器,由C/C++编写,用于加载jre/lib目录下的类 - Extension ClassLoader
扩展类加载器,用于加载jre/ext目录下的类 - Application ClassLoader
应用程序类加载器,主要用户加载用户自定义的类(用户自定义类加载器的情况除外)
2.2-基本特征
- 可见性
子类加载器可以访问父加载器加载的类,反之不成立。这是为了防止核心类被重复加载 - 单一性
对于任意一个类,都由这个类本身和加载这个类的类加载器共同确定这个类的唯一性。对于同一个类可以自定义多个类加载器加载多次
3.相关机制
3.1-双亲委派机制
向上申请,向下委托。
对于任意一个类的加载,严格遵循双亲委派机制,即由下层的应用加载器开始向上申请加载,如果上层的类加载器无法识别这个类或者没有办法加载这个类,则向下委托到能加载这个类的最高层加载器负责加载。这样做是为了防止用户篡改系统级别的类,如一个用户自定义了一个String类,则双亲委派机制能保证加载的String类一定是jre中确定的String类,自定义的String无法编译运行。
3.2-沙箱安全机制
沙箱是一个限制程序运行的环境。通过沙箱严格限制代码对本地系统资源访问,隔离代码和系统的运行环境。沙箱主要限制系统资源访问,系统资源如CPU、内存、文件系统、网络等。
4.对象实例化
- 判断方法区中有没有该类对应的数据结构
判断该类是否已经被加载,如果有继续执行对象实例化进程,如果没有则触发类的加载 - 为对象分配内存空间
在堆中为对象实例分配内存,用于存放对象实例 - 初始化分配的空间,赋初始值
为新建的对象属性赋初始值,String属性赋值null,int类型赋值0等- 指针碰撞
维护一个指针介于已分配内存和未分配内存之间,每次分配内存后更新指针位置,可以做到分配的内存连续,防止内存碎片化。
对应GC的标记整理算法 - 空闲列表
使用一个列表记录未分配的内存地址,每次分配内存挑选一片空的内存空间,容易造成内存碎片化
对应GC的标记清除算法
- 指针碰撞
- 设置对象头
设置该对象的身份标识,表示对象的“年龄”等信息 - 执行init方法,赋真实值
执行Java程序编译之后在字节码文件中生成的init()方法(称之为实例构造器),init()方法由非静态变量、非静态代码块以及对应的构造器组成,有几个构造方法就会生成几个init方法