建群邀请宝妈总结_[Java JVM] Hotspot GC研究- 开篇&对象内存布局_二维码生成器

我们提供【社群管理裂变】【自动建群】【多群转播】【活码系统】【小程序开发】【公众号开发】【各类商城SAAS】一站式服务,各类功能提供免费体验,满意付款,如您还有其他疑问请您添加企鹅/微信1003312430方便咨询哦。
非凡社群助手——微信活码系统演示(客户活码二维码累计扫描量一千万+)
活码系统介绍:
二维码图案不变,内容可随时变更, 极大提高营销效果,基于活码技术,二维码图案更简单,扫码更加容易宣传海报、二维码印刷、商品.群.软文。
让二维码图案更简单,扫码更加容易宣传。,

点击箭头处“蓝色字”,关注我们哦!!

前言

上一篇文章对JVM的运行时数据区域的内容举行了梳理,本篇文章对JVM中的工具和工具的内存结构举行深入剖析。本文参考了《深入明白Java虚拟机》、《深入剖析Java虚拟机HotSpot》、《HotSpot实战》三本书。

下面提到的虚拟机都特指JDK1.8版本的HotSpot VM,其他虚拟机的实现有可能不太一样。

类与工具

在编译时,通过Javac编译器为虚拟机规范的class文件花样。class文件花样是与操作系统和机械指令集无关的、平台中立的花样。其他语言编写的代码只需要实现指定语言的编译器编译位JVM规范尺度的class文件就可以实现该语言运行在JVM之上,这就是JVM的语言无关性。

通过java下令运行class文件,首先会通过类加载器将class文件加载到内存中,加载class文件会为类天生一个klass实例。在klass包罗了用于形貌Java类的元数据,包罗字段个数、巨细、是否为数组、是否有父类、方式信息等。

工具类二分模子

HotSpot虚拟机是使用C++实现的, C++也是面向工具语言。可以接纳java类逐一映射到C++类,当确立Java工具就确立对应的C++类的工具。

然则由于若是C++的工具含有虚函数,则确立的工具会有虚方式表指针,指向虚方式表。若是接纳这种直接一对一映射的方式,会导致含有虚方式的类确立的工具都包罗虚方式指针。因此在HotSpot虚拟机中,通过工具类二分模子,将类形貌信息和实例数据举行拆分。使用仅包罗数据不包罗方式的oop(Ordinary Object Pointer)工具形貌Java的工具,使用klass形貌java的类。oop的职责在于示意工具实例数据,没需要维护虚函数指针。通过oop工具头部的一个指针指向对应的klass工具举行关联。

在HotSpot虚拟机中,通俗工具的类通过instanceKlass示意,工具实例则通过instanceOopDesc示意。

在JVM中引用类型可以分为工具,基本类型数组和工具类型数组。可以划分映射到Java中的对应的工具和类型。

类 工具 工具 instanceKlass instanceOopDesc 基本类型数组 typeArrayKlass typeArrayOopDesc 工具类型数组 objArrayKlass objArrayOopDesc

除了常用的3类引用工具外,另有一些其他JVM自己要用的java.lang.ClassLoaderInstanceClassLoaderKlass形貌,java.lang.ClassInstanceMirrorKlass形貌等。

工具

HotSpot VM使用oop形貌工具,oop字面意思是“通俗工具指针”。它是指向一片内存的指针,只是将这片内存‘视作’(强制类型转换)Java工具/数组。工具的本质就是用工具头和字段数据填充这片内存。

工具内存结构

JOL工具

在谈论详细工具结构时,推荐一个JOL工具,可以打印工具的内存结构。通过maven引入。

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

通过ClassLayout.parseInstance(new Object()).toPrintable()即可打印工具的内存结构。

工具头

通俗工具的工具头包罗2部门,第一部门被称为Mark Word,第二部门为类型指针。若是工具为数组,除了通俗工具的两部格外工具头还包罗数组长度。下图是64位虚拟机工具头。

32位虚拟机头部的Mark Word长度为4个字节。

