Java GC详解 – 1. 最全面的理解Java对象结构 – 对象指针 OOPs

2023-05-29 0 356

前段时间在抽空写作 JDK 的源码,主要是 GC 除了 Safepoint 有关的源码,辨认出许多我在以后读到网路上各式各样 JVM 基本概念巨作这时候的我光看该文没看标识符造成的对于下层基本概念的误会。果然,十个人读水浒传,就有数百种水浒。却是须要更加深入的介绍下源码,才能更好地认知 JVM,进行Listary。那个系列产品,将在讲诉 Java GC 各式各样基本概念的基础上,结合相关联的源码分析,并附有源码门牌号。因为JVM的源码预览还是很快的,尤其是 GC 这一块,但是基本基本概念,应该大体上不会变,附有源码门牌号,意在让诸位听众掌握这些基本概念新一代同时实现情形。责任编辑在编写的这时候,会紧随新一代版的 JDK。

GC(Garbage Collection) 是目前许多C语言便携式的优点,例如Java,Python;GC是两个较好的优点,能让使用那个语言程式设计的开发人员JGD5重视缓存拆解,并且降低缓存外泄和缓存外溢发生的机率。

介绍Java GC,须要先知道 Java 最基础的第一类在缓存中究竟是如何储存的。他们著眼于 HotSpot 软件包同时实现,来详细阐释第一类储存内部结构。首先他们来介绍两个概念,第一类操作符(OOPs,Ordinary Object Pointers), 也是第一类头的主要就部分。

1. 第一类操作符(OOPs,Ordinary Object Pointers)

第一类操作符的同时实现,能参照oop.hpp

class oopDesc { private: volatile markWord _mark; union _metadata { Klass* _klass; narrowKlass _compressed_klass; } _metadata; }

从源码能简单看出,第一类操作符主要包括:

1.记号字(Mark Word): 几组记号,叙述了第一类的状况,主要包括第一类预设基元值(假如没有全面覆盖预设的 hashcode() 方式,则基元值在 hashcode() 方式被初始化之后,会被历史记录到记号字之中)、第一类的花纹(是不是字符串)、锁状况(偏重锁等锁信息,值得一提偏重锁在 Java 15 中弃置:Disable and Deprecate Biased Locking)、字符串宽度(假如记号显示那个第一类是字符串,叙述了字符串的宽度)。记号字的同时实现仅仅包含两个uintptr_t类别,所以,在 32 位和 64 位软件包上面,大小分别是 4 二进制和 8 二进制。能参照源码: markWord.hpp。他们这里只讨论 64 位的 JVM,也是标记字为 8 二进制的情形。

class markWord { private: uintptr_t _value; }

2.类别字(Class Word): 类别字是指向第一类同时实现的 Class 的操作符。Java 7 以后指向的区域位于持久带(Permanent Generation),Java 8 之后,持久带弃置,引入了元数据区的概念(Metaspace),所以Java 8 之后指向的是那个元数据区。

那个操作符可能是被压缩的,是压缩操作符(Compressed OOPs)。当开启第一类压缩时占用4二进制(JVM预设开启),关闭时占用8二进制

1.1. 记号字的具体内部结构

对于 64 位的软件包环境,记号字大小是 8 二进制。先给出记号字内部结构:

Java GC详解 – 1. 最全面的理解Java对象结构 – 对象指针 OOPs

