Java传家宝:微信公众号(Java传家宝)、Java传家宝-B站、Java传家宝-知乎、Java传家宝-CSND
方法区–类加载
方法区与Java堆一样,属于线程共享区域,一般用来存放被虚拟机加载的类信息、类实例、类变量(静态变量)、常量和即时编译器(JIT)编译后的代码等信息。
JIT:就是将热点代码编译与本地平台相关的机器码的编译器。
热点代码:就是执行特别频繁的代码
针对于HotSpot虚拟机对JVM的实现来说,在JDK7之前,方法区是通过永久代实现的,JDK8及之后都是通过元空间实现的。如图:
- 在JDK7之前,方法区的实现采用的是永久代,与堆是连续的一块内存区域;此时字符串常量还存储在方法区中。
- JDK7时,考虑到方法区的空间和字符串回收的频率比较高,所以将其移动到堆区,此时的实现仍是永久代。
- JDK8后,HotSpot VM方法区的实现变成了元空间,方法区不再使用VM内存,而是使用本地内存。
与方法区息息相关的就是类加载了,类加载的信息基本都会存储再方法区当中。类的生命周期分为以下几个
其中,类加载包括了加载、验证、准备、解析和初始化五个部分,以下进行详解。
加载
作为类加载的第一个阶段,在虚拟机规范中,要求完成这三个步骤:
- 通过类的全限定名生成获取定义该类的二进制字节流
- 将二进制字节流所代表的静态存储结构转换为方法区的运行时数据结构
- 在方法区中生成对应的Class对象
所以类对象时存储在方法区中的。
验证
验证是连接阶段的第一步,主要是用来确定Class文件的字节流中包含符合当前虚拟机的要求。主要完成四类验证:
- 文件格式验证:验证符合Class文件格式的规范,这部分操作基于二进制字节流进行的,只有通过了这部分验证,字节流才会进入方法区进行存储,所以后续的验证都是基于方法区的存储结构进行的。
- 是否是魔数开头
- 主次版本是否在当前虚拟机的处理范围之内
- …很多不一一举例了。
- 元数据验证:主要是进行语义分析,保存不存在不符合Java规范的元数据信息
- 是否存在父类
- 是否继承了不允许被继承的类(被final修饰的类)
- …
- 字节码验证:对类的方法体进行校验,保证不会做出危害虚拟机安全的事件
- 保证任何时刻操作数栈的数据类型与指令代码序列都能配合工作。
- 保证跳转指令不会跳转到方法体以外的字节码指令上。
- …
- 符号引用验证:发生在符号引用转化为直接引用时才验证。即在连接的第三阶段解析过程中发生。
- 符号引用中听过类的全限定名能否找到对应的类。
- 符号引用中类的访问权限,是否能够被当前类访问。
- …
准备
准备是连接的第二阶段,用于为类变量分配初始值。这些变量使用的内存都在方法区进行分配
类变量:static修饰的变量,即静态变量,存储在方法区中。
初始值:这里的初始值,对于普通的static变量是指数据类型的零值,而非程序代码的赋值操作。而对于static final变量,虚拟机在编译时将会为其生成ConstantValue属性,在准备阶段就根据ConstantValue的设置为其赋值。
数据类型 | 零值 |
---|---|
boolean | false |
char | ‘\u0000’ |
byte | (byte) 0 |
short | (short) 0 |
int | 0 |
long | 0l |
float | 0.0f |
double | 0.0d |
reference | null |
解析
将常量池中的符号引用替换为直接引用。
符号引用:用一组符号来描述引用的目标,可以是任意形式的字面量,只要使用时能无歧义的定位到目标即可。
直接引用:直接指向目标的指针、相对偏移量或者句柄,与内存布局相关。
初始化
在前面的类加载过程中,除了在加载阶段程序员能够自定义类加载器类型外,其他动作都是由虚拟机主导和控制,只有在初始化阶段,才真正开始执行程序代码。**在准备阶段,已经为类变量分配了零值。本阶段,会按照程序代码初始化类变量和其他资源。**或者说成是执行类构造器()方法的过程。
类构造():是虚拟机为所有的类变量赋值和static{}静态语句块合并生成的。
类加载器
类加载器的作用就是在加载阶段通过一个类的全限定名来获取描述此类的二进制字节流。值得注意的是,比较两个类是否相等,必须是两个类在同一个类加载器加载的前提下才有意义,否则,不可能相等。
双亲委派模型
首先说一下类加载器的类型:除了启动类加载器是用C++语言实现的,并且属于虚拟机的一部分,其余的都是用Java实现的,独立于虚拟机外部。
- 启动类加载器:用于加载<JAVA_HOME>/lib目录下的类,或者被-Xbootclasspath参数指定的类加载到虚拟机内存中。
- 扩展类加载器:用于加载<JAVA_HOME>/lib/ext目录下的类,或者·被java.ext.dirs系统变量所指定的类。
- 应用程序类加载器:负责加载用户类路径(Classpath)上指定的类。开发者可直接使用,一般是程序默认的类加载器。
在说到双亲委派模型,要求除了顶层的启动类加载器外,其余加载器都应当有其父类加载器。这里的父子关系不是以继承的方式实现,而是使用组合关系来复用父类加载器的代码。
双亲委派过程
当类加载器收到类加载的请求时,不会优先去加载他,而是先将这个请求委托给父类加载器,因此所以类加载请求都会委派给最顶层的启动类加载器。如果父类加载器不能加载该类时,才将请求下沉给子类加载器。
双亲委派的特点可以总结为:
- 使得Java类拥有天然的层次关系
- 避免出现多个同名不等的类,使得程序一片混乱。