Mark Word

Mark Word保留了工具运行时需要的信息,包罗哈希码(HashCode)、GC分代岁数、偏向状态、锁状态标志、偏向线程ID、偏向时间戳等信息。通过类型指针,可以找到工具对应的类型信息。32位虚拟机和64位虚拟机的Mark Word长度划分为4字节和8字节。

岂论是32位照样64位虚拟机的工具头部都使用了4比特纪录分代岁数,每次GC时工具幸存岁数都市加1,因此工具在survivor区最多幸存15次,跨越15次时,仍然有可达根的工具就会从survivor区被转移到暮年月。可以通过-XX:MaxTenuringThreshold=15参数修改最大幸存岁数。

CMS垃圾接纳器默以为6次。

类型句柄

相比32位工具头巨细,64位工具头更大一些,64位虚拟机工具头的Mark Word类型指针地址都是8字节。而通常情形,我们的程序不需要占用那么大的内存。因此虚拟机通过压缩指针功效,将工具头的类型指针举行压缩。而Mark Word由于运行时需要保留的头部信息会大于4字节,仍然使用8字节。若设置开启了-XX:+UseCompressedOops,虚拟时机将类型指针地址压缩为32位。若设置开启了-XX:+UseCompressedClassPointers,则会压缩klass工具的地址为32位。

需要注重的是,当地址经由压缩后,寻址局限不能阻止的会降低。对于64位CPU,由于现在内存一样平常到不了2^64,因此大多数64位CPU的地址总线现实会小于64位,好比48位。
开启-XX:+UseCompressedOops,默认也会开启-XX:+UseCompressedClassPointers。关闭-XX:+UseCompressedOops,默认也会关闭-XX:+UseCompressedClassPointers
若是开启-XX:+UseCompressedOops,然则关闭-XX:+UseCompressedClassPointers,启动虚拟机的时刻会提醒“Java HotSpot(TM) 64-Bit Server VM warning: UseCompressedClassPointers requires UseCompressedOops”。

通俗工具内存结构(64位虚拟机指针压缩时)

System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());

java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) Mark Word
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 类型指针
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

需要注重,由于内存按小端模式漫衍,因此显示的内容是反着的。上面现实工具头内容为 00000000 00000001 f80001e5

数组工具内存结构(64位虚拟机指针压缩时)

[Ljava.lang.Object; object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) Mark Word
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) f5 22 00 f8 (11110101 00100010 00000000 11111000) (-134208779) 类型指针
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

工具头与锁膨胀

工具头中存储了锁的需要信息,差其余锁的工具头存储内容稍有差异。32位工具头存储花样如下

JVM底层对加锁举行了性能优化,默认虚拟机启动后约莫4秒会开启偏向锁功效。当虚拟机未启用偏向锁时,锁的演化历程为无锁->轻量锁(自旋锁)->重量锁
当虚拟机启用了偏向锁时,锁的演化历程为无锁->偏向锁->轻量锁(自旋锁)->重量锁

本文不讨论JVM对加锁的详细优化逻辑,内容对照多,感兴趣的可以看同砚可以参考《浅谈偏向锁、轻量级锁、重量级锁》。

无锁

当工具未加锁时,锁状态为01,32位虚拟机的工具头部如图所示

