JOL使用样例01-照搬官网

0. details

	@Test
    void testDetails() {
        String details = VM.current().details();
        System.out.println(details);
    }

结果说明:

VM mode: 64 bits 64位虚拟机
Compressed references (oops): 3-bit shift 压缩了的普通对象指针
Compressed class pointers: 0-bit shift and 0x26510000000 base 压缩类指针
Object alignment: 8 bytes 对象对齐
属性名refboolbytecharshrtintfltlngdbl
Field sizes411224488
Array element sizes411224488
Array base offsets161616161616161616
Field sizes(字段大小): 4, 1, 1, 2, 2, 4, 4, 8, 8
Array element sizes(数组元素大小): 4, 1, 1, 2, 2, 4, 4, 8, 8
Array base offsets(数组基偏移量): 16, 16, 16, 16, 16, 16, 16, 16, 16
-XX:+UseCompressedClassPointers //开启压缩类指针
-XX:-UseCompressedClassPointers //关闭压缩类指针
-XX:+UseCompressedOops  //开启压缩普通对象指针
-XX:-UseCompressedOops  //关闭压缩普通对象指针

1. basic

public class JolDemo02Test {
    public static class A {
        boolean f;
    }
    public static void main(String[] args) {
        System.out.println(ClassLayout.parseClass(A.class).toPrintable());
    }
}

不开启压缩类指针(-XX:-UseCompressedClassPointers),结果如下:

org.example.jucdemo2.jol.JolDemo02Test$A object internals:
OFF  SZ      TYPE DESCRIPTION               VALUE
  0   8           (object header: mark)     N/A
  8   8           (object header: class)    N/A
 16   1   boolean A.f                       N/A
 17   7           (object alignment gap)    
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total

开启压缩类指针(-XX:+UseCompressedClassPointers),结果如下:

org.example.jucdemo2.jol.JolDemo02Test$A object internals:
OFF  SZ      TYPE DESCRIPTION               VALUE
  0   8           (object header: mark)     N/A
  8   4           (object header: class)    N/A
 12   1   boolean A.f                       N/A
 13   3           (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

如不说明,下面测试结果都是开启压缩

2. 对齐

In this example, we can see the long field
* is indeed aligned for 8 bytes, sometimes making the gap after the
* object header.
*/
这是个更高级的属性布局样例。由于底层硬件平台总是要求对齐,为了获得更好的性能和正确性。期待属性根据自身占据空间的大小对齐,对于boolean类型来说,这并没有什么,但是对于long类型来说,存在不同的地方。这下面的样例中我们发现long类型的属性缺失占了8个字节,有时和对象头之间存在一个gap

public class JolDemo02Test {
    public static class A {
        boolean f;
        long f2;
    }
    public static void main(String[] args) {
        System.out.println(ClassLayout.parseClass(A.class).toPrintable());
    }
}

结果如下(和boolean属性之间存在gap):

org.example.jucdemo2.jol.JolDemo02Test$A object internals:
OFF  SZ      TYPE DESCRIPTION               VALUE
  0   8           (object header: mark)     N/A
  8   4           (object header: class)    N/A
 12   1   boolean A.f                       N/A
 13   3           (alignment/padding gap)   
 16   8      long A.f2                      N/A
Instance size: 24 bytes
Space losses: 3 bytes internal + 0 bytes external = 3 bytes total

修改代码:

public class JolDemo02Test {
    public static class A {
        long f2;
        //boolean f;

    }
    public static void main(String[] args) {
        System.out.println(ClassLayout.parseClass(A.class).toPrintable());
    }
}

结果如下(和对象头之间存在gap):

org.example.jucdemo2.jol.JolDemo02Test$A object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     N/A
  8   4        (object header: class)    N/A
 12   4        (alignment/padding gap)   
 16   8   long A.f2                      N/A
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

3. packing

这是VM如何打包字段的一个示例。

JVM 通过紧密排列字段来最小化内存占用。运行此示例,可以看到字段被密集地打包,且空隙最小。这是通过按 8->4->2->1 的顺序对齐字段来实现的,因为一旦对齐了 8 字节的字段,就不能打破初始对齐。初始 8 字节对齐产生的空隙可以被一个或多个较小的字段占用。

注意,实际的字段顺序与声明的顺序非常不同。JVM 规范没有要求必须按照声明的顺序排列。

代码如下:

public class JolDemo03Test {
    public static void main(String[] args) {
        System.out.println(ClassLayout.parseClass(A.class).toPrintable());
    }
    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;
        short s1, s2;
    }
}

