3、堆里对象头的介绍

上一篇: java 字节码文件解析   https://blog.csdn.net/chenjianhuideyueding/article/details/109786394   

下一篇: Java虚拟机栈  https://blog.csdn.net/chenjianhuideyueding/article/details/110359419

堆,用于存放对象,就像上述的jconsole界面中,可以分为老年代和新生代,新生代又可以分为:伊甸园区和两个survivor区。

3.1、对象的探索

堆中的对象创建:需要划分内存区域。

有两种方式:

一种:指针碰撞

另外一种: 空闲列表

具体采用哪种方式,跟内存是否是规整有关。

内存是否规整,跟垃圾回收的策略有关。

如果采用的是复制算法,那么内存规整,采用指针碰撞的方式

如果是标记清除的方法,那么内存就不规整,采用空闲列表的方式

 

为了防止并发出现问题,一般会使用相关的同步策略。

一个使用cas方法

一个使用本地线程缓存的方式,就是为每一个线程分一块

TLAB,等需要分配新的缓存的时候,才需要同步。

 

分配完之后,会堆分配的内存进行初始化零值。

完成之后会进行对象头的设置。

对象在内存布局中分成三个部分:

对象头,实例数据,对齐填充(要求对象的大小满足8字节的整数倍)

 

对象头的介绍:

对象头由两部分组成:mark word 和 Klass pointer(如果是数组,还有加上数组的长度)

mark word这里会占用8个字节,class workd占用4个字节。

mark word的用处:

1、存gc分代年龄

2、存hash code

3、存锁的信息

这里由参考资料:https://stackoverflow.com/questions/26357186/what-is-in-java-object-header

因为我这里是64位的机器:只是截取其中一部分

//  64 bits:
//  --------
//  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
//  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)

具体各个位的状态如下图:

//    [JavaThread* | epoch | age | 1 | 01]       lock is biased toward given thread
//    [0           | epoch | age | 1 | 01]       lock is anonymously biased
//
//  - the two lock bits are used to describe three states: locked/unlocked and monitor.
//
//    [ptr             | 00]  locked             ptr points to real header on stack
//    [header      | 0 | 01]  unlocked           regular object header
//    [ptr             | 10]  monitor            inflated lock (header is wapped out)
//    [ptr             | 11]  marked             used by markSweep to mark an object
//                                               not valid at any other time

 

查看对象的大小,可以使用jol-core来查看:

相应的jar包,或者是直接将maven的坐标放入到pom.xml中。

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.14</version>
    <scope>provided</scope>
</dependency>

 

编写下面的代码:

package cn.yishijie.jol;
import org.openjdk.jol.info.ClassLayout;
public class JolTest {
    public static void main(String[] args) {
        Object obj = new Object();
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
}

执行,打印出以下结果:

java.lang.Object 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)                           e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

可以发现一个单纯的Object对象,大小是16字节。对象头占12个字节,实例数据为0字节,还有4个字节是填充字节。Space losses 表示损失的字节,就是浪费掉的字节数。分为内部和外部,这个还不知道是什么?

因为jvm存储是按照大端的存储方式,那么高字节在前,将上述写出我们习惯的顺序。

那么mark word的实际结构

unused:25 | hash:31 | unused:1 |  age:4  |  biased_lock:1 | lock:2 (normal object)
00000000 00000000 00000000 0 | 0000000 00000000 00000000 00000000 | 0 | 0000 | 0 | 01
    

最后的三位:0(是否偏向)01(无锁)表示无锁且不偏向。

这里没有打印出hashcode是因为,hashcode的写入,调用了系统的hashcode的方法后,写入的,自己重写的

hashcode方法对这个值不起作用。

这里调用下hashcode的方法。改写上述代码:

package cn.yishijie.jol;
​
import org.openjdk.jol.info.ClassLayout;
​
public class JolTest {
​
    public static void main(String[] args) {
        Object obj = new Object();
        System.out.println(obj.hashCode());
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
}
​

这里仅仅是调用了一下hashcode方法

999966131
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 b3 45 9a (00000001 10110011 01000101 10011010) (-1706708223)
      4     4        (object header)                           3b 00 00 00 (00111011 00000000 00000000 00000000) (59)
      8     4        (object header)                           e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

对mark word部分进行整理:

unused:25 | hash:31 | unused:1 |  age:4  |  biased_lock:1 | lock:2 (normal object)
​
00000000 00000000 00000000 0 | 0111011 10011010 01000101 10110011 | 0| 0000| 0 | 01

999966131 = 11101110011010 0100010110110011 跟hash那部分是一样的。

 

修改下原来的代码如下:

package cn.yishijie.jol;
​
import org.openjdk.jol.info.ClassLayout;
public class JolTest {
    public static void main(String[] args) {
        Object obj = new Object();
        System.out.println(obj.hashCode());
        synchronized (obj){
            System.out.println(ClassLayout.parseInstance(obj).toPrintable());
        }
    }
}

输出结果如下:

999966131
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           28 f3 38 02 (00101000 11110011 00111000 00000010) (37286696)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

对mark word部分进行整理:指向栈中的锁记录的指针+00标记,轻量级锁

00000000 00000000  00000000 00000000 00000010 00111000 11110011 001010 | 00

发现此时hashcode并没有写进mark word

 

注意上述的hashcode都是 identity hash code,用户重写的hashcode是不会写入到该对象头里去的。还有计算了

identity hash code的对象,是无法进入偏向状态的,就算对象已经进入了偏向状态,如果你 identity hash code计算了,那么会撤销偏向锁,膨胀轻量级锁。

 

不知道为什么,我去掉了 identity hash code,还是无法进入偏向锁的状态。

package cn.yishijie.jol;
​
import org.openjdk.jol.info.ClassLayout;
​
public class JolTest {
    public static void main(String[] args) {
        final Object obj = new Object();
        ClassLayout classLayout = ClassLayout.parseInstance(obj);
        synchronized (obj){
            System.out.println(classLayout.toPrintable());
        }
    }
}
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           40 f3 e8 02 (01000000 11110011 11101000 00000010) (48821056)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

发现结果还是处于轻量级锁状态。不知道为什么?

 

代码样式:

package cn.yishijie.jol;
​
import org.openjdk.jol.info.ClassLayout;
​
public class JolTest {
    public static void main(String[] args) throws Exception{
        Object obj = new Object();
        ClassLayout classLayout = ClassLayout.parseInstance(obj);
        new Thread(() -> {
            synchronized (obj){
                try {
                    Thread.sleep(1000L);
                }catch (Exception e){
​
                }
            }
        }).start();
        synchronized (obj){
            System.out.println(classLayout.toPrintable());
        }
    }
}
​

打印结果:

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           1a bd 01 03 (00011010 10111101 00000001 00000011) (50445594)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

发现已经上升到重量级锁了。

 

第二天,继续探索。

参考资料:https://shipilev.net/jvm/objects-inside-out/

我发现第一天,一直没有进入偏向锁的状态,是因为偏向锁是由延迟加载的。延迟时间默认是4s

BiasedLockingStartupDelay = 4000

引入代码:

package cn.yishijie.jol;
​
import org.openjdk.jol.info.ClassLayout;
​
import java.util.concurrent.TimeUnit;
​
public class MyTest {
    public static void main(String[] args) throws Exception{
        TimeUnit.SECONDS.sleep(5L); // 这里睡5s,是为了让偏向锁启动起来。
        Object o = new Object();
        ClassLayout classLayout = ClassLayout.parseInstance(o);
        System.out.println(classLayout.toPrintable());
    }
}
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)                           e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

发现此时的101就是处于偏向状态:此时是匿名偏向状态。

//    [JavaThread* | epoch | age | 1 | 01]       lock is biased toward given thread
//    [0           | epoch | age | 1 | 01]       lock is anonymously biased

继续上代码:

package cn.yishijie.jol;
​
import org.openjdk.jol.info.ClassLayout;
​
import java.util.concurrent.TimeUnit;
​
public class MyTest {
    public static void main(String[] args) throws Exception{
        TimeUnit.SECONDS.sleep(5L);
        Object o = new Object();
        synchronized (o){
            ClassLayout classLayout = ClassLayout.parseInstance(o);
            System.out.println(classLayout.toPrintable());
        }
    }
}