(上图来自于:https://www.cnblogs.com/helloworldcode/p/11914053.html

他们先来通过 jol (Java Object Layout) 工具,以及接下来的几个实例,来逐步看一下每种状况下的记号字内部结构。加入依赖:

<!– https://mvnrepository.com/artifact/org.openjdk.jol/jol-core –> <dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.13</version> </dependency>

1.1.1. 基元值计算几次?去哪里找基元值?

假如类没有全面覆盖hashcode(),那么同时实现hashcode()的是两个native方式。

@HotSpotIntrinsicCandidate public native int hashCode();

具体的同时实现参照源码synchronizer.cpp

intptr_t ObjectSynchronizer::FastHashCode(Thread* self, oop obj) { //假如启用了偏重锁优点 if (UseBiasedLocking) { //假如正处于偏重锁 dle hobj(self, obj); if (SafepointSynchronize::is_at_safepoint()) { BiasedLocking::revoke_at_safepoint(hobj); } else { BiasedLocking::revoke(hobj, self); } obj = hobj(); assert(!obj->mark().has_bias_pattern(), “biases should be revoked by now”); } } while (true) { ObjectMonitor* monitor = NULL; markWord temp, test; intptr_t hash; markWord mark = read_stable_mark(obj); // 不能处于偏重锁状况 assert(!mark.has_bias_pattern(), “invariant”); // 未处于任何锁状况的一般状况 if (mark.is_neutral()) { hash = mark.hash(); if (hash != 0) { // 假如已经计算过基元值,直接返回 return hash; } hash = get_next_hash(self, obj); // 否则,计算两个新的基元值,那个方式的详细分析在另一篇该文 temp = mark.copy_set_hash(hash); // 设置基元值 test = obj->cas_set_mark(temp, mark); //预览为设置成功 if (test == mark) { // 假如预览成功,则返回 return hash; } //假如设置失败,可能发生了锁膨胀,或者是多线程同时初始化了hashcode方式,循环重新尝试 } else if (mark.has_monitor()) { //假如处于重量级锁状况 r(); assert(temp.is_neutral(), “invariant: header=” INTPTR_FORMAT, temp.value()); //查看header中的hashcode hash = temp.hash(); //若monitor中有基元值,并且没有发生异步monitor降级(Async Monitor Deflation),就使用那个基元值 //异步monitor降级(Async Monitor Deflation),请参照:https://wiki.openjdk.java.net/display/HotSpot/Async+Monitor+Deflation,这是在 Java 15 之后开始引入的新优点 if (hash != 0) { // It has a hash. // Separate load of dmw/header above from the loads in // is_being_async_deflated(). if (support_IRIW_for_not_multiple_copy_atomic_cpu) { // A non-multiple copy atomic (nMCA) machine needs a bigger // hammer to separate the load above and the loads below. OrderAccess::fence(); } else { OrderAccess::loadload(); } if (monitor->is_being_async_deflated()) { // But we cant safely use the hash if we detect that async // deflation has occurred. So we attempt to restore the // header/dmw to the objects header so that we only retry // once if the deflater thread happens to be slow. monitor->install_displaced_markword_in_object(obj); continue; } return hash; } } else if (self->is_lock_owned((address)mark.locker())) { p.is_neutral(), “invariant: header=” INTPTR_FORMAT, temp.value()); hash = temp.hash(); if (hash != 0) { // if it has a hash, just return it return hash; } } //不处于以上状况或者处于以上锁状况以后,没有计算过基元值,则直接膨胀锁到重量级锁 monitor = inflate(self, obj, inflate_cause_hash_code); //查看是否已经有其他线程计算过基元值 mark = monitor->header(); assert(mark.is_neutral(), “invariant: header=” INTPTR_FORMAT, mark.value()); hash = mark.hash(); //假如没有,则在这里计算,并设置 if (hash == 0) { // if it does not have a hash hash = get_next_hash(self, obj); // get a new hash temp = mark.copy_set_hash(hash); // merge the hash into header assert(temp.is_neutral(), “invariant: header=” INTPTR_FORMAT, temp.value()); uintptr_t v = Atomic::cmpxchg((volatile uintptr_t*)monitor->header_addr(), mark.value(), temp.value()); test = markWord(v); //设置失败,则有其他线程已经设置,尝试读取 if (test != mark) { // The attempt to update the ObjectMonitors header/dmw field // did not work. This can happen if another thread managed to // merge in the hash just before our cmpxchg(). // If we add any new usages of the header/dmw field, this code // will need to be updated. hash = test.hash(); assert(test.is_neutral(), “invariant: header=” INTPTR_FORMAT, test.value()); assert(hash != 0, “should only have lost the race to a thread that set a non-zero hash”); } //假如有发生异步monitor降级(Async Monitor Deflation),跳过,重新判断 //异步monitor降级(Async Monitor Deflation),请参照:https://wiki.openjdk.java.net/display/HotSpot/Async+Monitor+Deflation,这是在 Java 15 之后开始引入的新优点 if (monitor->is_being_async_deflated()) { // If we detect that async deflation has occurred, then we // attempt to restore the header/dmw to the objects header // so that we only retry once if the deflater thread happens // to be slow. monitor->install_displaced_markword_in_object(obj); continue; } } // We finally get the hash. return hash; } }

能看出,hashcode:

尽量只计算一次,计算出后,对于无锁第一类,保存在第一类记号字 Markword 中。处于各式各样锁状况的话(除了偏重锁),都会修改并占用 Markword 导致须要其他地方缓存计算好的 hashcode,对于重量锁是相关联的 Monitor 中保存,对于轻量锁是所指向的锁历史记录的操作符中保存。对于偏重锁,由于没法基元值,所以只要计算过基元值,就不会再进入偏重锁的状况,而是直接从轻量锁开始。对于 JDK 15 之后引入的异步 Monitor 降级(Async Monitor Deflation),须要在那个过程完成或者未开始的这时候读取 monitor第一类的 hashcode 缓存。对于那个优点的详细说明,能参照:Async Monitor Deflation

基元值的具体计算,根据全局变量hashcode的值决定计算方式(通过-XX:hashCode=设定)。预设的基元值计算,是hashcode=5 的情形,不同情形的计算方式的详细说明,假如感兴趣,能参照:JDK核心JAVA源码解析(9) – hashcode 方式

他们来通过一段标识符以及输出来看看这些优点

public static void main(String[] args) throws Exception { A a = new A(); B b = new B(); System.out.println(“——After Initialization——\n” + ClassLayout.parseInstance(a).toPrintable() + “\n” + ClassLayout.parseInstance(b).toPrintable()); System.out.println(“a.hashcode: ” + a.hashCode()); System.out.println(“b.hashcode: ” + b.hashCode()); System.out.println(“——After call hashcode——\n” + ClassLayout.parseInstance(a).toPrintable() + “\n” + ClassLayout.parseInstance(b).toPrintable()); } //A没有全面覆盖预设的 hashcode 方式 public static class A { long d; } //B全面覆盖了 hashcode 方式 public static class B { long d; @Override public int hashCode() { return (int) 5555; } }

输出:

——After Initialization—— com.hashjang.jdk.TestObjectAlign$A 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) 49 ce 00 20 (01001001 11001110 00000000 00100000) (536923721) 12 4 (alignment/padding gap) 16 8 long A.d 0 Instance size: 24 bytes Space losses: 4 bytes internal + 0 bytes external = 4 bytes total com.hashjang.jdk.TestObjectAlign$B 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) 8a ce 00 20 (10001010 11001110 00000000 00100000) (536923786) 12 4 (alignment/padding gap) 16 8 long B.d 0 Instance size: 24 bytes Space losses: 4 bytes internal + 0 bytes external = 4 bytes total a.hashcode: 2124046270 b.hashcode: 5555 ——After call hashcode—— com.hashjang.jdk.TestObjectAlign$A object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 be 5f 9a (00000001 10111110 01011111 10011010) (-1705001471) 4 4 (object header) 7e 00 00 00 (01111110 00000000 00000000 00000000) (126) 8 4 (object header) 49 ce 00 20 (01001001 11001110 00000000 00100000) (536923721) 12 4 (alignment/padding gap) 16 8 long A.d 0 Instance size: 24 bytes Space losses: 4 bytes internal + 0 bytes external = 4 bytes total com.hashjang.jdk.TestObjectAlign$B 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) 8a ce 00 20 (10001010 11001110 00000000 00100000) (536923786) 12 4 (alignment/padding gap) 16 8 long B.d 0 Instance size: 24 bytes Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