public static void main(String[] args) {
System.out.println(ClassLayout.parseInstance(new Object()).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 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total*/

}

需要注重的是其中工具头保留的hashCode被称为identityHashCode,当我们挪用工具的hashCode方式,返回的就是该值。若我们重写了hashCode的值,工具头的hashCode值仍然是内部的identityHashCode,而不是我们重写的hashCode值。可以通过System.identityHashCode打印identityHashCode,或者也可以通过toString直接打印工具输出16进制的identityHashCode

public static void main(String[] args){
Object obj1 = new Object();
System.out.println(ClassLayout.parseInstance(obj1).toPrintable());
System.out.println(obj1.hashCode());
System.out.println(System.identityHashCode(obj1));
System.out.println(ClassLayout.parseInstance(obj1).toPrintable());
System.out.println(obj1);

/*
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 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

1766822961
1766822961
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 31 94 4f (00000001 00110001 10010100 01001111) (1335111937)
4 4 (object header) 69 00 00 00 (01101001 00000000 00000000 00000000) (105)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

java.lang.Object@694f9431 十进制即为1766822961,二进制为01101001 01001111 10010100 00110001
*/

}

偏向锁

偏向锁中的“偏”,就是偏心的“偏”、左袒的“偏”。它的意思是这个锁会偏向于第一个获得它的线程,若是在接下来的执行历程中,该锁一直没有被其他的线程获取,则持有偏向锁的线程将永远不需要再举行同步。

偏向锁的锁状态和未锁状态一样都是01,当工具处于偏向状态时,偏向符号为1;当工具处于未偏向时,偏向符号为0

32位虚拟机的偏向锁工具头部如图所示

偏向时间戳,它现实示意偏向的有用期。

无锁状态升级为偏向锁的条件:

  • 工具可偏向,工具未加锁时,执行CAS更新工具头部线程偏向线程ID为当前线程乐成。

  • 工具可偏向,工具已加锁,但偏向线程ID为空,执行CAS更新工具头部线程偏向线程ID为当前线程乐成。

  • 工具可偏向,工具已加锁,且偏向线程ID即是当前线程ID。

  • 工具可偏向,工具已加锁,且偏向线程ID不为空且不即是当前线程ID,执行CAS更新工具头部线程偏向线程ID为当前线程乐成。

虚拟机启动时,会凭证-XX:BiasedLockingStartupDelay设置延迟启动偏向,在JDK1.8中,默以为4秒。有需要时可以通过-XX:BiasedLockingStartupDelay=0关闭延时偏向。

//-XX:BiasedLockingStartupDelay=0
public static void main(String[] args){
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
/*
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 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
*/

synchronized (o) {
System.out.println(ClassLayout.parseInstance(o).toPrintable());
/*
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 28 9c 02 (00000101 00101000 10011100 00000010) (43788293)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
*/

}
}

轻量级锁

轻量级锁的锁状态为00,32位虚拟机的轻量级锁头部花样如下

升级为轻量级锁条件:

  • 工具不能偏向,跳过偏向锁直接使用轻量级锁。

  • 工具可偏向,但偏向加锁失败(存在线程竞争)。

  • 工具获取挪用hashCode后加锁。

  • 工具已升级为重量级锁后,锁降级只能降级为轻量级锁,无法降级为偏向锁。

轻量级锁会在线程的栈帧中开拓一个锁纪录区域,将当前工具的头部保留在锁纪录区域中,将锁纪录区域的地址保留到当前工具头部。

  • 工具不能偏向直接升级到轻量锁

public static void main(String[] args){
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
/*
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 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
*/

synchronized (o) {
System.out.println(ClassLayout.parseInstance(o).toPrintable());
/*
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 58 f4 dd 02 (01011000 11110100 11011101 00000010) (48100440)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
*/

}
  • 偏向锁竞争升级为轻量锁

//-XX:BiasedLockingStartupDelay=0
public static void main(String[] args){
final Object o = new Object();
Thread thread= new Thread(){
@Override
public void run() {
synchronized (o){
System.out.println(ClassLayout.parseInstance(o).toPrintable()); //偏向锁
/*
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 e8 31 28 (00000101 11101000 00110001 00101000) (674359301)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
*/

}
}
};
thread.start();
thread.join();
synchronized (o){
System.out.println(ClassLayout.parseInstance(o).toPrintable()); //轻量锁
/*
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 60 f4 b4 02 (01100000 11110100 10110100 00000010) (45413472)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
*/

}
}
  • 偏向后挪用hashCode方式升级为轻量级锁

//-XX:BiasedLockingStartupDelay=0
public static void main(String[] args){
final Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable()); //偏向锁
/*
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 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
*/

o.hashCode();
synchronized (o){
System.out.println(ClassLayout.parseInstance(o).toPrintable());
/*
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 18 f3 4a 02 (00011000 11110011 01001010 00000010) (38466328)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
*/

}
}

重量级锁

轻量级锁的锁状态为10,32位重量级锁头部如图所示

轻量级锁自循环一定次数后一致获取不到锁,则升级为重量级锁条件。自旋次数默以为10次,可以通过-XX:PreBlockSpin设置修改次数。

//-XX:BiasedLockingStartupDelay=0
public static void main(String[] args) throws InterruptedException {
final Object o = new Object();
Thread thread= new Thread(){
@Override
public void run() {
synchronized (o){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ClassLayout.parseInstance(o).toPrintable());
/*
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 2a 37 c6 25 (00101010 00110111 11000110 00100101) (633747242)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
*/

}
}
};
thread.start();
synchronized (o){
Thread.sleep(1000);
System.out.println(ClassLayout.parseInstance(o).toPrintable());
/*
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 2a 37 c6 25 (00101010 00110111 11000110 00100101) (633747242)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
*/

}
thread.join();
}

