zoukankan      html  css  js  c++  java
  • JVM之HotSpot中堆里的对象

    HotSpot是一种JVM的实现,它也是目前适用范围最广的Java虚拟机,拥有准确式内存管理和热点代码探测技术等优势。所谓准确式内存管理(Exact Memory Management),就是指虚拟机能够准确判断内存中存放的数据类型,需要了解的话可以进一步去学习。本次主要记录HotSpot中堆内存里对象创建,内存分配,内存布局以及内存访问方式。

    一、对象创建的过程

    我们平时创建对象时用的最多的就是new关键字,那么从new指令之后,在虚拟机中又是如何创建对象的呢?

    • 检查参数:首先JVM会检测到new指令,然后检查new指令的参数(类名)是否定位到类的符号引用,并检查该类是否已经被加载,解析和初始化过。
    • 为对象分配内存:类的符号引用没有问题,加载也已经完成,则开始为对象分配内存。这里需要注意,每个对象的内存在类加载之后就已经确定了,所以划分的内存大小是已知的。
    • 对象的内存初始化:将对象内存中的实例数据部分全部初始化为零值。
    • 对象的设置:设置对象头,存放一些必要的信息(对象所属类、类的元数据指针、对象哈希码、GC分代年龄等)
    • 至此虚拟机的工作完成,对象在堆内存中产生。之后虚拟机会执行Java程序中的<init>方法,根据用户的需求进行对象的初始化工作,完成用户初始化之后,对象才算真正可用。

    ps:对象创建过程中有两点比较重要的部分:

    1. 在为对象进行内存分配的时候,不同堆内存的特点有不同的做法。对于规整的堆内存(已经使用过和未使用的内存界限分明),可以通过指针碰撞来分配内存,具体实现就是将指针后移一段距离;对于不规整的堆内存,可以使用空闲列表的方式进行内存分配,即将可用内存记录在表中,分配对象内存时首先在空闲列表中查找足够大的内存空间,然后再进行分配。
    2. 堆内存是线程共享的,在内存分配过程中也可能会出现并发问题。如A对象的内存还为分配完,B对象也分配与之相同的内存,这样会出现并发问题。处理方法有两种,一是采用CAS+失败重试保证内存分配的原子性,二是使用本地线程分配缓冲(TLAB),在堆内存中为每个线程预先分配一小部分内存,该线程的内存用完了就再分配TLAB给它,TLAB的分配需要同步锁定。

    二、对象的内存布局

    前面提到了在对象的内存初始化以及设置过程中,我们分别设置对象的实例数据和对象头。那么对象的内存布局分为三部分:

    • 对象头:对象头包含对象的一些基本信息,主要分为两部分:
      • 自身运行时数据:如哈希码、分代年龄、锁的状态等。这部分数据的长度是32bit/64bit,依据虚拟机位数确定。
      • 类型指针:存放类的元数据指针,可以通过这个数据确定对象所属的类。
    • 实例数据:对象的实例数据就是我们经常用到的各种字段内容,它包含继承的字段和自己定义的字段。
    • 对齐填充:对齐填充只是为了让对象的内存大小保证是8字节的整数倍,没有实际意义。

    三、对象的访问方式

    对象构建完成之后,我们需要通过引用变量来访问对象,操作对象。对象的访问方式分为两种:

    • 通过句柄访问:通过句柄方式访问时候,我们需要在堆中开辟一部分空间作为句柄池,句柄池中的句柄存放每个对象的地址(堆)以及其类型数据(方法区)的指针。栈上的引用数据指向对象的句柄地址,然后通过句柄再访问对象实例及其类型数据。优点是栈中存储的句柄地址稳定,对象被收集之后只需要修改句柄,无需修改栈,缺点是堆内存中要分一部分空间作为句柄池。

    • 直接访问:直接访问的方式就是在对象的内存布局中放置类型数据相关信息,栈上的引用数据直接指向对象。优点是直接访问对象速度更快,节省指针开销。

  • 相关阅读:
    iOS ARC编译器规则和内存管理规则
    Servlet与JSP的关系
    传统javabean与spring中的bean的区别
    servlet学习笔记
    JAVA里面"=="和euqals的区别
    java垃圾回收
    java中初始化块、静态初始化块和构造方法
    抽象类与接口的区别
    Servlet 与 CGI 的比较
    spring的事务传播特性
  • 原文地址:https://www.cnblogs.com/zhengshuangxi/p/11084372.html
Copyright © 2011-2022 走看看