能看出,对于未全面覆盖hashcode()方式的 A 类,初始化hashcode()之后,基元值存在了第一类头。基元值为 2124046270,转换为二进制为:1111110 10011010 01011111 10111110。由于 Windows 系统中为小端储存,所以打印出来的 Header 是反过来的,能看到那个基元值就储存在 Header 中:

00000001 10111110 01011111 10011010 01111110 00000000 00000000 00000000

其他优点,将在接下来的 1.1.3. 偏重锁,轻量锁,重量锁 这一小节详细说明。

1.1.2. 分代年龄

第一类头中的分代年龄,用于分代 GC。分代 GC 他们在后面的章节会详细讲诉,这里只是看一些优点。

历史记录分代年龄一共 4 bit,所以最大为 2^4 – 1 = 15。所以配置最大分代年龄-XX:MaxTenuringThreshold=n那个n不能大于15,当然也不能小于 0.等于 0 的话,就直接入老年代。等于 16 的话,是从不进入老年代,这样不符合JVM规范,所以不能大于15(感谢 CSDN @JonsonJiao 指正)。预设是 15。

在发生 Young GC,更准确地说是在 Survivor 区复制的这时候,存活的第一类的分代年龄会加1。他们编写程序测试下,由于 编译器会优化标识符,同时初始化System.gc()并不是立刻触发 GC,并且是 Full GC,可能会使第一类直接进入老年代,分代年龄不再增长,所以他们能使用 volatile 属性辅助他们真正创建对象,避免编译器优化:

