垃圾收集分析(1)-Java对象结构(上)

GC(Garbage Collection)是目前很多编程语言自带的特性,例如Java,Python;GC是一个很好的特性,能让使用这个语言编程的程序员不去关心内存回收,并且降低内存泄漏和内存溢出发生的概率。
我们以Java语言JVM为例,从其对象结构和JVM运行时内存结构出发,针对其GC算法思路和实现进行分析,同时类比其他GC算法。
首先,在Java 8中,Java对象在内存中结构包括:
1. 类型指针:一个指向类信息的指针,描述了对象的类型。
2. 标记字(Mark Word):一组标记,描述了对象的状态,包括对象散列码(如果有)、对象的形状(是否是数组)、锁状态、数组长度(如果标记显示这个对象是数组,描述了数组的长度)
3. 对齐性填充:所有对象都是8字节对齐的 -> 也就是说,所有对象的起始位置都是满足A(A%8==0),所以对于有的对象需要这个对齐性填充来满足这个规则。
4. 域变量区域:这个对象的域变量所占用的内存。Java域变量存在两类:原始类型(primitive type)和普通对象指针(ordinary object pointer)。

同时,Java对象内存分布还有一些规则,通过openjdk的jol(http://openjdk.java.net/projects/code-tools/jol/)工具我们来查看下这些规律:
Maven引入包:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.7.1</version>
</dependency>

1.对齐性填充在域变量区域之前或者末尾(全为8字节长度类型时,补位在前,否则补位在后),测试代码:

public class MainTest {
    public static void main(String[] args) throws Exception {
        out.println(VM.current().details());
        out.println(ClassLayout.parseClass(A.class).toPrintable());
    }

    public static class A {
        long f;
    }
}

结果:

# Running 64-bit HotSpot VM.
# Using compressed oop with 0-bit shift.
# Using compressed klass with 3-bit shift.
# 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]

test.MainTest$A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0    12        (object header)                           N/A
     12     4        (alignment/padding gap)                  
     16     8   long A.f                                       N/A
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

由于整体对象的纯大小为20bytes,不能满足8bytes对齐,所以需要补位。补位4bytes。
2.原始类型域会重排序,按照长度大小从大到小排列(64位机器上reference类型占用8个字节,开启指针压缩后占用4个字节。其他原始类型如下例子所示)

public static class B{
        boolean bo1, bo2;
        byte b1, b2;
        char c1, c2;
        double d1, d2;
        float f1, f2;
        int i1, i2;
        long l1, l2;
        short s1, s2;
}

内存中结构:

 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0    12        (object header)                           N/A
     12     4        (alignment/padding gap)                  
     16     8   long A.f                                       N/A
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

test.MainTest$B object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0    12           (object header)                           N/A
     12     4     float B.f1                                      N/A
     16     8    double B.d1                                      N/A
     24     8    double B.d2                                      N/A
     32     8      long B.l1                                      N/A
     40     8      long B.l2                                      N/A
     48     4     float B.f2                                      N/A
     52     4       int B.i1                                      N/A
     56     4       int B.i2                                      N/A
     60     2      char B.c1                                      N/A
     62     2      char B.c2                                      N/A
     64     2     short B.s1                                      N/A
     66     2     short B.s2                                      N/A
     68     1   boolean B.bo1                                     N/A
     69     1   boolean B.bo2                                     N/A
     70     1      byte B.b1                                      N/A
     71     1      byte B.b2                                      N/A
Instance size: 72 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

3.子类的域排列在父类的域之后,但是注意,如果父类需要后置补位,则会将子类的某些域提前,来补位,但是整体上还是满足子类的域在父类的域后面,只是之前的1号规则域变量按长度从大到小排序的规则就不满足了:

    public static class A{
        boolean bo1, bo2;
        byte b1, b2;
        char c1, c2;
        double d1, d2;
        float f1, f2;
        int i1, i2;
        long l1, l2;
    }

    public static class B extends A{
        boolean bo1, bo2;
        byte b1, b2;
        char c1, c2;
        double d1, d2;
        float f1, f2;
        int i1, i2;
        long l1, l2;
    }

    public static class C extends B{
        boolean bo1, bo2;
        byte b1, b2;
        char c1, c2;
        double d1, d2;
        float f1, f2;
        int i1, i2;
        long l1, l2;
    }

对于C,内存中结构为:

test.MainTest$C object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0    12           (object header)                           N/A
     12     4     float A.f1                                      N/A
     16     8    double A.d1                                      N/A
     24     8    double A.d2                                      N/A
     32     8      long A.l1                                      N/A
     40     8      long A.l2                                      N/A
     48     4     float A.f2                                      N/A
     52     4       int A.i1                                      N/A
     56     4       int A.i2                                      N/A
     60     2      char A.c1                                      N/A
     62     2      char A.c2                                      N/A
     64     1   boolean A.bo1                                     N/A
     65     1   boolean A.bo2                                     N/A
     66     1      byte A.b1                                      N/A
     67     1      byte A.b2                                      N/A
     68     4     float B.f1                                      N/A
     72     8    double B.d1                                      N/A
     80     8    double B.d2                                      N/A
     88     8      long B.l1                                      N/A
     96     8      long B.l2                                      N/A
    104     4     float B.f2                                      N/A
    108     4       int B.i1                                      N/A
    112     4       int B.i2                                      N/A
    116     2      char B.c1                                      N/A
    118     2      char B.c2                                      N/A
    120     1   boolean B.bo1                                     N/A
    121     1   boolean B.bo2                                     N/A
    122     1      byte B.b1                                      N/A
    123     1      byte B.b2                                      N/A
    124     4     float C.f1                                      N/A
    128     8    double C.d1                                      N/A
    136     8    double C.d2                                      N/A
    144     8      long C.l1                                      N/A
    152     8      long C.l2                                      N/A
    160     4     float C.f2                                      N/A
    164     4       int C.i1                                      N/A
    168     4       int C.i2                                      N/A
    172     2      char C.c1                                      N/A
    174     2      char C.c2                                      N/A
    176     1   boolean C.bo1                                     N/A
    177     1   boolean C.bo2                                     N/A
    178     1      byte C.b1                                      N/A
    179     1      byte C.b2                                      N/A
    180     4           (loss due to the next object alignment)
Instance size: 184 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

4.首位补位会被继承:
例如:

    public static class A {
        long a;
    }

    public static class B extends A {
        long b;
        int c;
    }

这里A类在首位有补位,若不考虑补位的话B的大小正好是8的倍数,应该不需补位,但是由于补位也会继承,所以B需要在末尾补位:

test.MainTest$A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0    12        (object header)                           N/A
     12     4        (alignment/padding gap)                  
     16     8   long A.a                                       N/A
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

test.MainTest$B object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0    12        (object header)                           N/A
     12     4        (alignment/padding gap)                  
     16     8   long A.a                                       N/A
     24     8   long B.b                                       N/A
     32     4    int B.c                                       N/A
     36     4        (loss due to the next object alignment)
Instance size: 40 bytes
Space losses: 4 bytes internal + 4 bytes external = 8 bytes total

5.不同的JVM环境下,对象头的大小不同:
对于类A:

public static class A{
    long l1, l2;
}

我们执行:

Layouter l = new HotSpotLayouter(new X86_32_DataModel());
System.out.println("***** " + l);
System.out.println(ClassLayout.parseClass(A.class, l).toPrintable());

l = new HotSpotLayouter(new X86_64_DataModel());
System.out.println("***** " + l);
System.out.println(ClassLayout.parseClass(A.class, l).toPrintable());

l = new HotSpotLayouter(new X86_64_COOPS_DataModel());
System.out.println("***** " + l);
System.out.println(ClassLayout.parseClass(A.class, l).toPrintable());

输出为:

***** VM Layout Simulation (X32 model, 8-byte aligned, compact fields, field allocation style: 1)
test.MainTest$A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     8        (object header)                           N/A
      8     8   long A.l1                                      N/A
     16     8   long A.l2                                      N/A
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

***** VM Layout Simulation (X64 model, 8-byte aligned, compact fields, field allocation style: 1)
test.MainTest$A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0    16        (object header)                           N/A
     16     8   long A.l1                                      N/A
     24     8   long A.l2                                      N/A
Instance size: 32 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

***** VM Layout Simulation (X64 model (compressed oops), 8-byte aligned, compact fields, field allocation style: 1)
test.MainTest$A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0    12        (object header)                           N/A
     12     4        (alignment/padding gap)                  
     16     8   long A.l1                                      N/A
     24     8   long A.l2                                      N/A
Instance size: 32 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

6. 对象头结构探究与验证:
我们用两个空域的类对象来查看对象实例头结构:

public static class A{
}
public static class B{
}

执行:

out.println(VM.current().details());
out.println(ClassLayout.parseInstance(new A()).toPrintable());
out.println(ClassLayout.parseInstance(new B()).toPrintable());

结果:

# Running 64-bit HotSpot VM.
# Using compressed oop with 0-bit shift.
# Using compressed klass with 3-bit shift.
# 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]

test.MainTest$A 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)                           5d f0 00 20 (01011101 11110000 00000000 00100000) (536932445)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

