运行时数据区
Java虚拟机在程序运行时将他管理的内存划分为若干个不同的数据区。

程序计数器
程序计数器在内存中是一块较小的区域,存储的时程序下一条将要执行的指令的地址。
程序计数器为什么是线程私有的?在每一个程序中,都有可能存在多线程的情况。我们知道,在一个确定的时刻,每个处理器(对于多核处理器来说就是一个内核)都只会执行一条线程中的指令,当线程切换之后再次切换回来,怎么知道下一条将要执行的指令是什么呢?答案就是程序计数器。由于存在多线程,故而需要不同的计数器,因此程序计数器是线程私有的。
如果线程执行的是Java方法,计数器记录的就是下一条指令的地址; 如果线程执行的native方法,计数器为空 此内存区域是唯一一个JVM规范中没有规定任何OutOfMemoryError情况的区域
Java虚拟机栈
线程私有,生命周期与线程一致。
每个方法执行过程中都会产生一个栈帧,,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从开始到执行完毕,等同于一个栈帧在虚拟机栈中入栈到出栈的过程。
如果线程请求的栈深度大于虚拟机所运行的深度,抛出StackOverFlowError异常; 动态扩展虚拟机栈时,如果请求不到足够的内存,抛出OutOfMemoryError异常;
本地方法栈
与Java虚拟机栈一样,只不过服务对方时native方法。
堆
Java 堆时Java虚拟机中所管理的内存最大的一块区域。是线程共享的。
堆内存唯一的目的就是存储对象实例,几乎所有的内存对象实例以及数组都要在堆上分配。因此,Java堆也是垃圾回收机制管理的主要区域,很多所谓的JVM调优就是针对该区域进行的。
堆内存也会可扩展的,使用-Xmx 和 -Xms 来控制。当堆内存无法再扩展时,将抛出OutOfMemoryError异常。
方法区
线程共享区域,主要存放类信息、常量、静态变量、即时编译器编译之后的代码等。JVM规范就方法区描述为Java堆的一个逻辑部分,但它却有一个一个别名,称之为【非堆】(Non-Heap),目的就是将之与Java堆区分开来。
当方法区无法满足内存分配时,将抛出OutOfMemoryError异常。
运行时常量池
方法区的一部分。主要用于存储常量池信息。
运行时常量池相当于Class文件常量池的一个重要特征就是具备动态性,也就是在运行期间也可以将新的常量放置到常量池中。例如String的 intern() 方法。
直接内存
直接内存并不是JVM运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。
JDk1.4 z中引入了NIO,是一种基于通道(Channel)与缓冲区(Buffer)的I/O方法,它可以使用Native函数直接分配堆以外的内存,然后通过一个存储在Java堆中的DirectionByteBuffer对象作为这块内存的引用进行操作。
直接内存不会受到Java堆大小的限制,但肯定会受到本地总内存与处理器寻址空间的限制。当扩展时导致各个内存区域空间大于总的物理内存时,就会抛出OutOfMemoryError异常。
创建Hotspot虚拟机对象
当Java虚拟机遇到一条 new 指令时,先要检查待实例化的类是否已经加载,保证先要完成类加载过程。随后会为对象分配内存空间,每一个对象所需的大小在类加载结束之后便已经确定。
分配内存的方式:
- 指针碰撞
Java堆内存是一块连续的内容区域,已使用的内存放在一边,未使用内存区域在另一边,两者中间使用一个指针进行标示。当需要为对象分配内存时,将指针往未使用区域移动一段与对象所需空间大小的区域即可。
- 空闲列表
Java堆内存不是规整的,已使用的空间与未使用空间交错在一起,虚拟机维护了一张表,记录了哪些块是可用的,哪些是不可用的,在分配时找到一块足够到的空间划分给对象,并更新列表。
内存分配完成后,虚拟机会将分配到的内存都初始化为零值,保证对象的实例字段在Java代码中可以不赋初值就直接使用,程序能访问到这些字段的数据类型所对应的零值。 接下来虚拟机会对对象进行一些必要的设置,比如对象所属的类、对象的哈希码,对象的GC年代(垃圾回收)、是否启用偏向锁等,这部分下面会详说。 最后就是调用
方法,给字段赋值。这就是初始化阶段做的事情。
对象的内存布局
vtable
**vtable**,即虚函数表。记录了本类中所有虚函数的函数指针,实际上就是一个函数指针的数组,指向该数组的起始位置。数组中的每一个项都指向一个函数。每一个类只有唯一的一个vtable,不是每个对象都有一个vtable,同一个类的每个不同对象都有一个指针,这个指针指向该类的vtable。
**
对象结构声明
在Hotspot虚拟机中,对象的内存布局可以分为3个部分:
- 对象头
- 实例数据
- 对其填充
oopDesc
Java对象在JVM中是通过oop.hpp描述的。``
// oopDesc is the top baseclass for objects classes. The {name}Desc classes describe// the format of Java objects so the fields can be accessed from C++.// oopDesc是对象类的顶级基类。其实现类 {name}Desc(例如:普通实例对象为instanceOopDesc)描述java对象的格式,// 因此C++可以访问对象字段// oopDesc is abstract.// (see oopHierarchy for complete oop class hierarchy)//// no virtual functions allowedclass oopDesc {private:// 对象头volatile markOop _mark;// 元数据union _metadata {Klass* _klass;narrowKlass _compressed_klass;} _metadata;...}
arrayOopDesc
由注释可知,JVM使用不同的结构体描述不同的对象。如下:
- arrayOopDesc用来描述数组对象,见 arrayOop.hpp ```c // arrayOopDesc is the abstract baseclass for all arrays. It doesn’t // declare pure virtual to enforce this because that would allocate a vtbl // in each instance, which we don’t want.
// arrayOopDesc是数组的顶级抽象类。没有将该结构体强制声明为纯虚的是因为想让每个实例都能够分配一个vtable
// The layout of array Oops is: // // markOop // Klass* // 32 bits if compressed but declared 64 in LP64. // leng
// 数组对象的内存布局:
// 64位计算机:
// markOop: 64
// Klass*: 64/32(开启指针压缩)
// length: 数组长度
// 在C++中没有声明长度字段(_length)。如果未开启指针压缩,它将在arrayOopDesc中声明的非静态字段 // 之后分配,否则它将占据oopDesc中klass字段的后半部分(也就是说占据后16bits)。
class arrayOopDesc : public oopDesc {
// 对象头的大小等于元素的markOop部分加上length。// 返回值等于header_size_in_bytes的对齐static int header_size_in_bytes() {size_t hs = align_size_up(length_offset_in_bytes() + sizeof(int),HeapWordSize);
}
<a name="19bjH"></a>### instanceOopDescinstanceOopDesc用来描述普通实例对象,见 [instanceOop.hpp](http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/87ee5ee27509/src/share/vm/oops/instanceOop.hpp)```cclass instanceOopDesc : public oopDesc {public:// 对象头对齐static int header_size() { return sizeof(instanceOopDesc)/HeapWordSize; }}
Klass
对象由对象头与实例数据组成。实例数据由**union**类型的**_metadata**声明。union表示一种共用空间的结构。未开启指针压缩时使用结构体**Klass**表示,在 klass.hpp 中声明。开启指针压缩是使用narrowKlass类型,在oopsHierarchy.hpp中声明,而**juint**是一个无符号int类型,在globalDefinitions.hpp中声明。
// http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/87ee5ee27509/src/share/vm/oops/oopsHierarchy.hpp// If compressed klass pointers then use narrowKlass.typedef juint narrowKlass;
// http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/87ee5ee27509/src/share/vm/utilities/globalDefinitions.hpptypedef juint u4;
Klass提供:
- 语言级别的类对象
- 为对象提供
VM分派行为
这两个功能被组合在一个C++类中。
将对象实现分成oop/klass两部分是因为不想每一个对象都有一个C++ vtable指针。因此,普通的oop没有任何的虚函数。他们将所有的虚函数转发给了他们的klass,klass具有vtable并根据实际类型进行C++调用。(Java的多态)
klass的布局如下:
[C++ vtbl ptr ] (contained in Metadata)[layout_helper ][super_check_offset ] for fast subtype checks[name ][secondary_super_cache] for fast subtype checks[secondary_supers ] array of 2ndary supertypes[primary_supers 0][primary_supers 1][primary_supers 2]// ...[primary_supers 7][java_mirror ][super ][subklass ] first subclass[next_sibling ] link to chain additional subklasses[next_link ][class_loader_data][modifier_flags][access_flags ][last_biased_lock_bulk_revocation_time] (64 bits)[prototype_header][biased_lock_revocation_count][_modified_oops][_accumulated_modified_oops][trace_id]
对象头
对象头包含了 Mark Word 和 Klass Pointer 两部分。
oop An object pointer. Specifically, a pointer into the GC-managed heap. (The term is traditional. One ‘o’ may stand for ‘ordinary’.) Implemented as a native machine address, not a handle. Oops may be directly manipulated by compiled or interpreted Java code, because the GC knows about the liveness and location of oops within such code. (See GC map.) Oops can also be directly manipulated by short spans of C/C++ code, but must be kept by such code within handles across every safepoint. 一个对象指针。特指指向GC堆的指针。使用一个本机地址实现。Oops可以由编译或解释的Java代码直接操作,因为GC知道Oops在这些代码中的生存状态和位置(见GC Map)。Oops 也可以被C/C++的代码操作,但是必须保证每一个安全点内都有这样一份代码。
object header Common structure at the beginning of every GC-managed heap object. (Every oop points to an object header.) Includes fundamental information about the heap object’s layout, type, GC state, synchronization state, and identity hash code. Consists of two words. In arrays it is immediately followed by a length field. Note that both Java objects and VM-internal objects have a common object header format. 每一个GC堆对象的共同结构的开始部分。(每个oop都指向一个对象头)。包含了关于对象头布局的基本信息,类型,GC状态,同步状态,以及Hash标识。由两个词**组成,在数组中,紧接着是一个长度字段。 注意:Java对象与VM对象都有一个通用的对象头格式。
mark word The first word of every object header. Usually a set of bitfields including synchronization state and identity hash code. May also be a pointer (with characteristic low bit encoding) to synchronization related information. During GC, may contain GC state bits. 每一个对象头的第一个词。通常是一个位字段集合,包括同步状态和hash标识。也可以是一个指向同步相关的指针(小端编码)。在GC期间,可以包含GC状态
klass pointer The second word of every object header. Points to another object (a metaobject) which describes the layout and behavior of the original object. For Java objects, the “klass” contains a C++ style “vtable”.
每一个对象头的第二个词。指向另一个描述了原始对象的布局和行为的对象(元对象)。对于
Java对象来说,这个klass包含了一个C++样式的vtable
数组长度(可选)只有数组对象才有这个字段,标识数组的长度。
关于对象头的信息参考官方文档
Mark Word
关于Mark Word 的描述见 markOop
markOop 描述的是对象头。 注意:markOop 不是一个真实的 Oop,仅仅是一个词。
对象头的位格式(大端格式)如下: 32bit:
| 普通对象 | |||
|---|---|---|---|
| hash | age | biased_lock | lock |
| 25 | 4 | 1 | 2 |
| 偏向对象 | ||||
|---|---|---|---|---|
| JavaThread* | epoch | age | biased_lock | lock |
| 23 | 2 | 4 | 1 | 2 |
| CMS free block | ||||
|---|---|---|---|---|
| size | ||||
| 32 |
| CMS promoted object | |
|---|---|
| PromotedObject* | promo_bits |
| 29 | 3 |
64bit:
| 普通对象 | |||||
|---|---|---|---|---|---|
| unused | hash | unused | age | biased_lock | lock |
| 25 | 31 | 1 | 4 | 1 | 2 |
| 偏向对象 | |||||
|---|---|---|---|---|---|
| JavaThread* | eoch | unused | age | biased_lock | lock |
| 54 | 2 | 1 | 4 | 1 | 2 |
| CMS free block | ||||
|---|---|---|---|---|
| size | ||||
| 64 |
| CMS promoted object | |
|---|---|
| PromotedObject* | promo_bits |
| 61 | 3 |
COOPs && 普通对象 unused hash cms_free age biased_lock lock 25 31 1 4 1 2
COOPs && 偏向对象 JavaThread* eoch cms_free age biased_lock lock 54 2 1 4 1 2
COOPs && CMS protomted object narrowOpp unused cms_free unused oromo_bits 32 24 1 4 3
COOPs && CMS free block unused isze cms_free unused 21 35 1 7
- hash 包含了身份标识hash值:最大值为31bit,见 OS::random(),同样,64bit的虚拟机也不能获取到的一个更大的hash值。
- biased_lock 模式用于将锁偏向于给定线程,当这个模式设置为低3位时,锁要么偏向于给定线程,要么表示可能有偏向。当锁偏向于给定线程时,该线程可以执行解锁与锁定而无需原子操作。当锁的偏移被撤销时,它将恢复到下面描述的的锁定方案。
- age:对象的分代年龄
- lock:锁状态标识
- epoch:偏向时间戳
- JavaThread* :保存持有偏向锁的线程ID
[JavaThread* | epoch | age | 1 | 01] 偏向于给定线程的锁 [0 | epoch | age | 1 | 01] 匿名偏向锁
两个锁 bits 被是用来描述3个状态:locked/unlocked/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
不同的锁状态,存储着不同的数据:
| 锁状态 | 存储内容 | 偏向锁标识 | 锁标志位 |
|---|---|---|---|
| 无锁 | 哈希吗、分代年龄 | 0 | 01 |
| 偏向锁 | 线程ID、时间戳、分代年龄 | 1 | 01 |
| 轻量级锁 | 指向栈中锁记录的指针 | 无 | 00 |
| 重量级锁 | 指向膨胀锁(monitor)的指针 | 无 | 10 |
| GC标记 | 无 | 无 | 11 |
怎样证明对象的内存布局就是由这3部分组成的呢?但是使用 java object layout。
JOL是分析Java对象的布局方案的工具箱。这些工具大量使用了Unsafe、JVMTI 和 Serviceablility Agent来解码实际的对象布局、占用空间和引用。
Java Object Layout
使用JOL依赖
<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>put-the-version-here</version></dependency>
JOL 的版本发布见 这里
获取当前虚拟机信息
out.println(VM.current().details());
Running 64-bit HotSpot VM.
Using compressed oop with 0-bit shift.
Using compressed klass with 3-bit shift.
Objects are 8 bytes aligned. // 8 字节对齐
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]
其中,Field Size 分别表示 oopSize,booleanSize,byteSize,charSize,shortSize、intSize、floatSize、longSize、doubleSize 的大小 Array element size 分别表示 Object[]、boolean[]、byte[]、char[]、short[]、int[]、float[]、long[]、double[] 数组中每个元素的大小
底层是使用 Unsafe 类来完成的,关于该类的更多信息见 Java魔法类:Unsafe应用解析
获取Class的内存布局
public static class A {boolean f;}out.println(ClassLayout.parseClass(A.class).toPrintable());
com.yangll.jvm.jol.sample.JOLSample_01_Basic$A object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 1 boolean A.f N/A 13 3 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
- 对象头占
12个字节 - 实例数据
f占1个字节 - 由于是
8字节对齐的,故而需要3个字节的填充数据
下面将这个例子稍作改变,将f改变成为long类型:
public static class A {long f;}
com.yangll.jvm.jol.sample.JOLSample_02_Alignment$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
由于底层硬件平台通常需要对齐访问来保持性能和正确性,因此需要根据字段的大小对字段进行对齐。
由于是**8**字节对齐,对于**boolean**类型来说,其只占据**1**个字节,故而可以在对象头之后依次进行填充,但对**long**来说就不同了,**long**本身占据**8**个字节,这就已经是对齐的,故而不可再分,因此。在这个例子中,我们可以看到:由于**long**类型已经对齐了**8**个字节,而对象头之后仅需**4**个字节就可以对齐,故而在对象头之后填充**4**个字节。
VM是怎样对字段进行打包的呢?请看示例:
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;}
com.yangll.jvm.jol.sample.JOLSample_03_Packing$A 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 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
JVM打包字段以最小化内存占用量为标准。它是通过按 8 -> 4 -> 2 -> 1 的顺序来实现字段对齐的,因为一旦我们对齐了 8字节字段,它将无法中断初始对齐。导致8字节对初始位置与上一份数据之间存在由一个或多个较小间隙。
优先最小化内存占用量 其次根据
8 -> 4 -> 2 -> 1的顺序来实现字段对齐
例如:以上代码先根据最小化内存占用量填充了float字段,补齐16字节(8字节整数倍),然后再按照 8 -> 4 -> 2 -> 1 的顺序来实现字段对齐。
注意:实际字段顺序与Java代码中声明的顺序很可能是不同的。
**
在继承关系中是怎样进行对齐的呢?
public static class A {long a;}public static class B extends A {long b;}public static class C extends B {long c;int d;}
com.yangll.jvm.jol.sample.JOLSample_05_InheritanceBarrier$C 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 8 long C.c N/A 40 4 int C.d N/A 44 4 (loss due to the next object alignment) Instance size: 48 bytes Space losses: 4 bytes internal + 4 bytes external = 8 bytes total
父类字段优先级布局,这是继承关系中的重要布局原则。就下来才是子类中的字段。另外,当父类中字段存在对齐间隙,并不能使用子类的字段去填充这个间隙,子类与父类之间的字段块存在继承屏障。
另外一个类字段大小不足类引用大小时,会在类的结尾产生填充。例如,A、B、C都只有一个boolean字段,对象内存布局并不是A.a占一个字节,紧接着时B.b占一个字节,而是将A.a、B.b、C.c分别舍入到A、B、C引用大小,即4个字节,然后布局。如下:
public static class A {boolean a;}public static class B extends A {boolean b;}public static class C extends B {boolean c;}
com.yangll.jvm.jol.sample.JOLSample_06_Gaps$C object internals: OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A12 1 boolean A.a N/A13 3 (alignment/padding gap)16 1 boolean B.b N/A17 3 (alignment/padding gap)20 1 boolean C.c N/A21 3 (loss due to the next object alignment)Instance size: 24 bytes
Space losses: 6 bytes internal + 3 bytes external = 9 bytes total
获取普通Object的内存布局
以上都是基于类的布局结构的,如果是一个对象,是怎样的呢?会存在Mark Word和Klass Pointer吗?验证一番:
public static class A {// no fields}// 打印 A 的内存布局out.println(ClassLayout.parseInstance(new A()).toPrintable());
com.yangll.jvm.jol.sample.JOLSample_11_ClassWord$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) ae 11 01 20 (10101110 00010001 00000001 00100000) (536940974)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)
也就是说,mark word占据8个字节的空间,其中,由于是无锁状态,故锁标识为 01(00000001),偏向锁标识为0.根据打印的日志来看,整个对象头的二进制表示为这样的:00000001 00000000 00000000 0000000000000000 00000000 00000000 00000000。为什么会这样呢?看起来00000000 00000000 00000000 000000000 0000000 00000000 00000000 00000001才更符合预期结果。还有,31位的hash值呢?怎么没看到呢?分代年龄也是0吗?针对这些问题,好好再来分析一下:
- 结果看起来和预期不一致?
这是因为JVM采用的是小端编码(无论是大端还是小端,都是对于字节而言的概念)的存储格式,即低字节存低地址,高字节存高地址。例如:0x12345678:
- 大端编码:0x12345678,这种表示是符合开发者的逻辑思维的;
- 小端格式:0x78563412,因为计算机内部使用的是小端字节序,故而小端编码比较利于计算机对数据的处理。
因此,mark word打印出来的字节流是这样的:
16进制表示:01 00 00 00 00 00 00 00
2进制表示:00000001 00000000 00000000 0000000000000000 00000000 00000000 00000000
但是对象布局字节流大端格式表示应该是这样的:
16进制表示:00 00 00 00 00 00 00 01
2进制表示:00000000 00000000 0000000000000000 00000000 00000000 00000000 00000001
因此,最后两位(锁标志)为01,倒数第3位(偏向锁标志)为0,这正是我们预期的结果。
**hash**值为什么是0呢?
其实Hash值不是真实存在的,是通过计算得到的。也就是说,需要手动调用hashCode()方法,才会让对象产生hash标志。
验证:将上述程序改动,是A对象主动调用hashCode()方法
A a = new A();out.println(a.hashCode());out.println(ClassLayout.parseInstance(a).toPrintable());
355629945
com.yangll.jvm.jol.sample.JOLSample_11_ClassWord$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 79 7b 32 (00000001 01111001 01111011 00110010) (846952705)4 4 (object header) 15 00 00 00 (00010101 00000000 00000000 00000000) (21)8 4 (object header) ae 11 01 20 (10101110 00010001 00000001 00100000) (536940974)12 4 (loss due to the next object alignment)Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
根据打印日志可知,hash值为355629945,化为16进制等于 15 32 7b 79,将整个mark word按照大端格式排列可得00 00 00 15 32 7b 79 01。再还原回mark word的布局结构,如下:
| unused(25) | hash(31) | unused(1) | age(4) | biased_lock(1) | lock(2) |
|---|---|---|---|---|---|
| 00000000 00000000 00000000 0 | 0010101 00110010 01111011 01111001 | 0 | 0000 | 0 | 01 |
这正是我们得到的关于一个普通对象的整个mark word的内存布局结构。
- 对象的分代年龄为0
整个堆内存是分为新生代、老年代和永久代(Java某个版本,好像是Java8,已经把永久代去掉了)。其中,新生代又分为Eden区(8/10)和两个Survivor区,分别为from(1/10)和to(1/10),两者是为垃圾回收时淘汰不可达对象建立的。具体内容参考JVM系列-垃圾回收。对象刚new出来的时候是存放在Eden区域的,此时对象年龄为0,经历第一次GC之后,会将存活的对象移动到from区,GC年龄增1,再次经历GC,将存活对象从from移到to区域,GC年龄再次增1,再次经历GC,会将to区域存活的对象从移动到from区,GC年龄再增1,如此 循环,当GC年龄到达一定值之后,就会将该对象移动到老年代,这时候该对象已经不会轻易被销毁了。尤其是移动到永久代的对象,基本就是不会被回收了。
接下来的ae 11 01 20便指向了实例数据的地址。
获取synchronized Object的内存布局
A a = new A();synchronized (a) {out.println(ClassLayout.parseInstance(a).toPrintable());}
com.yangll.jvm.jol.sample.JOLSample_11_ClassWord$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 38 f1 42 03 (00111000 11110001 01000010 00000011) (54718776)4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)8 4 (object header) ae 11 01 20 (10101110 00010001 00000001 00100000) (536940974)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 00000000 00000000 00000000 00000011 01000010 ``11110001 ``00**00**。锁状态为为 00,表示这是一个轻量级锁,锁的是栈中的对象。偏向锁位为0,表示这不是一个偏向锁。
获取biased Object的内存布局
TimeUnit.SECONDS.sleep(6);final A a = new A();ClassLayout layout = ClassLayout.parseInstance(a);out.println("**** Fresh object");out.println(layout.toPrintable());out.println(Thread.currentThread().getId());synchronized (a) {out.println("**** With the lock");out.println(layout.toPrintable());}out.println("**** After the lock");out.println(layout.toPrintable());
** Fresh object**
com.yangll.jvm.jol.sample.JOLSample_13_BiasedLocking$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) ae 11 01 20 (10101110 00010001 00000001 00100000) (536940974)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**
com.yangll.jvm.jol.sample.JOLSample_13_BiasedLocking$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 e0 2b 01 (00000101 11100000 00101011 00000001) (19652613)4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)8 4 (object header) ae 11 01 20 (10101110 00010001 00000001 00100000) (536940974)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**
com.yangll.jvm.jol.sample.JOLSample_13_BiasedLocking$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 e0 2b 01 (00000101 11100000 00101011 00000001) (19652613)4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)8 4 (object header) ae 11 01 20 (10101110 00010001 00000001 00100000) (536940974)12 4 (loss due to the next object alignment)Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
将当前线程睡眠超过5秒,使其升级为偏向锁,可以看到偏向锁位为1,锁位01,表示这是一个偏向锁 。
注意:释放锁之后标记字段仍然没有改变,依旧是101,这是因为标记字段现在包含对该对象偏向的线程的引用。