static volatile Object consumer; public static void main(String[] args) throws Exception { //这是他们要观察的第一类 Object instance = new Object(); long lastAddr = VM.current().addressOf(instance); for (int i = 0; i < 10000; i++) { //查看门牌号是否发生了变化,代表是否发生了 Survivor 复制,或者是移动到老年代 long currentAddr = VM.current().addressOf(instance); if (currentAddr != lastAddr) { //门牌号发生变化的这时候,打印第一类内部结构 ClassLayout layout = ClassLayout.parseInstance(instance); System.out.println(layout.toPrintable()); lastAddr = currentAddr; } for (int j = 0; j < 10000; j++) { //一直创建新第一类 //因为是volatile的属性预览,不会被编译器优化 consumer = new Object(); } } }

能配合 GC 日志一起观察,关于 JVM 日志配置能参照这篇该文:OpenJDK 11 JVM日志有关参数解析与使用

首先他们用那个参数运行程序-Xmx128m -Xlog:gc=info,输出:

[0.016s][info][gc] Using G1 # WARNING: Unable to get Instrumentation. Dynamic Attach failed. You may add this JAR as -javaagent manually, or supply -Djdk.attach.allowAttachSelf [2.540s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 24M->1M(128M) 2.600ms java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 0d 00 00 00 (00001101 00000000 00000000 00000000) (13) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total [2.627s][info][gc] GC(1) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.273ms java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 15 00 00 00 (00010101 00000000 00000000 00000000) (21) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total [2.675s][info][gc] GC(2) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.063ms java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 1d 00 00 00 (00011101 00000000 00000000 00000000) (29) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total [2.724s][info][gc] GC(3) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.068ms java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 25 00 00 00 (00100101 00000000 00000000 00000000) (37) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total [2.772s][info][gc] GC(4) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.212ms java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 2d 00 00 00 (00101101 00000000 00000000 00000000) (45) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total [2.821s][info][gc] GC(5) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.202ms java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 35 00 00 00 (00110101 00000000 00000000 00000000) (53) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total [2.869s][info][gc] GC(6) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.143ms java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 3d 00 00 00 (00111101 00000000 00000000 00000000) (61) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total [2.917s][info][gc] GC(7) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.313ms java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 45 00 00 00 (01000101 00000000 00000000 00000000) (69) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total [2.969s][info][gc] GC(8) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.473ms java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 4d 00 00 00 (01001101 00000000 00000000 00000000) (77) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total [3.021s][info][gc] GC(9) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.283ms java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 55 00 00 00 (01010101 00000000 00000000 00000000) (85) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total [3.072s][info][gc] GC(10) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.648ms java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 5d 00 00 00 (01011101 00000000 00000000 00000000) (93) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total [3.122s][info][gc] GC(11) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.585ms java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 65 00 00 00 (01100101 00000000 00000000 00000000) (101) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total [3.173s][info][gc] GC(12) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.130ms java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 6d 00 00 00 (01101101 00000000 00000000 00000000) (109) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total [3.224s][info][gc] GC(13) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.078ms java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 75 00 00 00 (01110101 00000000 00000000 00000000) (117) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total [3.273s][info][gc] GC(14) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.135ms java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 7d 00 00 00 (01111101 00000000 00000000 00000000) (125) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total [3.322s][info][gc] GC(15) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.467ms java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 7d 00 00 00 (01111101 00000000 00000000 00000000) (125) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total [3.404s][info][gc] GC(16) Pause Young (Normal) (G1 Evacuation Pause) 76M->1M(128M) 0.556ms [3.485s][info][gc] GC(17) Pause Young (Normal) (G1 Evacuation Pause) 76M->1M(128M) 0.303ms [3.566s][info][gc] GC(18) Pause Young (Normal) (G1 Evacuation Pause) 76M->1M(128M) 0.288ms [3.647s][info][gc] GC(19) Pause Young (Normal) (G1 Evacuation Pause) 76M->1M(128M) 0.317ms [3.727s][info][gc] GC(20) Pause Young (Normal) (G1 Evacuation Pause) 76M->1M(128M) 0.286ms

能看到,在第 15 次 GC 的这时候,第一类进入了老年代,缓存门牌号不再随着 Young GC 的进行而变化。

诸位听众能将-XX:MaxTenuringThreshold=设置为 0 和 16 看看效果。

1.1.3. 偏重锁,轻量锁,重量锁

他们来编写测试标识符:

A a = new A(); System.out.println(“——After Initialization——\n” + ClassLayout.parseInstance(a).toPrintable()); //偏重锁 synchronized (a) { System.out.println(“——After Fetched Lock——\n” + ClassLayout.parseInstance(a).toPrintable()); } System.out.println(“——After Released Lock——\n” + ClassLayout.parseInstance(a).toPrintable()); System.out.println(“a.hashcode: ” + a.hashCode()); System.out.println(“——After call hashcode——\n” + ClassLayout.parseInstance(a).toPrintable()); //由于初始化了 hashcode,这里直接升级成为轻量锁 synchronized (a) { System.out.println(“——After Fetched Lock——\n” + ClassLayout.parseInstance(a).toPrintable()); } System.out.println(“——After Released Lock——\n” + ClassLayout.parseInstance(a).toPrintable()); //测试重量级锁 Runnable r = () -> { synchronized (a) { System.out.println(“——After ” + Thread.currentThread() + ” lock is fetched——\n” + ClassLayout.parseInstance(a).toPrintable()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }; Thread [] threads = new Thread[2]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(r); threads[i].start(); } for (int i = 0; i < threads.length; i++) { threads[i].join(); } System.out.println(“——After Released Lock——\n” + ClassLayout.parseInstance(a).toPrintable());

输出为(他们这里省略掉他们不重视的输出):

——After Initialization—— 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) ——After Fetched Lock—— OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 90 8a 5e (00000101 10010000 10001010 01011110) (1586139141) 4 4 (object header) c7 02 00 00 (11000111 00000010 00000000 00000000) (711) ——After Released Lock—— OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 90 8a 5e (00000101 10010000 10001010 01011110) (1586139141) 4 4 (object header) c7 02 00 00 (11000111 00000010 00000000 00000000) (711) a.hashcode: 929776179 ——After call hashcode—— OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 33 42 6b (00000001 00110011 01000010 01101011) (1799500545) 4 4 (object header) 37 00 00 00 (00110111 00000000 00000000 00000000) (55) ——After Fetched Lock—— OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 78 f2 0f 53 (01111000 11110010 00001111 01010011) (1393554040) 4 4 (object header) ee 00 00 00 (11101110 00000000 00000000 00000000) (238) ——After Released Lock—— OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 33 42 6b (00000001 00110011 01000010 01101011) (1799500545) 4 4 (object header) 37 00 00 00 (00110111 00000000 00000000 00000000) (55) ——After Thread[Thread-0,5,main] lock is fetched—— OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 02 3e e0 b0 (00000010 00111110 11100000 10110000) (-1327481342) 4 4 (object header) 69 02 00 00 (01101001 00000010 00000000 00000000) (617) ——After Thread[Thread-1,5,main] lock is fetched—— OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 02 3e e0 b0 (00000010 00111110 11100000 10110000) (-1327481342) 4 4 (object header) 69 02 00 00 (01101001 00000010 00000000 00000000) (617) ——After Released Lock—— OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 02 3e e0 b0 (00000010 00111110 11100000 10110000) (-1327481342) 4 4 (object header) 69 02 00 00 (01101001 00000010 00000000 00000000) (617)