test.MainTest$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)                           ed f1 00 20 (11101101 11110001 00000000 00100000) (536932845)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

对于64bits的JVM,开启指针压缩的对象头占12bytes(指针压缩将8bytes的reference类型压缩成了4bytes,本来对象头包括MarkWord和一个指向对象类型的reference类型,32bitsJVM的MarkWord占32bits,64bitsJVM的MarkWord占64bits,即8bytes,加上压缩指针后的对象类型指针,就是12bytes)
7. 轻量锁状态下的对象头:
轻量锁(thin lock)就是没有被争夺过的锁,重量锁(fat lock)就是被同时几个线程所争夺过的锁。
一个轻量级锁的例子:

public static void main(String[] args) throws Exception {
    out.println(VM.current().details());

    final A a = new A();

    ClassLayout layout = ClassLayout.parseInstance(a);

    out.println("**** Fresh object");
    out.println(layout.toPrintable());

    synchronized (a) {
        out.println("**** With the lock");
        out.println(layout.toPrintable());
    }

    out.println("**** After the lock");
    out.println(layout.toPrintable());
}

public static class A{

}

结果:

# Running 64-bit HotSpot VM.
# Using compressed oop with 0-bit shift.
# Using compressed klass with 3-bit shift.
# 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]

**** Fresh object
test.MainTest$A 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)                           5d f0 00 20 (01011101 11110000 00000000 00100000) (536932445)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

**** With the lock
test.MainTest$A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           20 ea e4 02 (00100000 11101010 11100100 00000010) (48556576)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           5d f0 00 20 (01011101 11110000 00000000 00100000) (536932445)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

**** After the lock
test.MainTest$A 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)                           5d f0 00 20 (01011101 11110000 00000000 00100000) (536932445)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

首先我们回顾下普通的加锁过程,首先在代码进入同步块的时候,如果此同步对象没有被锁定(锁标志位为“01”状态),结构如下:

这里写图片描述
虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁记录目前的Mark Word的拷贝(称为Displaced Mark Word),同时,紧跟着一个owner指针指向对象头;
这里写图片描述

这时,为了获取锁,该线程尝试CAS更新(compareAndSet(0000……,48556576))这个对象的Mark Word更新为指向Lock Record的指针:如果更新成功,则变成:

这里写图片描述

如果更新失败,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧。如果指向,说明当前线程已经拥有了这个对象的锁(重入锁),那就可以直接进入同步块继续执行,否则说明这个锁对象已经被其他线程抢占了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值