本文将通过JOL
分析Java对象的内存布局
,包括Throwable
、Class
、Object Header
、HashCode
等内容
代码托管在https://github.com/zhongmingmao/java_object_layout
Throwable
代码
1 2 3 4 5 6 7 8
| public class JOLSample_07_Exceptions {
public static void main(String[] args) throws Exception { out.println(VM.current().details()); out.println(ClassLayout.parseClass(Throwable.class).toPrintable()); } }
|
运行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| # Running 64-bit HotSpot VM. # Using compressed oop with 3-bit shift. # Using compressed klass with 3-bit shift. # Objects are 8 bytes aligned. # 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]
java.lang.Throwable object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 4 (alignment/padding gap) 16 4 java.lang.String Throwable.detailMessage N/A 20 4 java.lang.Throwable Throwable.cause N/A 24 4 java.lang.StackTraceElement[] Throwable.stackTrace N/A 28 4 java.util.List Throwable.suppressedExceptions N/A Instance size: 32 bytes Space losses: 4 bytes internal + 0 bytes external = 4 bytes total
|
分析
- 在内存中并没有为
Throwable.backtrace
分配空间,这是因为这个实例域用于处理虚拟机的内部信息
(VM internal info
),因此**在任何条件下都不允许用户访问
**
- 下面尝试用
反射
的机制访问Throwable.backtrace
反射测试代码
1 2 3 4 5 6 7 8 9
| @Test(expected = NoSuchFieldException.class) public void backtraceTest() throws NoSuchFieldException { try { new Throwable().getClass().getDeclaredField("backtrace"); } catch (NoSuchFieldException e) { System.err.println(String.format("无法通过反射获得Throwable的backtrace实例域 ,%s", e)); throw e; } }
|
在尝试通过反射
获取Throwable的backtrace实例域
时,会抛出NoSuchFieldException
Class
代码
1 2 3 4 5 6 7 8
| public class JOLSample_08_Class {
public static void main(String[] args) throws Exception { out.println(VM.current().details()); out.println(ClassLayout.parseClass(Class.class).toPrintable()); } }
|
运行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| # Running 64-bit HotSpot VM. # Using compressed oop with 3-bit shift. # Using compressed klass with 3-bit shift. # Objects are 8 bytes aligned. # 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]
java.lang.Class object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 4 java.lang.reflect.Constructor Class.cachedConstructor N/A 16 4 java.lang.Class Class.newInstanceCallerCache N/A 20 4 java.lang.String Class.name N/A 24 4 (alignment/padding gap) 28 4 java.lang.ref.SoftReference Class.reflectionData N/A 32 4 sun.reflect.generics.repository.ClassRepository Class.genericInfo N/A 36 4 java.lang.Object[] Class.enumConstants N/A 40 4 java.util.Map Class.enumConstantDirectory N/A 44 4 java.lang.Class.AnnotationData Class.annotationData N/A 48 4 sun.reflect.annotation.AnnotationType Class.annotationType N/A 52 4 java.lang.ClassValue.ClassValueMap Class.classValueMap N/A 56 32 (alignment/padding gap) 88 4 int Class.classRedefinedCount N/A 92 4 (loss due to the next object alignment) Instance size: 96 bytes Space losses: 36 bytes internal + 4 bytes external = 40 bytes total
|
分析
- 在
56
的位置开始有32 Bytes
的的内存空间,JVM
会向该内存区域注入元数据
(meta-information
),相关内容请看下来CPP
代码,暂未弄懂其中原理
- http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/tip/src/share/vm/classfile/javaClasses.hpp
- http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/tip/src/share/vm/classfile/javaClasses.cpp
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class JOLSample_11_ClassWord {
public static void main(String[] args) throws Exception { out.println(VM.current().details()); out.println(ClassLayout.parseInstance(new A()).toPrintable()); out.println(ClassLayout.parseInstance(new B()).toPrintable()); }
public static class A { }
public static class B { } }
|
运行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| # Running 64-bit HotSpot VM. # Using compressed oop with 3-bit shift. # Using compressed klass with 3-bit shift. # Objects are 8 bytes aligned. # 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]
me.zhongmingmao.jol.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) a2 f0 00 f8 (10100010 11110000 00000000 11111000) (-134156126) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
me.zhongmingmao.jol.JOLSample_11_ClassWord$B 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) 32 f2 00 f8 (00110010 11110010 00000000 11111000) (-134155726) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
|
分析
- 在
Hotspot JVM
中,对象头(Object Header)由两部分组成Mark Word
和Class Word
(即「对象内存布局 - Instrumentation + sa-jdi 实例分析」中的Klass Ref
)
- 上述运行结果将对象头的值都打印出来了,方便进行更详细地分析,因为
hashCode
和锁信息
都存储在Mark Word
中
HashCode
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class JOLSample_15_IdentityHashCode {
public static void main(String[] args) throws Exception { out.println(VM.current().details());
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 { } }
|
运行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| # Running 64-bit HotSpot VM. # Using compressed oop with 3-bit shift. # Using compressed klass with 3-bit shift. # Objects are 8 bytes aligned. # 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]
**** Fresh object me.zhongmingmao.jol.JOLSample_15_IdentityHashCode$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) a2 f0 00 f8 (10100010 11110000 00000000 11111000) (-134156126) 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: 36aa7bc2
**** After identityHashCode() me.zhongmingmao.jol.JOLSample_15_IdentityHashCode$A object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 c2 7b aa (00000001 11000010 01111011 10101010) (-1434729983) 4 4 (object header) 36 00 00 00 (00110110 00000000 00000000 00000000) (54) 8 4 (object header) a2 f0 00 f8 (10100010 11110000 00000000 11111000) (-134156126) 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()
之前,Mark Word
对应的hash
全为为0
- 对象尚调用
hashCode()
之后,计算得到的hash
记录在Mark Word
中(08~39
),(Intel是小端序
,低字节存储在低地址)
Mark Word
的格式请参考markOop.hpp
手动计算HashCode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @Test public void haseCodeTest() throws NoSuchFieldException, IllegalAccessException { ClassLayout layout = ClassLayout.parseClass(Object.class); Object object = new Object(); System.out.println(layout.toPrintable(object));
String realHashCode = Integer.toHexString(object.hashCode()); System.out.println(String.format("realHashCode : 0x%s\n", realHashCode)); System.out.println(layout.toPrintable(object));
Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); Unsafe unsafe = (Unsafe) field.get(null); long hashCode = 0; for (long index = 7; index > 0; index--) { hashCode |= (unsafe.getByte(object, index) & 0xFF) << ((index - 1) * 8); } String manualHashCode = Long.toHexString(hashCode); System.out.println(String.format("manualHashCode : 0x%s", manualHashCode)); assertEquals(realHashCode, manualHashCode); }
|
在Hotspot JVM
计算对象的HashCode
后,通过Mark Word
可以手动计算HashCode
,如果哪位大神知道个中原理,麻烦不吝赐教