他们这里通过第一二进制 8 位的末尾两位判断锁状况:

创建第一类后,初始为无锁状况:第一二进制为00000101,01 代表处于无锁状况。同时,偏重锁是开启状况,因为00000101,倒数第三位为1,那个根据前面的内部结构图能知道是偏重锁记号。第一次 main 个偏重锁状况(Hotspot 取消偏重锁须要全局 safepoint,关于那个有关的分析,能参照:JVM有关 – SafePoint 与 Stop The World 全解(基于OpenJDK 11版))。初始化 hashcode,根据以后的源码分析,须要取消偏重锁,同时将 hashcode 写入 header:第一二进制为00000001,代表处于无锁状况,偏重锁关闭,倒数第三位为 0。初始化 h锁,释放轻量锁,锁历史记录会被拆解,所以 hashcode 回到 header 保存。多线程导致第一类升级为重量级锁之后:第一二进制为 00000010,10 代表重量级锁,由于 monitor 一旦生成一直存在,所以那个第一类头会一直保留 monitor 的操作符,hashcode 也会保存在 monitor 上。最后锁释放后,header 没有改变,也是上面说的原因。

1.2. 类别字压缩操作符与 JVM 最大缓存

压缩操作符那个属性预设是打开的,能通过-XX:-UseCompressedOops关闭。