重量级锁降级

当重量级锁解锁后就会举行锁降级,锁降级只能降级为轻量锁,无法再使用偏向锁。

//-XX:BiasedLockingStartupDelay=0
public static void main(String[] args) throws InterruptedException {
final Object o = new Object();
Thread thread= new Thread(){
@Override
public void run() {
synchronized (o){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ClassLayout.parseInstance(o).toPrintable());
/*
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 2a 37 c6 25 (00101010 00110111 11000110 00100101) (633747242)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
*/

}
}
};
thread.start();
synchronized (o){
Thread.sleep(1000);
System.out.println(ClassLayout.parseInstance(o).toPrintable());
/*
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 2a 37 c6 25 (00101010 00110111 11000110 00100101) (633747242)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
*/

}
thread.join();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
/*
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 f8 (11100101 00000001 00000000 11111000) (-134217243)
*/

synchronized (o){
System.out.println(ClassLayout.parseInstance(o).toPrintable());
/*
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 98 f6 8c 02 (10011000 11110110 10001100 00000010) (42792600)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
*/

}
}

实例数据

工具实例数据默认根据long、double、int、short、char、byte、boolean、reference顺序结构,相同字段宽度总是分配在一起。若有父工具,则父工具的实例字段在子工具前面。
另外若是HotSpot虚拟机的 +XX:CompactFields参数值为true(默认就为true),那子类之中较窄的变量也允许插入父类变量的清闲之中,以节约出一点点空间。

填充

JVM中,工具巨细默以为8的整数倍,若工具巨细不能被8整除,则会填充空字节来填充工具保证。

工具生命周期

在领会完工具头部后,我们看下工具的确立的时刻发生了什么事情。当我们挪用new Object()确立一个工具时,天生的字节码如下

0 new #2 <java/lang/Object>
3 dup
4 invokespecial #1 <java/lang/Object.<init>>

首先通过new指令分配工具,并将工具地址入栈,通过dup指令复制一份栈顶元素。通过invokespecial指令挪用工具的init举行初始化会消耗栈顶2个槽。由于init方式需要传入一个参数,该参数即为引用工具自己。在init初始化时会将this指针举行赋值。这样我们在代码中就可以通过this指向当前工具。