结果如下:

org.example.jucdemo2.jol.JolDemo03Test$A object internals:
OFF  SZ      TYPE DESCRIPTION               VALUE
  0   8           (object header: mark)     N/A
  8   4           (object header: class)    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   2     short A.s1                      N/A
 66   2     short A.s2                      N/A
 68   1   boolean A.bo1                     N/A
 69   1   boolean A.bo2                     N/A
 70   1      byte A.b1                      N/A
 71   1      byte A.b2                      N/A
Instance size: 72 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

4 继承

该示例,展示了VM如何在类层次结构中布局字段。
VM 需要维护的一个重要不变量是,无论通过哪个类访问字段,这些可访问字段都位于相同的偏移量。
也就是说,对于下面的类 B 和 C,字段 A.a 应该位于相同的偏移量。这促使 VM 首先布局超类的字段。

代码如下:


import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;


public class JolDemo04Test {
    
    public static void main(String[] args) {
        out.println(ClassLayout.parseClass(A.class).toPrintable());
        out.println();
        out.println(ClassLayout.parseClass(B.class).toPrintable());
        out.println();
        out.println(ClassLayout.parseClass(C.class).toPrintable());
    }

    public static class A {
        int a;
        private int d; // 子类会保存这个信息
        static int r;
        public void test(){}
    }

    public static class B extends A {
        int b;
        protected int e;
    }

    public static class C extends B {
        int c;
    }
}

结果如下:

  • 静态属性不在对象数据里面,方法也不在对象数据里面。
  • 子类的对象数据里面保存了父类的私有变量数据信息
  • VM 首先布局父类的字段。
org.example.jucdemo2.jol.JolDemo04Test$A object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     N/A
  8   4        (object header: class)    N/A
 12   4    int A.a                       N/A
 16   4    int A.d                       N/A
 20   4        (object alignment gap)    
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total


org.example.jucdemo2.jol.JolDemo04Test$B object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     N/A
  8   4        (object header: class)    N/A
 12   4    int A.a                       N/A
 16   4    int A.d                       N/A
 20   4    int B.b                       N/A
 24   4    int B.e                       N/A
 28   4        (object alignment gap)    
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total


org.example.jucdemo2.jol.JolDemo04Test$C object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     N/A
  8   4        (object header: class)    N/A
 12   4    int A.a                       N/A
 16   4    int A.d                       N/A
 20   4    int B.b                       N/A
 24   4    int B.e                       N/A
 28   4    int C.c                       N/A
Instance size: 32 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

5.

6.

7. 异常

这个示例展示了VM如何特殊处理某些字段。
在JDK 8及更低版本中,你会在Throwable类中看到可疑的空隙。如果你查看Java源代码,会发现Throwable.backtrace字段,这个字段在内存转储中并未列出。这是因为该字段处理的是VM内部信息,任何时候都不应让用户访问。
在JDK 9及更高版本中,该字段再次可见,并且保证包含Java可读的内容。
参见:
https://bugs.openjdk.java.net/browse/JDK-4496456
https://bugs.openjdk.java.net/browse/JDK-8033735

代码如下:

public class JolDemo07Test {
    public static void main(String[] args) {
        out.println(ClassLayout.parseClass(Throwable.class).toPrintable());
    }
}

结果如下:

java.lang.Throwable object internals:
OFF  SZ                            TYPE DESCRIPTION                      VALUE
  0   8                                 (object header: mark)            N/A
  8   4                                 (object header: class)           N/A
 12   4                             int Throwable.depth                  N/A
 16   4                java.lang.Object Throwable.backtrace              N/A
 20   4                java.lang.String Throwable.detailMessage          N/A
 24   4             java.lang.Throwable Throwable.cause                  N/A
 28   4   java.lang.StackTraceElement[] Throwable.stackTrace             N/A
 32   4                  java.util.List Throwable.suppressedExceptions   N/A
 36   4                                 (object alignment gap)           