首先说一下为何须要压缩操作符呢?32 位的储存,能叙述多大的缓存呢?假设每两个1代表1二进制,那么能叙述 0~2^32-1 这 2^32 二进制也是 4 GB 的缓存。

Java GC详解 – 1. 最全面的理解Java对象结构 – 对象指针 OOPs

但是呢,Java 预设是 8 二进制对齐的缓存,也是两个第一类占用的空间,必须是 8 二进制的整数倍,不足的话会填充到 8 二进制的整数倍。也就是其实叙述缓存的这时候,不用从 0 开始叙述到 8(是根本不须要定位到之间的1,2,3,4,5,6,7)因为第一类起止肯定都是 8 的整数倍。所以,2^32 二进制假如两个1代表8二进制的话,那么最多能描述 2^32 * 8 二进制也是 32 GB 的缓存。

Java GC详解 – 1. 最全面的理解Java对象结构 – 对象指针 OOPs

这是压缩操作符的基本概念。假如配置最大堆缓存超过 32 GB(当 JVM 是 8 二进制对齐),那么压缩操作符会失效。 但是,那个 32 GB 是和二进制对齐大小有关的,也是-XX:ObjectAlignmentInBytes配置的大小(预设为8二进制,也是 Java 预设是 8 二进制对齐)。-XX:ObjectAlignmentInBytes能设置为 8 的整数倍,最大 128。也是假如配置-XX:ObjectAlignmentInBytes为 24,那么配置最大堆缓存超过 96 GB 压缩操作符才会失效。