工具确立流程如下图所示。

  • 栈上分配
    通常工具都是在堆上确立的,若工具仅在当前作用域下使用,那么使用完很快就会被GC接纳。JVM通过逃逸剖析对工具作用域举行剖析,若是工具仅在当前作用域下使用,则将工具的实例数据分配在栈上,从而提升工具确立速率的同时削减GC接纳的工具数目。

  • 线程局部缓冲区(TLAB)
    若是无法在栈上分配,则工具会在堆上分配。对于JDK1.8来说,Java堆通常使用分代模子,(关于GC,垃圾接纳算法等这里不做详细讨论)。经由统计,90%的工具在使用完成后都市被接纳,因此默认新生代会分配10%的空间给幸存者区。

    工具先在eden区举行分配,然则我们知道,堆是所有线程共享的区域,会存在多线程并发问题。因此在堆上分配就需要举行线程同步。为了提高分配效率,JVM会为每个线程从eden区初始化一块堆内存,该内存是线程私有的。这样每次分配工具时就无需举行同步操作,从而提高工具分配效率。线程的这块局部内存区域被称为线程局部缓冲区(TLAB)。通常这块内存会小于eden区的1%。当这块内存用完时,就会重新通过CAS的方式为线程重新分配一块TLAB。
    通常工具分配有两种方式,一种是线性分配,当内存是规整时(大部门垃圾接纳器新生代都是用符号整理算法,可以保证内存规整),通过一个指针向后移动工具巨细,直接分配一块内存给工具,指针左边是已使用的内存,指针右边是未使用的内存,这种方式被称为指针碰撞。TLAB配合指针碰撞手艺能够在线程平安的情形下移动一次指针直接就可以完成工具的内存分配。

    当内存不规整时(好比CMS垃圾接纳器通常情形并不会每次GC后都压缩内存,会存在内存碎片),则需要一块分外的内存纪录哪些内存是空闲的,这个缓存被称为空闲列表

  • eden区分配
    若是TLAB无法分配工具,那么工具只能在Eden区直接分配,前面说过,在堆上分配,必须接纳同步战略阻止有发生线程平安问题。若是分配内存时,工具的klass没有剖析过,则需要先举行类加载历程,然后才气分配工具。这个历程被称为慢速分配,而若是klass已剖析过则直接可以分配工具,这个历程被称为快速分配

  • 暮年月分配
    当eden区放不下工具时(固然另有其他的判断战略,这里暂时不去体贴),工具直接分配到暮年月。

  • 工具实例初始化
    当工具完成内存分配时,就会初始化工具,将内存清零。需要注重,工具的静态变量在类初始化的初始化阶段已经完成设置。

  • 初始化工具头部
    当工具实例初始化完,就会设置工具头部,默认的工具头部存放在klass,若是启用了偏向,则设置的就是可偏向的工具头。

工具接见方式

现在我们领会了工具的内存结构和工具的确立逻辑,那么工具在运行时,若何通过栈的局部变量找到现实的工具呢?常用的工具接见方式有2种,直接指针接见句柄接见

直接指针接见

工具确立时,局部变量表只保留工具的地址,地址指向的是堆中的现实工具的markword地址,JVM中接纳的就是这种方式接见工具。

句柄接见

通过句柄接见时势部变量保留的时句柄池的工具句柄,句柄池中,则会存储工具实例指针和工具类型指针。再通过这两个指针划分指向工具实例池中的工具和元数据的klass。

相比直接指针接见,这种接见方式由于需要2次接见,而直接指针只需要一次接见,因此句柄接见工具的速率相对较慢。然则对于垃圾接纳器来说是对照友好的,由于工具移动无需更新栈中的局部变量表的内容,只需要更新句柄池中的工具实例指针的值。

HSDB

前面我们通过JOL工具可以很利便的输出工具的结构。JDK也提供了一些工具可以查看更详细的运行时数据。
HSDB(Hotspot Debugger) 是 JDK1.8 自带的工具,使用该工具可以毗邻到运行时的java历程,查看到JVM运行时的状态。

以该偏向锁代码为例

