从一个new操作理解类加载机制

515 阅读4分钟

背景

基于类加载机制的问题一直以来都是机械式的背诵,一来不长久,二来脱离理解的背诵容易出现奇怪的错误,从最常见的new操作这样的角度来结合JVM理解类加载机制相关过程

类加载机制

类加载的流程大致如下

image.png

而JVM虚拟机的分区大致如下

image.png

1.加载:加载就是检查JVM的方法区内是否存在对应类的符号引用以及对应类是否已被加载,当没有加载的时候就会执行对应加载的流程,即一个全称类名的字符出将对应类的二进制流加载并在其中存放为一个Class对象。

2.连接: 连接内做了三件事:

  1. 验证:即对加载的类的信息,格式,语义的层面对成员字段和方法做检查,使其符合虚拟机规范。但这个验证环节也非必须,虚拟机配置可以调整参数跳过验证阶段从而提升程序的执行效率
  2. 准备:类中的静态成员分配内存并赋给对应类型的默认值,如int为0,String为null
  3. 解析:将对应方法区的符号引用转为直接引用

在这其实有一些思考:

1.验证阶段是否存在检查失败的问题?

个人猜测一般是不会,IDE存在对应的编译检查,写代码的过程中已经对所谓的规范语法做了直接的检查,而JVM应该是对应字节码层级的概念,相当于IDE在java-class这个阶段已经将验证的问题做了规避了。再者如果是其他的情况真的导致验证阶段错误,也会抛出对应的异常verifyException来停止程序

2.准备阶段有针对静态变量的内存分配,解析时将符号引用转为直接引用说明已经内存中存在类对象,那么对于整个类对象的内存分配发生在什么时候?

搜索没有直接的答案,根据这个理解应该是在加载阶段之后连接直接给堆区做了对象的内存分配,因为需要在解析阶段做这个引用的转换,而加载阶段可以知道类所有成员的相关信息,那就可以分配出一个对应的内存大小给到。

3.初始化: 这块的理解就相对简单,就是基于代码逻辑的赋值执行类的初始化

代入理解

假设现在有一个student类,我们执行一个Student s = new Student()的逻辑,代入这个类加载机制流程以及JVM分区

image.png

程序执行开始,Student s = new Student(),判断Student这个类是否加载过?没有,那么往下

1.加载,将Student对应的全称作为字符串,通过双亲委派机制确认类加载器之后将对应Student的信息以二进制流加载到方法区,此时方法区就有一个Student的Class对象的一个符号引用

1.5加载完毕通过类本身信息,给JVM的堆区分配一个对应大小的内存

2.连接: 验证:验证类中的成员与方法等信息是否符合规范 准备:给静态变量分配内存并赋默认值,Student里没有 解析:将方法区内的符号引用转换为直接引用,理解为此时堆内有对应的Student对象的内存地址指针信息,此处为引用关系的链接

3.初始化,针对Student内的age和name字段,将对应的0和null赋值为11和"Jack" 此时理解为Student s = new Student执行完

Student s2= new Student(),理解为此时判断方法区已有对应的Student的Class对象,无需进行对应的加载-连接和初始化,于是直接进入内存分配给堆区分配一个新的Student的对象内存,使用阶段把name字段修改为Mark

总结:

类的加载主要是加载,连接和初始化,加载就是通过全称类名将类通过二进制流加载到方法区并存放一个对应的Class对象连接阶段进行对类的信息验证,确保其符合虚拟机规范,然后对静态成员进行内存分配以及赋初值,最后将符号引用转化为直接引用。初始化阶段执行代码中的赋值逻辑