打印结果:

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 58 6a 02 (00000101 01011000 01101010 00000010) (40523781)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

对于mark word 部分

00000000 00000000 00000000 00000000  00000000 01101010 010110 | 00 | 0 | 0000| 1| 01

处于偏向锁状态。

 

上代码:

package cn.yishijie.jol;
​
import org.openjdk.jol.info.ClassLayout;
​
import java.util.concurrent.TimeUnit;
​
public class JolTest {
    public static void main(String[] args) throws Exception{
        TimeUnit.SECONDS.sleep(5L);
        Object obj = new Object();
        ClassLayout classLayout = ClassLayout.parseInstance(obj);
        new Thread(() -> {
            synchronized (obj){
                System.out.println("jeff.chan");
                System.out.println(classLayout.toPrintable());
            }
        }).start();
        synchronized (obj){
            System.out.println("caraliu");
        }
    }
}

打印结果:

caraliu
jeff.chan
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           18 f2 5a 1a (00011000 11110010 01011010 00011010) (442167832)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

发现现在处于轻量级锁,打印caraliu很快就执行完了,jeff.chan的那里等待并不会自旋太久,所以就升级到轻量级锁。

可以给caraliu的那里,睡眠一段时间,那么对于jeff.chan,就会一直自旋,达到一定次数,默认10次,就升级为重量级锁。

package cn.yishijie.jol;
​
import org.openjdk.jol.info.ClassLayout;
​
import java.util.concurrent.TimeUnit;
​
public class JolTest {
​
    public static void main(String[] args) throws Exception{
        TimeUnit.SECONDS.sleep(5L);
        Object obj = new Object();
        ClassLayout classLayout = ClassLayout.parseInstance(obj);
        new Thread(() -> {
            synchronized (obj){
                System.out.println("jeff.chan");
                System.out.println(classLayout.toPrintable());
            }
        }).start();
        synchronized (obj){
            System.out.println("caraliu");
            Thread.sleep(3000L);
        }
    }
}
​

结果:

caraliu jeff.chan java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 1a c3 30 03 (00011010 11000011 00110000 00000011) (53527322) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397) 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,处于重量级锁状态。

 

3.2、Klass Pointer

类的指针,虚拟机可以通过这个指针来确定对应的类,找到这个类的元数据。在32位jvm上,会占4个字节;如果是64位jvm,会占用8个字节,如果是开启了压缩指针

-XX:+UseCompressedOops,那么会压缩至4个字节。jdk8默认是开启指针压缩的。

代码:

package classfile;
​
import org.openjdk.jol.info.ClassLayout;
public class Jvm {
​
    public static void main(String[] args) {
        Object obj = new Object();
      System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
}

 

结果:

java.lang.Object 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)                           e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
​

看结果,发现klass pointer占用4个字节。

加入jvm参数:-XX:-UseCompressedOops

java.lang.Object 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)                           00 1c de 16 (00000000 00011100 11011110 00010110) (383654912)
     12     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

可以发现klass pointer占用8个字节。

 

加上数组:

package classfile;
​
import org.openjdk.jol.info.ClassLayout;
​
public class Jvm {
​
    public static void main(String[] args) {
        Object[] obj = new Object[1];
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
}

打印结果:

[Ljava.lang.Object; 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)                           3b 23 00 20 (00111011 00100011 00000000 00100000) (536879931)
     12     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
     16     4   java.lang.Object Object;.<elements>                        N/A
     20     4                    (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

对象头多了一行,刚好也是1。

 

这个一部分的数据,对于hotspot虚拟机来说,因为是使用直接指针的方式,就是栈中存的引用,直接引用到堆的实列数据,然后实列数据的对象头中,又klass pointer又指向常量池中的类的元数据信息。如果采用的是句柄的方式的,那么堆中会开辟一个句柄池来存放实际实列的地址信息,栈存放句柄引用的位置。这样句柄就相当于是一个中间层,如果移动独对象,只需要改变句柄池里面的引用地址就行,而不用改变栈中的地址。直接地址的方式,可以减少一次指针定位的时间。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值