//-XX:BiasedLockingStartupDelay=0
public class BiasedLock {
public static void main(String[] args) {
Object o = new Object();
synchronized (o) {
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
}
/*
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 28 64 03 (00000101 00101000 01100100 00000011) (56895493)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*/

为了能看到运行时状态,我们可以使用idea工具单笔调试,也可以使用jdb工具举行调试。jdb是Java的调试器,位于%JAVA_HOME%/bin下面。通过jdb -classpath XXX class名 执行main方式。
执行后,我们可以将打断点,然后举行调试。

  • 通过stop in <class id>.<method>[(argument_type,...)]在方式中打断点,或者可以通过stop at <class id>:<line>在指定行打断点。

  • 通过stop in com.company.BiasedLock.main将断点打在main方式。

  • 通过run运行

  • 通过next举行调试。(可以使用step举行单步骤试)

C:\Users\Dm_ca>jdb -classpath "D:\study\java\symbolreference\target\classes;D:\develop\mavenrepository\org\openjdk\jol\jol-core\0.9\jol-core-0.9.jar" com.company.lock.BiasedLock
正在初始化jdb...
> stop in com.company.lock.BiasedLock.main
正在延迟断点com.company.lock.BiasedLock.main。
将在加载类后设置。
> run
运行com.company.lock.BiasedLock
设置未捕捉的java.lang.Throwable
设置延迟的未捕捉的java.lang.Throwable
>
VM 已启动: 设置延迟的断点com.company.lock.BiasedLock.main

断点掷中: "线程=main", com.company.lock.BiasedLock.main(), 行=8 bci=0

main[1] next
>
已完成的步骤: "线程=main", com.company.lock.BiasedLock.main(), 行=9 bci=8
...
未挂起任何工具。
> java.lang.Object o
bject internals:
OFFSET已完成的步骤: SIZE TYPE DESCRIPTION VALUE
"线程=main", com.company.lock.BiasedLock.main(), 行=11 bci=25
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 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

此时我们可以通过HSDB毗邻到历程。通过JPS 下令查看历程的pid

C:\Users\Dm_ca>jps
17072
22724 HSDB
23268 TTY
24548 Launcher
3828 BiasedLock

通过java -cp “.;%JAVA_HOME%/lib/sa-jdi.jar” sun.jvm.hotspot.HSDB 3828 启动HSDB(这种方式会壅闭我们的程序,不要直接在生产环境这样操作)
第一次启动可能会报错误无法找到sawindbg.dll,这时需要将%JAVA_HOME%/lib目录下面的sawindbg.dll文件拷贝到jre的/lib目录下即可。

启动后,在界面选中main线程,点击工具栏第二个图片打开线程栈。

HSDB工具在线程栈中已经标出我们的工具。在菜单找到内存查看器


输入栈局部变量表中的工具的地址,就可以显示出工具的内存,和JOL工具打印的工具头部是一样的。

参考文档

  1. HSDB – HotSpot debugger

  2. JOL:剖析Java工具的内存结构

  3. [Java JVM] Hotspot GC研究- 开篇&工具内存结构

  4. 看了这篇文章,我搞懂了StringTable

  5. 盘一盘 synchronized (一)—— 从打印Java工具头提及

  6. 浅谈偏向锁、轻量级锁、重量级锁

  7. 源码剖析-线程A请求偏向于线程B的偏向锁

  8. C++为什么要弄出虚表这个器械?

  9. 《深入明白Java虚拟机》

  10. 《Java虚拟机规范(Java SE 8版)》



长按二维码

关注我们






来都来了,点个在看再走吧~~~








手机摄影如何拍出高大上的感觉? – 木西AlexanDENG 的回答 – 知乎 : 2016.6.6. 更新:今天被推到发现啦,感谢知乎的编辑团队。也欢迎大家来值乎向我提问https://www.zhihu.com/zhi/people/723514935031652352 (二维码自动识别)距离这个问题首次回答已经4个多月了。这四个月答主我又积累了一些手机摄影的片子,在这里分享给大家。请不要怀疑,真的全部使用手机…

,手机摄影如何拍出高大上的感觉? – 木西AlexanDENG 的回答 – 知乎 : 2016.6.6. 更新:今天被推到发现啦,感谢知乎的编辑团队。也欢迎大家来值乎向我提问https://www.zhihu.com/zhi/people/723514935031652352 (二维码自动识别)距离这个问题首次回答已经4个多月了。这四个月答主我又积累了一些手机摄影的片子,在这里分享给大家。请不要怀疑,真的全部使用手机…,手机摄影如何拍出高大上的感觉? – 木西AlexanDENG 的回答 – 知乎 : 2016.6.6

相关文章