摘自周志明老师JVM第3版
对象内部结构分为:对象头、实例数据、对齐填充(保证8个字节的倍数)。
对象头分为对象标记(markword,类元信息/类型指针(Class Pointer ) -java层面的
对象标记(markOop)和类元信息(klassOop),类元信息存储的是指向该对象类元数据(klass)的首地址。 --c++层面的
对齐填充就是保证8个字节的倍数
对象头对象头分为Mark Word 对象标记和Class Pointer 类型指针
Java对象头里的Mark Word里默认存储对象的HashCode、分代年龄和锁标记位 等信息
默认存储对象的HashCode、分代年龄和锁标志位等信息。
每个对象都有一个hashcode
GC垃圾回收的次数,java8中对象超过15次yonggc,进入老年代,所以占4个字节
GC年龄采用4位bit存储,最大为15, 例如MaxTenuringThreshold参数默认值就是15
如果修改为16试一试
-XX:MaxTenuringThreshold=16
这些信息都是与对象自身定义无关的数据,所以MarkWord被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。 它会根据对象的状态复用自己的存储空间,也就是说在运行期间MarkWord里存储的数据会随着锁标志位的变化而变化。
类元信息(又叫类型指针)Class PointerCustomer customer = new Customer()
new Customer()的实例Customer就是类元信息
对象的类型指针指向它的类元数据,虚拟机通过这个指针来确定这个对象是哪个类的实例。
对象头多大一个对象 在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,
如果类型指针没有经过压缩,一个对象 的对象头一共是16个字节。
如果类型指针经过压缩会变为4个字节, (类型指针的压缩会在最后部分说明)
所以对象 的对象头一共是12个字节。
底层源码oop.hpp
class oopDesc {
friend class VMStructs;
private:
volatile markOop _mark; //_mark字段是mark word
union _metadata { //_metadata是类指针klass pointer
Klass* _klass;
narrowKlass _compressed_klass;
} _metadata;
// Fast access to barrier set. Must be initialized.
static BarrierSet* _bs;
public:
markOop mark() const { return _mark; }
markOop* mark_addr() const { return (markOop*) &_mark; }//c++层面的对象标记
...
}
对象头(object header)即是由这两个字段组成,这些术语可以参考Hotspot术语表,
存放类的属性(Field)数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。
org.openjdk.jol
jol-core
0.9
对于一个没有任何属性的对象
package com.dongguo.object;
import org.openjdk.jol.info.ClassLayout;
/**
* @author Dongguo
* @date 2021/9/8 0008-18:30
* @description:
*/
class MyObject{
}
public class MyObjectDemo {
public static void main(String[] args) {
MyObject myObject = new MyObject();
//使用ClassLayout解析object并转成可以打印的格式
System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
}
}
运行结果
com.dongguo.object.MyObject object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x00060a18
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Mark Word 占8个字节
Class Pointer占4个字节(经过压缩)
实例数据为 0个字节
对齐填充4个字节
总大小为16个字节
对于一个有属性的对象
package com.dongguo.object;
import org.openjdk.jol.info.ClassLayout;
/**
* @author Dongguo
* @date 2021/9/8 0008-18:30
* @description:
*/
class MyObject {
int i = 5;
char a = 'a';
}
public class MyObjectDemo {
public static void main(String[] args) {
MyObject myObject = new MyObject();
//使用ClassLayout解析object并转成可以打印的格式
System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
}
}
运行结果
com.dongguo.object.MyObject object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x2000c143
12 4 int MyObject.i 5
16 2 char MyObject.a a
18 6 (object alignment gap)
Instance size: 24 bytes
Space losses: 0 bytes internal + 6 bytes external = 6 bytes total
int 占4个字节
char占2个字节
Mark Word 占8个字节
Class Pointer占4个字节
实例数据为 6个字节 18个字节
根据8的倍数 对齐填充6个字节
总大小为24个字节
对齐填充虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐
这部分内存按8字节补充对齐。
再说对象头的MarkWord32位了解即可
看一下64位的:
在markOop.hpp
就有对对象头的注释介绍
// The markOop describes the header of an object.
//
// Note that the mark is not a real oop but just a word.
// It is placed in the oop hierarchy for historical reasons.
//
// Bit-format of an object header (most significant first, big endian layout below):
//
// 32 bits:
// --------
// hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
// size:32 ------------------------------------------>| (CMS free block)
// PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
// 64 bits:
// --------
// unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)
// PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
// size:64 ----------------------------------------------------->| (CMS free block)
//
// unused:25 hash:31 -->| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && normal object)
// JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && biased object)
// narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
// unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
//
// - hash contains the identity hash value: largest value is
// 31 bits, see os::random(). Also, 64-bit vm's require
// a hash value no bigger than 32 bits because they will not
// properly generate a mask larger than that: see library_call.cpp
// and c1_CodePatterns_sparc.cpp.
//
// - the biased lock pattern is used to bias a lock toward a given
// thread. When this pattern is set in the low three bits, the lock
// is either biased toward a given thread or "anonymously" biased,
// indicating that it is possible for it to be biased. When the
// lock is biased toward a given thread, locking and unlocking can
// be performed by that thread without using atomic operations.
// When a lock's bias is revoked, it reverts back to the normal
// locking scheme described below.
//
// Note that we are overloading the meaning of the "unlocked" state
// of the header. Because we steal a bit from the age we can
// guarantee that the bias pattern will never be seen for a truly
// unlocked object.
//
// Note also that the biased state contains the age bits normally
// contained in the object header. Large increases in scavenge
// times were seen when these bits were absent and an arbitrary age
// assigned to all biased objects, because they tended to consume a
// significant fraction of the eden semispaces and were not
// promoted promptly, causing an increase in the amount of copying
// performed. The runtime system aligns all JavaThread* pointers to
// a very large value (currently 128 bytes (32bVM) or 256 bytes (64bVM))
// to make room for the age bits & the epoch bits (used in support of
// biased locking), and for the CMS "freeness" bit in the 64bVM (+COOPs).
//
// [JavaThread* | epoch | age | 1 | 01] lock is biased toward given thread
// [0 | epoch | age | 1 | 01] lock is anonymously biased
//
// - the two lock bits are used to describe three states: locked/unlocked and 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
// not valid at any other time
//
// We assume that stack/thread pointers have the lowest two bits cleared.
hash: 保存对象的哈希码 age: 保存对象的分代年龄 biased_lock: 偏向锁标识位 lock: 锁状态标识位 JavaThread* :保存持有偏向锁的线程ID epoch: 保存偏向时间戳
nornal object 无锁状态
…
markword(64位)详细分布图,对象布局、GC回收和后面的锁升级就是 对象标记MarkWord里面标志位的变化
一般而言JDK8按照默认情况下,new一个对象占多少内存空间
JOL证明JOL官网http://openjdk.java.net/projects/code-tools/jol/
org.openjdk.jol
jol-core
0.16
使用jol
package com.dongguo.object;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
/**
* @author Dongguo
* @date 2021/9/8 0008-19:09
* @description:
*/
public class ObjectHeadDemo {
public static void main(String[] args) {
Object object = new Object();
//使用ClassLayout解析object并转成可以打印的格式
System.out.println(ClassLayout.parseInstance(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 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
OFFSET 偏移量,也就是到这个字段位置所占用的byte数 SIZE 后面类型的字节大小 TYPE 是Class中定义的类型 DESCRIPTION DESCRIPTION是类型的描述 VALUE VALUE是TYPE在内存中的值
Mark Word 占8个字节
Class Pointer占4个字节(Class Pointer原本占8个字节 经过压缩为4个字节)
实例数据为 0个字节(Object没有属性(Field)数据信息)
对齐填充4个字节
总大小为16个字节
Class Pointer类型指针原本是8个字节 为什么会压缩到4个字节呢查看JVM设置的参数
java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=265410752 -XX:MaxHeapSize=41046572032 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividu
alAllocation -XX:+UseParallelGC
java version "1.8.0_301"
Java(TM) SE Runtime Environment (build 1.8.0_301-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.301-b09, mixed mode)
-XX:+UseCompressedClassPointers -XX:+UseCompressedOops
JVM默认为了提高性能,节约内存
-XX:+UseCompressedClassPointers 默认 开启压缩类型指针
类型指针从8个字节压缩为了4个字节
上述表示开启了类型指针的压缩,以节约空间,假如不加压缩???
-XX:-UseCompressedClassPointers
package com.dongguo.object;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
/**
* @author Dongguo
* @date 2021/9/8 0008-19:09
* @description:
*/
public class ObjectHeadDemo {
public static void main(String[] args) {
Object object = new Object();
//使用ClassLayout解析object并转成可以打印的格式
System.out.println(ClassLayout.parseInstance(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) 00 1c 59 ae (00000000 00011100 01011001 10101110) (-1369891840)
12 4 (object header) f0 02 00 00 (11110000 00000010 00000000 00000000) (752)
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
对象标记8个字节
类型指针8个字节
实例数据0个字节
对齐填充0个字节
一共16个字节
换成其他对象试试package com.dongguo.object;
import org.openjdk.jol.info.ClassLayout;
/**
* @author Dongguo
* @date 2021/9/8 0008-18:30
* @description: -XX:-UseCompressedClassPointers
*/
class MyObject {
int i = 5;
char a = 'a';
long l =99;
}
public class MyObjectDemo {
public static void main(String[] args) {
MyObject myObject = new MyObject();
//使用ClassLayout解析object并转成可以打印的格式
System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
}
}
运行结果
com.dongguo.object.MyObject 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) c8 35 e9 e8 (11001000 00110101 11101001 11101000) (-387369528)
12 4 (object header) 66 01 00 00 (01100110 00000001 00000000 00000000) (358)
16 8 long MyObject.l 99
24 4 int MyObject.i 5
28 2 char MyObject.a a
30 2 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 2 bytes external = 2 bytes total
类型指针占8个字节
package com.dongguo.object;
import org.openjdk.jol.info.ClassLayout;
/**
* @author Dongguo
* @date 2021/9/8 0008-18:30
* @description: XX:+UseCompressedClassPointers
*/
class MyObject {
int i = 5;
char a = 'a';
long l =99;
}
public class MyObjectDemo {
public static void main(String[] args) {
MyObject myObject = new MyObject();
//使用ClassLayout解析object并转成可以打印的格式
System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
}
}
运行结果
com.dongguo.object.MyObject 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) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 int MyObject.i 5
16 8 long MyObject.l 99
24 2 char MyObject.a a
26 6 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 6 bytes external = 6 bytes total
类型指针占4个字节
所以new Object()
当开启压缩类型指针(默认开启) 对象标记8个字节,类型指针4个字节,对齐填充4个字节 共16个字节
当没有开启压缩类型指针 对象标记8个字节,类型指针8个字节 共16个字节