接着上篇《一个对象占用多少字节?》中遇到的问题:
基于上述事实,通过new A()和new B()占用字节推断,基本类型int在开启、关闭压缩情况下都是占用4个bytes的,这个没有影响。而通过B和B2在开启、关闭指针压缩情况下的对比看,Integer类型分别占了4 bytes和8 bytes,实际上引用类型都是这样。如何验证?
new Integer[0]在压缩前后分别占用16、24个字节,这是又是为什么呢?
其实要想验证这些信息,需要知道对象在内存中的布局,并且可以把他们输出出来,很巧看到了撒加(RednaxelaFX)大神的《借助HotSpot SA来一窥PermGen上的对象》,可以一窥java对象在内存中的布局。不过我没搞那么复杂,没用oom的方式输出内存对象信息——主要是由于在我的mac os x上Intellij IDEA权限的原因那样做不成功——而是通过启动两个进程的方式,一个监控程序和一个被监控程序。
先写了个程序,也用unsafe的方法获取到字段偏移量,来跟通过SA的方式做对比。首先说明,我的os是Mac OSX 10.9.2,64bit机器,jdk是jdk1.7.0_11,64位。
- import sun.misc.Unsafe;
- import java.lang.reflect.Field;
- /**
- * -Xmx1024m
- * @author tianmai.fh
- * @date 2014-03-18 19:10
- */
- public class FieldOffsetTest {
- static Unsafe unsafe;
- static {
- Field field = null;
- try {
- field = Unsafe.class.getDeclaredField("theUnsafe");
- field.setAccessible(true);
- unsafe = (Unsafe) field.get(null);
- } catch (NoSuchFieldException e) {
- e.printStackTrace();
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
- }
- static class MyClass {
- Object a = new Object();
- Integer b = new Integer(3);
- int c = 4;
- long d = 5L;
- Long[] e = new Long[2];
- Object[] f = new String[0];
- }
- static class B2 {
- int a;
- Integer b;
- int c;
- }
- static long objectFieldOffset(Field field) {
- return unsafe.objectFieldOffset(field);
- }
- static String objectFieldOffset(Class<?> clazz) {
- Field[] fields = clazz.getDeclaredFields();
- StringBuilder sb = new StringBuilder(fields.length * 50);
- sb.append(clazz.getName()).append(" Field offset:\n");
- for (Field field : fields) {
- sb.append("\t").append(field.getType().getSimpleName());
- sb.append("\t").append(field.getName()).append(": ");
- sb.append(objectFieldOffset(field)).append("\n");
- }
- return sb.toString();
- }
- public static void main(String[] args) throws InterruptedException, NoSuchFieldException {
- MyClass mc = new MyClass();
- int[] big = new int[30 * 1024 * 1024];
- big = null;
- System.gc();
- System.out.println(objectFieldOffset((MyClass.class)));
- System.out.println(objectFieldOffset((B2.class)));
- Object a = new Long[1];
- System.out.println(Long[].class.getName());
- Thread.sleep(1000000);
- }
- }
在启用指针压缩的情况下输出为:
- com.tmall.buy.structure.FieldOffsetTest$MyClass Field offset:
- Object a: 24
- Integer b: 28
- int c: 12
- long d: 16
- Long[] e: 32
- Object[]f: 36
- com.tmall.buy.structure.FieldOffsetTest$B2 Field offset:
- int a: 12
- Integer b: 20
- int c: 16
第一个实例变量的偏移量都是12,也就是说对象头占用了12个字节;基本类型int占用4个字节;对象引用占用了4个字节,如MyClass#a;对象数组占用也是4个字节;这里看不出数组这个对象占用了多少个字节。
在不启用对象指针压缩的时候(vm参数添加-XX:-UseCompressedOops):
- com.tmall.buy.structure.FieldOffsetTest$MyClass Field offset:
- Object a: 32
- Integer b: 40
- int c: 24
- long d: 16
- Long[] e: 48
- Object[] f: 56
- com.tmall.buy.structure.FieldOffsetTest$B2 Field offset:
- int a: 16
- Integer b: 24
- int c: 20
第一个实例变量的偏移量都是16,也就是说对象头占用了16个字节;基本类型int占用4个字节;对象引用占用了8个字节,如MyClass#a;对象数组占用也是8个字节;这里看不出数组这个对象占用了多少个字节。
那接下来通过对象的内存布局进一步验证:
- import sun.jvm.hotspot.oops.*;
- import sun.jvm.hotspot.runtime.VM;
- import sun.jvm.hotspot.tools.Tool;
- import sun.jvm.hotspot.utilities.SystemDictionaryHelper;
- /**
- * 打印对象的内存布局
- */
- public class PrintObjectTest extends Tool {
- public static void main(String[] args) throws InterruptedException {
- PrintObjectTest test = new PrintObjectTest();
- test.start(args);
- test.stop();
- }
- @Override
- public void run() {
- VM vm = VM.getVM();
- ObjectHeap objHeap = vm.getObjectHeap();
- HeapVisitor heapVisitor = new HeapPrinter(System.out);
- //观察特定对象
- Klass klass = SystemDictionaryHelper.findInstanceKlass("xxx.yyy.zzz.FieldOffsetTest$MyClass");
- objHeap.iterateObjectsOfKlass(heapVisitor, klass, false);
- //观察数组对象
- objHeap.iterate(heapVisitor,new ObjectHeap.ObjectFilter() {
- @Override
- public boolean canInclude(Oop oop) {
- return oop.isObjArray();
- }
- });
- objHeap.iterate(heapVisitor);
- }
- }
这个程序在运行前,需要传入要监控的java进程id,也就是上边那个程序的进程id,可以通过jps拿到。但是在我的IDEA上,是跑不起来的,是由于权限问题:
- Attaching to process ID 1923, please wait...
- attach: task_for_pid(1923) failed (5)
- Error attaching to process: sun.jvm.hotspot.debugger.DebuggerException: Can't attach to the process
用命令行,sudo就可以了:
- sudo java -cp $JAVA_HOME/lib/sa-jdi.jar:. xxx.yyy.zzz.PrintObjectTest 进程id > heap_OOps.txt
如果你被监控的jvm实例是1.7.x启动的,而命令行监控实例通过1.8的jdk启动,会抛出如下错误:
- Attaching to process ID 3024, please wait...
- Exception in thread "main" java.lang.NoSuchMethodError: getJavaThreadsInfo
- at sun.jvm.hotspot.debugger.bsd.BsdDebuggerLocal.init0(Native Method)
- at sun.jvm.hotspot.debugger.bsd.BsdDebuggerLocal.<clinit>(BsdDebuggerLocal.java:595)
- at sun.jvm.hotspot.bugspot.BugSpotAgent.setupDebuggerBsd(BugSpotAgent.java:775)
- at sun.jvm.hotspot.bugspot.BugSpotAgent.setupDebugger(BugSpotAgent.java:519)
- at sun.jvm.hotspot.bugspot.BugSpotAgent.go(BugSpotAgent.java:492)
- at sun.jvm.hotspot.bugspot.BugSpotAgent.attach(BugSpotAgent.java:331)
- at sun.jvm.hotspot.tools.Tool.start(Tool.java:163)
- at com.tmall.buy.structure.PrintObjectTest.main(PrintObjectTest.java:14)
直接全路径用1.7的jdk带的java启动就好了。
接下来我们看输出,这个是启用指针压缩的,由于输出比较长,我们就只关心我们想看的几个:
- Oop for com/tmall/buy/structure/FieldOffsetTest$MyClass @ 0x000000011bfce258 (object size = 40)
- - _mark: {0} :1
- - _metadata._compressed_klass: {8} :InstanceKlass for com/tmall/buy/structure/FieldOffsetTest$MyClass @ 0x0000000146d2a160
- - a: {24} :Oop for java/lang/Object @ 0x000000011bf9bb90
- - b: {28} :Oop for java/lang/Integer @ 0x000000011bf9bba8
- - c: {12} :4
- - d: {16} :5
- - e: {32} :ObjArray @ 0x000000011bf9bbc0
- - f: {36} :ObjArray @ 0x000000011bf9bbd8
- ...
- ObjArray @ 0x000000011bf9bbc0 (object size = 24)
- - _mark: {0} :1
- - _metadata._compressed_klass: {8} :ObjArrayKlass for InstanceKlass for java/lang/Long @ 0x0000000146d2b910
- - 0: {16} :null
- - 1: {20} :null
- ...
- ObjArray @ 0x000000011bf9bbd8 (object size = 16)
- - _mark: {0} :1
- - _metadata._compressed_klass: {8} :ObjArrayKlass for InstanceKlass for java/lang/String @ 0x0000000146b229c0
- ...
可以看到,MyClass这个类的大小是40个字节,不包括它引用的对象的大小,其中大括号是对象实例字段的偏移量,单位是字节。验证了对象头是12 bytes,其中_mark占8个字节_metadata._compressed_klass占用4个字节;剩下的就跟第一个例子中启用了压缩指针的结论一致。这里我们也可以看到数据对象占用的内存空间了,数组对象的头部占用了16个字节,_mark占8个,_metadata._compressed_klass占8个;另外也验证了,对象是8字节对齐的。
在看不启用对象指针压缩的情况:
- Oop for com/tmall/buy/structure/FieldOffsetTest$MyClass @ 0x000000011ad491e8 (object size = 64)
- - _mark: {0} :1
- - _metadata._klass: {8} :InstanceKlass for com/tmall/buy/structure/FieldOffsetTest$MyClass @ 0x0000000145a873d8
- - a: {32} :Oop for java/lang/Object @ 0x000000011ad1e1a8
- - b: {40} :Oop for java/lang/Integer @ 0x000000011ad211b8
- - c: {24} :4
- - d: {16} :5
- - e: {48} :ObjArray @ 0x000000011ad201c8
- - f: {56} :ObjArray @ 0x000000011ad211d0
- ...
- ObjArray @ 0x000000011ad201c8 (object size = 40)
- - _mark: {0} :1
- - _metadata._klass: {8} :ObjArrayKlass for InstanceKlass for java/lang/Long @ 0x0000000145a88120
- - 0: {24} :null
- - 1: {32} :null
- ...
- ObjArray @ 0x000000011ad211d0 (object size = 24)
- - _mark: {0} :1
- - _metadata._klass: {8} :ObjArrayKlass for InstanceKlass for java/lang/String @ 0x0000000145876ef0
- ...
MyClass这个类的大小是64个字节,不包括它引用的对象的大小,其中大括号是对象实例字段的偏移量,单位是字节。验证了对象头是16 bytes,其中_mark占8个字节_metadata._klass占用8个字节;剩下的就跟第一个例子中不启用了压缩指针的结论一致。数组对象的头部占用了24个字节,_mark占8个,_metadata._compressed_klass占16个;另外也验证了,对象是8字节对齐的。
tips:在查找MyClass对象中数组类型实例字段的内存布局时,可以直接用后边的内存地址搜索@ 0x000000011ad201c8。
相关推荐
本文将详细探讨Java对象在JVM中的创建过程以及其内存布局,帮助读者更深入地理解Java对象是如何在内存中产生的。 #### 二、对象的创建 Java对象是由类实例化的结果,当我们使用`new`关键字创建一个对象时,实际上...
理解对象内存布局有助于优化程序性能,例如减少对象创建、理解和使用对象池,或者通过调整JVM参数来改善垃圾回收效率。`SizeOfAgent.java`、`Rule1.java`和`Rule2.java`可能包含了用于分析对象大小的代码,而`rule1....
Java对象的内存布局是Java虚拟机(JVM)中至关重要的一部分,它涉及到对象创建、内存分配以及对象访问等核心概念。在Java程序中,我们通常使用`new`关键字创建对象,但这只是表面操作,背后涉及的内存管理和构造器...
总结来说,这个例子旨在帮助开发者理解Java对象在内存中的表示方式,以及不同类型和结构的对象如何影响内存使用。通过这种方式,开发者可以优化代码,减少不必要的内存开销,提高应用程序的效率。同时,这也提醒我们...
总的来说,理解Java对象在内存中的表示方式和占用空间,结合合适的工具进行监控,是开发高效、稳定的Java应用的基础。通过深入学习这些知识点,开发者可以更好地优化程序,提高系统资源利用率,避免因内存问题导致的...
介绍了heap dump和thread dump,以及详细介绍dump工具Memory Analyzer的使用,最后讲解了Java对象的内存布局。
在Java中,对象的创建主要包括定义对象变量和构造对象两个步骤,而构造对象的过程又分为为对象分配内存和初始化对象两个阶段。 ##### 3.1 定义对象变量 定义对象变量实际上就是定义一个指向对象的引用,例如: ```...
Java对象内存布局(对象头信息锁升级)示例
在Java编程语言中,计算一个对象的大小是一个相对复杂的过程,因为对象的内存布局涉及到多个因素,包括对象头、实例字段、对齐填充等。这个主题通常与性能优化和内存管理有关,尤其是在处理大规模数据结构时。这篇...
3. **堆内存**:Java对象的主要存储区域,所有实例对象和数组都存储在堆中。堆内存是线程共享的,通过垃圾收集器进行自动内存管理。Java的新生代、老年代和永久代(JDK 8前)都是堆的一部分,它们各自有不同的垃圾...
Java虚拟机(JVM)内存结构是Java程序运行的基础,它将内存划分为若干个不同的数据区域,包括...而Java对象模型则决定了对象在内存中的布局和访问方式。每个主题都值得深入研究,以便更好地理解和调试Java程序的行为。
总的来说,理解Java对象的创建、内存分配和JVM的工作机制对于编写高效、健壮的Java代码至关重要。这涉及到堆内存管理、对象生命周期、垃圾收集以及各种JVM优化策略。开发者应时刻关注这些细节,以便在解决性能问题时...
对象内存布局则涉及到实例字段、对齐填充等,JVM会根据这些信息计算出对象的实际内存地址。 在“Java界面版 内存地址转换的三种方式过程演示”项目中,开发者可能创建了一个用户界面,用户可以通过这个界面触发不同...
2. 关闭压缩参数后的对象内存布局: 如果关闭这两项压缩选项,对象引用将变为64位,即8字节。实验显示`Person`对象占用40字节,这是因为对象头的Klass Pointer从4字节增长到8字节,其他部分保持不变,总大小增加了8...
总结来说,Java内存模型定义了对象的内存布局以及在多线程环境中对共享变量访问的规则,它为Java程序员提供了一个高级的内存模型来保证内存的有序性和一致性。理解和应用Java内存模型是编写高效且稳定Java程序的关键...
对象的内存布局包括实例变量、方法引用等,遵循Java对象模型。 2. **浅层大小、保留大小与弱引用** - **浅层大小**(Shallow Size):一个对象自身的大小,不包括它引用的对象。 - **保留大小**(Retained Size)...
本文将深入探讨如何计算Java对象的大小,以及这个知识点在实际开发中的应用。 首先,Java对象的大小不仅仅包括其字段的大小,还包括对象头(object header)的大小,对于HotSpot虚拟机,它包含了对齐填充、Mark ...
在讨论如何获取JVM内存大小之前,首先需要理解JVM的内存布局。JVM内存主要分为以下几个区域: 1. **堆内存(Heap)**:这是程序共享的内存区域,用于存储对象实例和数组。 2. **方法区(Method Area)**:用于存储类的...
本章节将通过《JAVA 内存视图与封装》这一课件,详细介绍Java程序在运行时内存布局的基本概念及其重要性。 #### 二、对象的内存视图 对象的内存视图是指在程序运行过程中,对象实例在计算机内存中的具体表现形式。...
在Java编程语言中,类对象的创建和内存解析是理解面向对象编程的关键概念。当我们谈论“类对象”时,我们实际上是指类的实例,也就是常说的对象。这些对象是通过类的构造器创建的,而类则定义了对象的属性(字段)和...