Instance size: 40 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

8. Class

该示例展示了某些字段的特殊处理。
如果你运行这个示例,可以看到实例字段块中的大空隙。没有Java字段可以占据这个块,因此这里没有“隐藏”字段,如前面的示例所示。这次,VM将一些字段“注入”到Class中,用于存储一些元信息。

参见:https://github.com/openjdk/jdk/blob/fd45694c58452635db572cb55e5a1b2cb7bc34b2/src/hotspot/share/classfile/javaClasses.hpp#L219-L226

public class JolDemo08Test {
    public static void main(String[] args) {
        out.println(ClassLayout.parseClass(Class.class).toPrintable());
    }
}

结果如下:

java.lang.Class object internals:
OFF  SZ                                              TYPE DESCRIPTION                   VALUE
  0   8                                                   (object header: mark)         N/A
  8   4                                                   (object header: class)        N/A
 12   4                                               int Class.classRedefinedCount     N/A
 16  24                                                   (alignment/padding gap)       
 40   4                     java.lang.reflect.Constructor Class.cachedConstructor       N/A
 44   4                                  java.lang.String Class.name                    N/A
 48   4                                  java.lang.Module Class.module                  N/A
 52   8                                                   (alignment/padding gap)       
 60   4                                  java.lang.String Class.packageName             N/A
 64   4                                   java.lang.Class Class.componentType           N/A
 68   4                       java.lang.ref.SoftReference Class.reflectionData          N/A
 72   4   sun.reflect.generics.repository.ClassRepository Class.genericInfo             N/A
 76   4                                java.lang.Object[] Class.enumConstants           N/A
 80   4                                     java.util.Map Class.enumConstantDirectory   N/A
 84   4                    java.lang.Class.AnnotationData Class.annotationData          N/A
 88   4             sun.reflect.annotation.AnnotationType Class.annotationType          N/A
 92   4                java.lang.ClassValue.ClassValueMap Class.classValueMap           N/A
Instance size: 96 bytes
Space losses: 32 bytes internal + 0 bytes external = 32 bytes total

11 Classword

代码如下:

public class JolDemo11Test {
    public static void main(String[] args) {
        out.println(ClassLayout.parseInstance(new A()).toPrintable());
        out.println(ClassLayout.parseInstance(new B()).toPrintable());
    }

    public static class A {
    }

    public static class B {
    }
}

结果如下:
对象头(object header: class)对应的值不一样

org.example.jucdemo2.jol.JolDemo11Test$A object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0x01001200
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

org.example.jucdemo2.jol.JolDemo11Test$B object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0x0101da20
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

12 偏向锁

这是对标记字(mark word)的一次深入探讨。
标记字除了存储其他信息外,还存储锁定信息。我们可以清楚地看到,在我们获取锁并随后释放锁时,标记字的内容是如何变化的。
在这个示例中,我们演示了偏向锁定。每个Java对象都有可能是同步的目标。大多数情况下,对象只被单个线程锁定。在这种情况下,我们可以将对象“偏向”到那个单个线程,从而使对该对象的同步非常廉价。
为了演示这一点,我们在获取锁之前、期间和之后打印对象的内部信息。你可以注意到,标记字从“可偏向”变为“已偏向”。解锁后,标记字保持不变:对象现在偏向于该线程。

在JDK 9之前,默认情况下,偏向锁定只有在VM启动5秒后才会启用。因此,最佳做法是在JDK 8及更低版本上使用-XX:BiasedLockingStartupDelay=0运行测试。
从JDK 15开始,默认情况下禁用了偏向锁定,因此需要使用-XX:+UseBiasedLocking来运行此测试。

代码如下:

