jvm内存与类加载机制

JVM内存结构

    待补充.....

 

类加载机制

一:java类的加载过程

编译后的Java类是以字节码的形式存在的,它只有被加载到虚拟机内存中才能被使用,它是如何被加载到内存中的呢?

下图为类加载到内存的机制:

作者:夏昊
链接:https://www.zhihu.com/question/20097631/answer/817071740
来源:知乎
著作权归作者所有,转载请联系作者获得授权。
 

1:加载

在加载(注意和类加载是不同的概念)阶段虚拟机需要完成三件事

(1).通过一个类的全限定名(类名全称,带包路径的用点隔开,例如: java.lang.String)来获取其定义的二进制字节流(被编译以后的字节码文件就是二进制的)。

(2).将这个字节流所代表的静态存储结构(字节码文件就是其中一种)转化为方法区的运行时数据结构(能够在虚拟机中存储的结构)。

(3).在Java堆中生成一个代表这个类的 java.lang.Class对象(用于表示这个类的信息),作为对方法区中这些数据的访问入口。

2:验证:

验证,准备,解析统称为连接,作为连接阶段的第一步,验证的主要作用是保证加载进来的二进制流中的信息是符合当前虚拟机要求的,并且不会对虚拟机的安全造成危害。主要包括:

(1)文件格式验证:主要是验证二进制流是否符合class文件格式的规范,并且能被当前的虚拟机处理,例如:主次版本号是否在当前虚拟机处理范围之内,常量池的常量中是否有不被支持的常量类型。只有通过了这个阶段的验证后字节流才会进入内存的方法区中进行存储,从这里我们可以看出加载和验证阶段是交叉进行的,加载还未完成,文件格式验证就已经开始了。

(2)元数据验证:对字节码描述的信息进行语义分析以保证其描述的信息符合java语言规范的要求:例如:这个类是否有父类(所有类除了Object都应该有父类)

(3)字节码验证:确定程序语义是合法的符合逻辑的,如将子类对象赋给父类类型是符合逻辑的,反之,将父类对象赋给子类类型则是不合法的。

(4)符号引用验证:可以对常量池中各种符号引用的信息进行匹配性校验。例如:符号引用中通过字符串描述的全限定名是否能找到对应的类。

3:准备

准备阶段会为静态变量分配内存并设置初始值,注意:该初始值为数据类型的零值例如:

Public static int num = 3;在准备阶段会将num值设置为0而不是3.只有在初始化阶段才会赋值为3.

4:解析

解析阶段是把类中的符号引用转换为直接引用的过程:

符号引用:在编译的时候是不知道类所引用的类的具体地址,因此只能使用符号引用来代替,比如:com.Student类引用了com.Grade类,编译时Student类并不知道Grade类在内存中的实际地址,只能用符号 com.Grade。

直接引用;引用的实际内存地址。

5:初始化

初始化阶段是类加载过程的最后一步,在这个阶段会根据程序中的赋值语句给变量赋值,当有继承关系时先初始化父类,再初始化子类。如果该类还没有被加载和连接,那么初始化之前先加加载和连接。

什么时候会进行初始化呢?

1. 使用new关键字实例化对象的时候

2. 读取或设置一个类的静态字段的时候

3. 调用一个类的静态方法的时候

4. 对类进行反射调用的时候

5. 当虚拟机启动时执行一个类的main方法,会先初始化这个类

二:类加载器:

加载阶段中的第一步:“通过一个类的全限定名来获取其定义的二进制字节流”是通过类加载器来完成的,类加载器分为三种:

1. 启动类加载器(BootStrap ClassLoader),这个类加载器负责将jdk\jre\lib下的类库加载到内存中,启动类加载器无法被应用程序直接使用。

2. 扩展类加载器(Extension ClassLoader),它负责加载jdk\jre\lib\ext中的类库。开发者可以直接使用扩展类加载器。

3. 应用程序类加载器(Application ClassLoader),它用来加载classpath路径(src路径下的文件在编译后会放到WEB-INF/classes路径下。默认的classpath是在这里)指定的类。开发者可以直接使用这个类加载器,如果如果应用程序中没有定义自己的类加载器,一般情况下这个就是程序中默认的类加载器。

我们的应用程序都是由这三种类加载器互相配合进行加载的,如果有必要还可以加入自己定义的类加载器。

三:双亲委派模型

 

下图为双亲委派模型的类加载器的层次关系:


 

双亲委派模型的的工作过程是:如果一个类加载器收到了类加载的请求,它会把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的加载请求最终都会传送到顶层的启动类加载器中。只有当父加载器反馈自己无法完成这个加载请求时(在自己的加载范围内没有搜索到该类),子加载器才会尝试自己去加载。例如:

1. 当应用程序类加载器加载一个类时,它会把类加载请求委派给扩展类加载器。

2. 扩展类加载器又把这个类加载请求委派给启动类加载器。

3. 启动类加载器如果加载失败,在(jdk/jre/lib)里没有找到这个类,会使用扩展类加载器进行加载。

4. 扩展类加载器如果加载失败,在(jdk/jre/lib/ext)里没有找到这个类,会使用应用程序类加载器来加载

5. 应用程序加载器加载失败则会报:ClassNotFoundException异常。

使用双亲委派模型的意义:

例如类java.lang.Object存放在rt.jar中,无论哪个类加载器要加载这个类,最终都会委派启动类加载器来加载,如果没有双亲委派模型用户自己编写了一个java.lang.Object类,放到ClassPath中,系统中将会出现多个不同的Object类,应用程序也会变得混乱。

参考:https://www.zhihu.com/collection/86433369

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章