编写程序测试下:

A a = new A(); System.out.println(“——After Initialization——\n” + ClassLayout.parseInstance(a).toPrintable());

首先,以启动参数:-XX:ObjectAlignmentInBytes=8 -Xmx16g执行:

——After Initialization—— com.hashjang.jdk.TestObjectAlign$A 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) 48 72 06 00 (01001000 01110010 00000110 00000000) (422472) 12 4 (alignment/padding gap) 16 8 long A.d 0 Instance size: 24 bytes Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

能看到类别字大小为 4 二进制48 72 06 00 (01001000 01110010 00000110 00000000) (422472),压缩操作符生效。

首先,以启动参数:-XX:ObjectAlignmentInBytes=8 -Xmx32g执行:

——After Initialization—— com.hashjang.jdk.TestObjectAlign$A 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) a0 5b c6 00 (10100000 01011011 11000110 00000000) (12999584) 12 4 (object header) b4 02 00 00 (10110100 00000010 00000000 00000000) (692) 16 8 long A.d 0 Instance size: 24 bytes Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

能看到类别字大小为 8 二进制,压缩操作符失效:

a0 5b c6 00 (10100000 01011011 11000110 00000000) (12999584) b4 02 00 00 (10110100 00000010 00000000 00000000) (692)

修改对齐大小为 16 二进制,也是以-XX:ObjectAlignmentInBytes=16 -Xmx32g执行:

——After Initialization—— com.hashjang.jdk.TestObjectAlign$A 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) 48 72 06 00 (01001000 01110010 00000110 00000000) (422472) 12 4 (alignment/padding gap) 16 8 long A.d 0 24 8 (loss due to the next object alignment) Instance size: 32 bytes Space losses: 4 bytes internal + 8 bytes external = 12 bytes total

能看到类别字大小为 4 二进制48 72 06 00 (01001000 01110010 00000110 00000000) (422472),压缩操作符生效。

总结

第一类操作符主要包括记号字与类别字。记号字中保存了:第一类预设基元值(假如没有全面覆盖预设的 hashcode() 方式,则基元值在 hashcode() 方式被初始化之后,会被历史记录到记号字之中)、第一类的花纹(是不是字符串)、锁状况(偏重锁等锁信息,值得一提偏重锁在 Java 15 中弃置:Disable and Deprecate Biased Locking)、字符串宽度(假如记号显示那个第一类是字符串,叙述了字符串的宽度)。记号字的同时实现仅仅包含两个uintptr_t类别,所以,在 32 位和 64 位软件包上面,大小分别是 4 二进制和 8 二进制。能参照源码:markWord.hpp类别字中保存了:指向第一类同时实现的 Class 的操作符。类别字预设是被压缩的,压缩操作符指的是这里。预设基元值计算,对于偏重锁是否生效,是有影响的。是预设基元值与偏重锁不能共存。预设基元值有缓存:无锁缓存在记号字;轻量锁缓存在锁历史记录,记号字中有操作符指向锁历史记录,轻量锁释放后,锁历史记录中的基元值复制到记号字中;重量锁缓存在 monitor 对象,记号字中有操作符指向 monitor 第一类,释放后,基元值依然缓存在 monitor 第一类中。预设基元值计算,须要考虑异步 monitor 降级的情形,这是 Java 15 中的新优点:Async Monitor Deflation分代年龄在每次 Young GC 复制之后 +1,最大是 -XX:MaxTenuringThreshold=n配置的值,大于那个值就进入老年代了。压缩操作符是否启用和 Java 对齐二进制大小(-XX:ObjectAlignmentInBytes,预设是 8,也是 8 二进制对齐)除了最大堆栈大小有关。

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务