文章目录
虚拟机类加载机制
类的生命周期
- 加载
- 连接
- 验证
- 准备
- 解析
- 初始化
- 使用
- 卸载(在垃圾回收中)
类型的加载,连接,初始化都是在程序运行时完成的,为Java应用提供了极高的扩展性和灵活性
Java天生可以动态扩展的语言特性:依赖运行期动态加载,动态连接这个特点实现的
加载阶段
-
类加载器根据类的全限定名通过不同的渠道以二进制流的方式获取字节码信息(类加载器将类的信息获取到)
-
类加载器加载完类后,Java虚拟机会将字节码中的信息保存到方法区,在方法区中生成一个对象InstanceKlass,保存所有类的信息,里边包含实现特定功能,如多态的信息
-
同时,在堆中生成一份与方法区中数据类似的java.lang.Class对象(用来在Java代码中去获取类的信息以及存储静态字段的数据,JDK8之后·这个静态字段数据就存放在堆区)
问题:为什么要在方法区和堆区都要创建相同的对象
- InstanceKlass对象是用c++编写的,我们不能直接访问,需要将它用Java语言进行包装
- 堆区中的java.lang.Class中的数据是少于方法区中对象的,而开发人员只需要访问其中一部分数据,这样Java虚拟机就能很好的控制开发者访问数据的范围。
可以使用JDK自带的hsdb工具查看虚拟机内存信息,启动命令java -cp sa-jdi.jar sun.jvm.hotspot.HSDB
jps
可显示系统中所有的java进程id
连接阶段
-
验证:验证内容是否满足Java虚拟机规范,不满足的话会直接抛出错误,一般不需要程序员参与
- 文件格式验证,检验魔数
- 元信息验证,例如类中必须有父类(super不能为空)
- 验证程序执行指令的语义,例如不能跳转到不存在的语句中
- 符号引用验证,例如是否访问了其他类中的private方法
-
准备:给静态变量分配内存并赋初值(每种数据类型的变量都有其初始值)
- 如果是final修饰的基本数据类型的静态变量,准备阶段直接会将代码中的值进行赋值。
-
解析:给常量池中的符号引用替换成指向内存的直接引用
-
符号引用就是在字节码文件中用编号找到常量池中对应编号的内容
-
直接引用不再使用编号,而是使用内存中的地址进行访问具体的数据。这种方式性能比较高。
-
初始化阶段
一个类的加载并且初始化只会执行一次
- 执行静态代码块中的代码,并为静态变量赋值
- 执行字节码文件中clinit部分的字节码指令
clinit方法中的执行顺序与Java中编写的顺序是一致的
以下几种方式会导致类的初始化:
- 访问一个类的静态变量或者静态方法(如果变量是final修饰的并且等号右边是常量不会触发初始化
- 调用Class.forName(String className)
- new一个该类的对象时
- 执行Main方法的当前类(执行main方法之前要将main方法所在类加载初始化)
clinit指令在特定情况下不会出现
-
无静态代码块且无静态变量赋值语句
-
有静态变量的声明,但是无赋值
-
静态变量定义使用final关键字,在连接阶段的准备阶段直接进行初始化。
-
访问父类的初始化变量不需要初始化子类
-
初始化子类之前一定会初始化父类
-
数组的·创建不会导致数组·中元素的类进行初始化
-
final修饰的变量等号右边的内容如果不是常量而是需要执行指令才能得出结果的内容,会执行clinit方法进行初始化。
类加载器
是jvm提供给应用程序去实现获取类和接口字节码数据的技术
类加载器的分类
- Java代码中实现的
- Java虚拟机底层源码实现的
类加载器作用:
将字节码信息以流的方式获取并加载到内存中
加载class文件,提供了三层类加载器
- 启动类(根)加载器 Bootstrap classLoader(位置:\jre\lib)负责加载核心的类库,如rt.jar中的一些类
- 扩展类加载器 ExtClassLoader(负责加载位于 \jre\lib\ext目录下的一些扩展的jar)(通用但是不重要)
- 应用程序加载器 AppClassLoader (负责加载自己编写的类和第三方jar包中的类)
双亲委派机制
- 类加载器收到类加载的请求
- 将这个请求不断向上委托给父类加载器去完成,直到启动类加载器
- 启动类加载器检查能否加载这个类,如果不能,抛出异常,通知子类加载器进行加载
- 不断重复步骤3,直到找到能加载这个类的加载器,或者到最后的应用程序加载器也不能加载这个类的话,抛出异常Class Not Found
好处:
- 避免恶意代码替换JDK中的核心类库
- 避免一个类重复的被加载
打破双亲委派机制的三种方式
- 自定义类加载器
- 自定义类加载器并且重写loadClass方法,如Tomcat
- Tomcat使用自定义的类加载器,每个应用都会有一个独立的类加载器加载对应的类
- 线程上下文类加载器
- 利用上下文类加载器加载类
- JDBC案例·,DriverManger属于rt.jar,由启动类加载器加载,而用户jar包中的驱动需要由应用类加载器加载,就违反了双亲委派机制
- Osgi框架类加载器
JDK8及之前的类加载器
扩展类加载器和应用类加载器的源码位于rt.jar包中的sun.misc.Launcher.java,是按照jar包的位置来加载字节码文件
JDK9及之后的类加载器
引入了module的概念,类加载器在设计上发生了很多变化
- 启动类加载器使用java编写
- 启动类加载器依然无法通过java代码获取到,返回依然是null,为了保持统一
- 扩展类加载器被替换成了平台类加载器