JVM 类加载机制、对象的创建过程

目录

      • 类加载机制
        • 加载
        • 连接
        • 初始化
        • 类加载|初始化的时机(jvm什么时候加载|初始化一个类)
        • 类加载方式
      • 对象的创建过程
      • 对象的内存布局
      • 对象的访问方式

 

类加载机制

类加载也叫类初始化,包括加载、连接、初始化三个步骤。
 

加载

jvm将类的.class文件读到内存中,并创建对应的java.lang.Class对象。
 

加载由类加载器来完成,jvm提供了3种类加载器

  • Bootstrap ClassLoader :根类加载器,也叫做引导类加载器,负责加载jdk中的核心类。根类加载器是用C++写的,不继承java.lang.ClassLoader。
  • Extension ClassLoader :扩展类加载器,负责加载jdk中的扩展类(非核心类),扩展类加载器是用java写的,继承自ClassLoader。
  • System ClassLoader:系统类加载器,负责加载第三方jar包、我们自己写的类。系统类加载器是用java写的,继承自ClassLoader。

可以继承ClassLoader类来实现自定义的类加载器。

加载顺序:根类加载器、扩展类加载器、系统类加载器、自定义的类加载器

 

jvm的类加载机制有3种,这3种机制共同作用,一起完成类的加载

  • 全盘负责:使用类加载器加载一个类时,这个类所依赖(引用)的类也由该类加载器加载
  • 父类委托:先使用父类的类加载器来加载,如果父类的类加载器加载不了,再使用类本身的类加载器来加载
  • 缓存机制:jvm会缓存加载过的类的class对象,要使用某个类时,先在缓存中搜索是否有对应的class对象,有就直接使用、不再加载,没有才加载。运行程序后,如果修改了某个类,需要重启jvm才会生效,不然使用的是之前缓存的class对象。

 

类加载的大致过程
JVM 类加载机制、对象的创建过程_第1张图片
类加载是按需加载,使用该类时才加载,类加载生成的class对象用全类名唯一标识。

因为存在缓存机制,一个类只加载一次,且一个类在内存中最多只有一个class对象。
 

为什么要使用双亲委派机制去加载类?

避免重复加载同一个.class文件

 

连接

加载获得的class对象是二进制数据,连接是把class对象放到jre中(把class对象连接到jre),jre即java运行时环境。
 

连接分为3个阶段

  • 校验:检验被加载的类的内部结构是否正确、和其他类是否协调一致
  • 准备:为类的成员变量分配内存,设置默认的初始值,比如int赋为0,引用型赋为null
  • 解析:将class对象中的符号引用替换为直接引用

class对象是加载时生成的,加载时class对象中的成员变量还没有分配内存,不知道该成员变量在内存中的地址,只能用符号引用暂时表示该成员变量在内存中的地址。连接的准备阶段给成员变量分配内存,之后就可以用直接引用(内存地址)替换掉符号引用。

 

初始化

初始化是对类进行初始化,不是对类的对象进行初始化。类初始化会执行static代码块、初始化static静态成员。

 

类加载|初始化的时机(jvm什么时候加载|初始化一个类)

  • 创建类的实例。包括通过new来创建、通过反射来创建、通过反序列化来创建。
  • 通过类名调用类的静态成员
  • 初始化这个类的子类之前
  • 通过java.exe运行主类时,JVM会先初始化这个主类,再执行这个主类

 

类加载方式

主要有两种

  • 隐式加载:使用new创建对象,隐式调用类加载器,加载对应的类到 JVM 中,这是最常见的类加载方式
  • 显式加载:使用 loadClass()、forName() 等方法显式加载需要的类,获取到 Class 对象后,调用 Class 对象的 newInstance() 方法来创建类的实例

 

两种类加载方式的区别

  • 隐式加载能直接获取类的实例,显式加载需要调用 Class 对象的 newInstance() 方法来生成类的实例
  • 隐式加载能使用带参的构造函数,而Class对象的 newInstance() 不能传入参数,如果要使用带参的构造函数,可以通过反射获取到该类带参的构造方法,通过反射调用带参的构造方法来创建实例

 

loadClass() 、 forName() 的区别

loadClass() 只执行类加载的第一步:加载,后续操作均未进行;Class.forName() 执行了类加载的整个过程(3步)。

 

对象的创建过程

1、先在常量池中定位该类的符号引用,判断是否已有该类的class对象,如果没有则先加载该类。加载时会执行static代码块、初始化静态成员
 

2、在堆中分配内存空间。有2种分配方式

  • 指针碰撞:适用于连续的内存空间,包括开辟一块内存、移动指针两个步骤
  • 空闲列表:适用于琐碎的内存空间,包括开辟一块内存、修改空闲列表两个步骤

都是2个步骤,不具有原子性,可能出现并发问题,jvm采用CAS算法实现乐观锁,搭配失败重试来保证内存分配的成功率。
 

3、初始化分配的内存空间。分配内存空间时是按成员变量的类型进行分配的,此时初始化成员变量为默认值,比如int型初始化为0,引用型初始化为null
 

4、设置对象头的相关数据,比如GC分代年龄、对象的hashCode、锁状态标识、元数据信息
 

5、执行普通初始块、构造函数

 

对象的内存布局

JVM 类加载机制、对象的创建过程_第2张图片
1、对象头用于存储对象的元数据信息

  • Mark Word 部分存储对象自身的运行时数据,比如哈希值、gc分代年龄、锁状态标识
  • 类型指针指向对象所属的类的元数据,标识对象所属的类

2、实例数据存储的是对象本身的数据,即各成员变量的值

3、对齐填充部分只是让实例数据占用的内存空间是8的倍数,无实际意义

 

对象的访问方式

对象创建之后,在java虚拟机栈中进行访问,有2种访问方式

  • 直接指针访问:虚拟机栈的局部变量表中存储对象的引用(reference类型),通过引用直接访问对象
  • 句柄访问:用句柄存储对象的引用,句柄放在句柄池中,虚拟机栈的局部变量表中存储对象的句柄,相当于二级指针。句柄池是堆中的一块内存。

直接指针访问效率高,但gc回收对象时效率低;句柄访问效率低,但gc回收对象时效率高。HotSpot虚拟机采用的是直接指针访问。

你可能感兴趣的:(JVM,jvm,类加载机制,对象的创建过程)