zoukankan      html  css  js  c++  java
  • JVM系列之:详解java object对象在heap中的结构

    简介

    在之前的文章中,我们介绍了使用JOL这一神器来解析java类或者java实例在内存中占用的空间地址。

    今天,我们会更进一步,剖析一下在之前文章中没有讲解到的更深层次的细节。一起来看看吧。

    对象和其隐藏的秘密

    java.lang.Object大家应该都很熟悉了,Object是java中一切对象的鼻祖。

    接下来我们来对这个java对象的鼻祖进行一个详细的解剖分析,从而理解JVM的深层次的秘密。

    工具当然是使用JOL:

    @Slf4j
    public class JolUsage {
    
        @Test
        public void useJol(){
            log.info("{}", VM.current().details());
            log.info("{}", ClassLayout.parseClass(Object.class).toPrintable());
            log.info("{}", ClassLayout.parseInstance(new Object()).toPrintable());
        }
    }
    

    代码很简单,我们打印JVM的信息,Object class和一个新的Object实例的信息。

    看下输出:

    [main] INFO com.flydean.JolUsage - # Running 64-bit HotSpot VM.
    # Using compressed oop with 3-bit shift.
    # Using compressed klass with 3-bit shift.
    # WARNING | Compressed references base/shifts are guessed by the experiment!
    # WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
    # WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
    # Objects are 8 bytes aligned.
    # Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
    # Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
    
    10:27:32.311 [main] INFO com.flydean.JolUsage - java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0    12        (object header)                           N/A
         12     4        (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    
    10:27:32.312 [main] INFO com.flydean.JolUsage - java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
          4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4        (object header)                           86 06 00 00 (10000110 00000110 00000000 00000000) (1670)
         12     4        (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    

    从上面的结果我们知道,在64位的JVM中,一个Object实例是占用16个字节。

    因为Object对象中并没有其他对象的引用,所以我们看到Object对象只有一个12字节的对象头。剩下的4个字节是填充位。

    Object对象头

    那么这12字节的对象头是做什么用的呢?

    如果想要深入了解这12字节的对象头,当然是要去研读一下JVM的源码:src/share/vm/oops/markOop.hpp。

    有兴趣的小伙伴可以去看看。如果没有兴趣,没关系,这里给大家一个张总结的图:

    javaObject对象的对象头大小根据你使用的是32位还是64位的虚拟机的不同,稍有变化。这里我们使用的是64位的虚拟机为例。

    Object的对象头,分为两部分,第一部分是Mark Word,用来存储对象的运行时数据比如:hashcode,GC分代年龄,锁状态,持有锁信息,偏向锁的thread ID等等。

    在64位的虚拟机中,Mark Word是64bits,如果是在32位的虚拟机中Mark Word是32bits。

    第二部分就是Klass Word,Klass Word是一个类型指针,指向class的元数据,JVM通过Klass Word来判断该对象是哪个class的实例。

    且慢!

    有的小伙伴可能发现了问题,之前我们用JOL解析Object对象的时候,Object head大小是12字节,也就是96bits,这里怎么写的是128bits?

    没错,如果没有开启COOPs就是128bits,如果开启了COOPs,那么Klass Word的大小就从64bits降到了32bits。

    还记得我们之前讲的COOPs吗?

    COOPs就是压缩对象指针技术。

    对象指针用来指向一个对象,表示对该对象的引用。通常来说在64位机子上面,一个指针占用64位,也就是8个字节。而在32位机子上面,一个指针占用32位,也就是4个字节。

    实时上,在应用程序中,这种对象的指针是非常非常多的,从而导致如果同样一个程序,在32位机子上面运行和在64位机子上面运行占用的内存是完全不同的。64位机子内存使用可能是32位机子的1.5倍。

    而压缩对象指针,就是指把64位的指针压缩到32位。

    怎么压缩呢?64位机子的对象地址仍然是64位的。压缩过的32位存的只是相对于heap base address的位移。

    我们使用64位的heap base地址+ 32位的地址位移量,就得到了实际的64位heap地址。

    对象指针压缩在Java SE 6u23 默认开启。在此之前,可以使用-XX:+UseCompressedOops来开启。

    数组对象头

    java中有一个非常特别的对象叫做数组,数组的对象头和Object有什么区别吗?

    我们用JOL再看一次:

    log.info("{}",ClassLayout.parseClass(byte[].class).toPrintable());
    
    log.info("{}",ClassLayout.parseInstance("www.flydean.com".getBytes()).toPrintable());
    

    上面的例子中我们分别解析了byte数组的class和byte数组的实例:

    10:27:32.396 [main] INFO com.flydean.JolUsage - [B object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0    16        (object header)                           N/A
         16     0   byte [B.<elements>                             N/A
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
    
    10:27:32.404 [main] INFO com.flydean.JolUsage - [B object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
          4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4        (object header)                           22 13 07 00 (00100010 00010011 00000111 00000000) (463650)
         12     4        (object header)                           0f 00 00 00 (00001111 00000000 00000000 00000000) (15)
         16    15   byte [B.<elements>                             N/A
         31     1        (loss due to the next object alignment)
    Instance size: 32 bytes
    Space losses: 0 bytes internal + 1 bytes external = 1 bytes total
    

    看到区别了吗?我们发现数组的对象头是16字节,比普通对象的对象头多出了4个字节。这4个字节就是数组的长度。

    整个对象的结构

    好了,写到这里我们来总结一下,java对象的结构可以分为普通java对象和数组对象两种:

    数组对象在对象头中多了一个4字节的长度字段。

    大家看到最后的字节是padding填充字节,为什么要填充呢?

    因为JVM是以8字节为单位进行对其的,如果不是8字节的整数倍,则需要补全。

    本文链接:http://www.flydean.com/jvm-java-object-in-heap/

    最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

    欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

  • 相关阅读:
    2015.2.27 UltraEdit中显示XML结构
    2015.1.31 DataGridView自动滚动到某行
    2015.1.15 利用函数实现将一行记录拆分成多行记录 (多年想要的效果)
    2015.1.15 利用Oracle函数返回表结果 重大技术进步!
    2015.1.15 利用Oracle函数插入表结构 Bulk collect into 不用循环,简洁高效
    2015.1.8 Left join 左连接
    2015.1.10 解决DataGridView SelectionChanged事件自动触发问题
    delphi 遍历窗口
    delphi 访问 protected 属性 哈哈
    clientdataset 读取excel 如果excel 文件不存在的时候 相应的gird 会不显示数据, 鼠标掠过 gird 格子 才会显示数据。 这是一个bug 哈哈
  • 原文地址:https://www.cnblogs.com/flydean/p/jvm-java-object-in-heap.html
Copyright © 2011-2022 走看看