public class JolDemo12Test {
    public static void main(String[] args) {

        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 {
    }
}

执行时增加虚拟机参数 -XX:+UseBiasedLocking, 开启偏向锁,结果如下:

注:Java HotSpot(TM) 64-Bit Server VM warning: Option UseBiasedLocking was deprecated in version 15.0 and will likely be removed in a future release.

**** Fresh object
org.example.jucdemo2.jol.JolDemo12Test$A object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0x01001200
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

**** With the lock
org.example.jucdemo2.jol.JolDemo12Test$A object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000001d7a0d50805 (biased: 0x0000000075e83542; epoch: 0; age: 0)
  8   4        (object header: class)    0x01001200
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

**** After the lock
org.example.jucdemo2.jol.JolDemo12Test$A object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000001d7a0d50805 (biased: 0x0000000075e83542; epoch: 0; age: 0)
  8   4        (object header: class)    0x01001200
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

13 ThinLocking

执行时开启偏向锁 -XX:-UseBiasedLocking, 结果如下:
对比发现:加锁之后是0x000000e581dff5d8 (thin lock: 0x000000e581dff5d8)

**** Fresh object
org.example.jucdemo2.jol.JolDemo12Test$A object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0x01001200
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

**** With the lock
org.example.jucdemo2.jol.JolDemo12Test$A object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000e581dff5d8 (thin lock: 0x000000e581dff5d8)
  8   4        (object header: class)    0x01001200
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

**** After the lock
org.example.jucdemo2.jol.JolDemo12Test$A object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0x01001200
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

14 FatLocking

这是一个胖锁(fat locking)的示例。
如果VM检测到线程之间的竞争,它需要将访问仲裁委托给操作系统。这涉及将对象与本地锁关联起来,即“膨胀”(inflate)锁。
在这个示例中,我们需要模拟竞争,因此引入了一个额外的线程。你可以看到,新对象具有默认的标记字,而对象在辅助线程获取锁之前已经有一个标记字,当主线程最终获取锁时,锁已经被“膨胀”了。即使锁被释放后,膨胀状态仍然保留。

在某些JDK版本中,锁在垃圾回收(GC)后会被“缩减”(deflate)。在JDK 15之前,锁清理在安全点(safepoint)进行,因此任何GC都会进入该代码。从JDK 15开始,监视器在不再使用的数量足够多时异步地被缩减。

代码如下:

public class JolDemo14Test {
     public static void main(String[] args) throws Exception {

         final A a = new A();

         ClassLayout layout = ClassLayout.parseInstance(a);

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

         Thread t = new Thread(() -> {
             synchronized (a) {
                 try {
                     TimeUnit.SECONDS.sleep(10);
                 } catch (InterruptedException e) {
                     // Do nothing
                 }
             }
         });

         t.start();

         TimeUnit.SECONDS.sleep(1);

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

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

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

         System.gc();

         out.println("**** After System.gc()");
         out.println(layout.toPrintable());
     }

     public static class A {}
}

结果如下:

**** Fresh object
org.example.jucdemo2.jol.JolDemo14Test$A object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0x01001200
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

**** Before the lock
org.example.jucdemo2.jol.JolDemo14Test$A object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x00000032a41ff700 (thin lock: 0x00000032a41ff700)
  8   4        (object header: class)    0x01001200
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

**** With the lock
org.example.jucdemo2.jol.JolDemo14Test$A object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000017159410ce2 (fat lock: 0x0000017159410ce2)
  8   4        (object header: class)    0x01001200
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

**** After the lock
org.example.jucdemo2.jol.JolDemo14Test$A object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000017159410ce2 (fat lock: 0x0000017159410ce2)
  8   4        (object header: class)    0x01001200
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

**** After System.gc()
org.example.jucdemo2.jol.JolDemo14Test$A object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000017159410ce2 (fat lock: 0x0000017159410ce2)
  8   4        (object header: class)    0x01001200
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

15 hashcode

这是一个身份哈希码(identity hash code)的示例。
一旦计算出身份哈希码,它就应该保持不变。HotSpot 选择将哈希码存储在标记字中。
一旦计算出哈希码,你可以在对象头中清楚地看到哈希码的字节。

代码如下:

public class JolDemo15Test {

     public static void main(String[] args) {

         final A a = new A();

         ClassLayout layout = ClassLayout.parseInstance(a);

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

         out.println("-------------->hashCode: " + Integer.toHexString(a.hashCode()));
         out.println();

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

     public static class A {}
}
**** Fresh object
org.example.jucdemo2.jol.JolDemo15Test$A object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0x01001200
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

-------------->hashCode: 2096442d

**** After identityHashCode()
org.example.jucdemo2.jol.JolDemo15Test$A object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000002096442d01 (hash: 0x2096442d; age: 0)
  8   4        (object header: class)    0x01001200
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值