`

查看java对象在内存中的布局

    博客分类:
  • j2se
 
阅读更多

接着上篇《一个对象占用多少字节?》中遇到的问题:

        UseCompressOops开启和关闭,对对象头大小是有影响的,开启压缩,对象头是4+8=12byte;关闭压缩,对象头是8+8=16bytes。这个如何观察验证呢?
       基于上述事实,通过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位。

Java代码  收藏代码
  1. import sun.misc.Unsafe;  
  2.   
  3. import java.lang.reflect.Field;  
  4.   
  5. /** 
  6.  * -Xmx1024m 
  7.  * @author tianmai.fh 
  8.  * @date 2014-03-18 19:10 
  9.  */  
  10. public class FieldOffsetTest {  
  11.     static Unsafe unsafe;  
  12.   
  13.     static {  
  14.         Field field = null;  
  15.         try {  
  16.             field = Unsafe.class.getDeclaredField("theUnsafe");  
  17.             field.setAccessible(true);  
  18.             unsafe = (Unsafe) field.get(null);  
  19.         } catch (NoSuchFieldException e) {  
  20.             e.printStackTrace();  
  21.         } catch (IllegalAccessException e) {  
  22.             e.printStackTrace();  
  23.         }  
  24.     }  
  25.   
  26.     static class MyClass {  
  27.         Object a = new Object();  
  28.         Integer b = new Integer(3);  
  29.         int c = 4;  
  30.         long d = 5L;  
  31.         Long[] e = new Long[2];  
  32.         Object[] f = new String[0];  
  33.     }  
  34.     static class B2 {  
  35.         int a;  
  36.         Integer b;  
  37.         int c;  
  38.     }  
  39.   
  40.     static long objectFieldOffset(Field field) {  
  41.         return unsafe.objectFieldOffset(field);  
  42.     }  
  43.   
  44.     static String objectFieldOffset(Class<?> clazz) {  
  45.         Field[] fields = clazz.getDeclaredFields();  
  46.         StringBuilder sb = new StringBuilder(fields.length * 50);  
  47.         sb.append(clazz.getName()).append(" Field offset:\n");  
  48.         for (Field field : fields) {  
  49.             sb.append("\t").append(field.getType().getSimpleName());  
  50.             sb.append("\t").append(field.getName()).append(": ");  
  51.             sb.append(objectFieldOffset(field)).append("\n");  
  52.         }  
  53.         return sb.toString();  
  54.     }  
  55.   
  56.     public static void main(String[] args) throws InterruptedException, NoSuchFieldException {  
  57.         MyClass mc = new MyClass();  
  58.         int[] big = new int[30 * 1024 * 1024];  
  59.         big = null;  
  60.         System.gc();  
  61.         System.out.println(objectFieldOffset((MyClass.class)));  
  62.         System.out.println(objectFieldOffset((B2.class)));  
  63.         Object a = new Long[1];  
  64.         System.out.println(Long[].class.getName());  
  65.         Thread.sleep(1000000);  
  66.     }  
  67. }  

         在启用指针压缩的情况下输出为:

Java代码  收藏代码
  1. com.tmall.buy.structure.FieldOffsetTest$MyClass Field offset:  
  2.     Object  a: 24  
  3.     Integer b: 28  
  4.     int c: 12  
  5.     long    d: 16  
  6.     Long[]  e: 32  
  7.     Object[]f: 36  
  8.   
  9. com.tmall.buy.structure.FieldOffsetTest$B2 Field offset:  
  10.     int a: 12  
  11.     Integer b: 20  
  12.     int c: 16  

        第一个实例变量的偏移量都是12,也就是说对象头占用了12个字节;基本类型int占用4个字节;对象引用占用了4个字节,如MyClass#a;对象数组占用也是4个字节;这里看不出数组这个对象占用了多少个字节。

        在不启用对象指针压缩的时候(vm参数添加-XX:-UseCompressedOops):

Java代码  收藏代码
  1. com.tmall.buy.structure.FieldOffsetTest$MyClass Field offset:  
  2.     Object  a: 32  
  3.     Integer b: 40  
  4.     int c: 24  
  5.     long    d: 16  
  6.     Long[]  e: 48  
  7.     Object[]    f: 56  
  8.   
  9. com.tmall.buy.structure.FieldOffsetTest$B2 Field offset:  
  10.     int a: 16  
  11.     Integer b: 24  
  12.     int c: 20  

       第一个实例变量的偏移量都是16,也就是说对象头占用了16个字节;基本类型int占用4个字节;对象引用占用了8个字节,如MyClass#a;对象数组占用也是8个字节;这里看不出数组这个对象占用了多少个字节。

       那接下来通过对象的内存布局进一步验证:

Java代码  收藏代码
  1. import sun.jvm.hotspot.oops.*;  
  2. import sun.jvm.hotspot.runtime.VM;  
  3. import sun.jvm.hotspot.tools.Tool;  
  4. import sun.jvm.hotspot.utilities.SystemDictionaryHelper;  
  5.   
  6. /** 
  7.  * 打印对象的内存布局 
  8.  */  
  9. public class PrintObjectTest extends Tool {  
  10.     public static void main(String[] args) throws InterruptedException {  
  11.         PrintObjectTest test = new PrintObjectTest();  
  12.         test.start(args);  
  13.         test.stop();  
  14.     }  
  15.   
  16.     @Override  
  17.     public void run() {  
  18.         VM vm = VM.getVM();  
  19.         ObjectHeap objHeap = vm.getObjectHeap();  
  20.         HeapVisitor heapVisitor = new HeapPrinter(System.out);  
  21.         //观察特定对象  
  22.         Klass klass = SystemDictionaryHelper.findInstanceKlass("xxx.yyy.zzz.FieldOffsetTest$MyClass");  
  23.         objHeap.iterateObjectsOfKlass(heapVisitor, klass, false);  
  24.   
  25.         //观察数组对象  
  26.         objHeap.iterate(heapVisitor,new ObjectHeap.ObjectFilter() {  
  27.             @Override  
  28.             public boolean canInclude(Oop oop) {  
  29.                 return oop.isObjArray();  
  30.             }  
  31.         });  
  32.         objHeap.iterate(heapVisitor);  
  33.     }  
  34. }  

        这个程序在运行前,需要传入要监控的java进程id,也就是上边那个程序的进程id,可以通过jps拿到。但是在我的IDEA上,是跑不起来的,是由于权限问题:

Java代码  收藏代码
  1. Attaching to process ID 1923, please wait...  
  2. attach: task_for_pid(1923) failed (5)  
  3. Error attaching to process: sun.jvm.hotspot.debugger.DebuggerException: Can't attach to the process  

        用命令行,sudo就可以了:

Java代码  收藏代码
  1. sudo java -cp $JAVA_HOME/lib/sa-jdi.jar:. xxx.yyy.zzz.PrintObjectTest 进程id > heap_OOps.txt  

        如果你被监控的jvm实例是1.7.x启动的,而命令行监控实例通过1.8的jdk启动,会抛出如下错误:

Java代码  收藏代码
  1. Attaching to process ID 3024, please wait...  
  2. Exception in thread "main" java.lang.NoSuchMethodError: getJavaThreadsInfo  
  3.     at sun.jvm.hotspot.debugger.bsd.BsdDebuggerLocal.init0(Native Method)  
  4.     at sun.jvm.hotspot.debugger.bsd.BsdDebuggerLocal.<clinit>(BsdDebuggerLocal.java:595)  
  5.     at sun.jvm.hotspot.bugspot.BugSpotAgent.setupDebuggerBsd(BugSpotAgent.java:775)  
  6.     at sun.jvm.hotspot.bugspot.BugSpotAgent.setupDebugger(BugSpotAgent.java:519)  
  7.     at sun.jvm.hotspot.bugspot.BugSpotAgent.go(BugSpotAgent.java:492)  
  8.     at sun.jvm.hotspot.bugspot.BugSpotAgent.attach(BugSpotAgent.java:331)  
  9.     at sun.jvm.hotspot.tools.Tool.start(Tool.java:163)  
  10.     at com.tmall.buy.structure.PrintObjectTest.main(PrintObjectTest.java:14)  

         直接全路径用1.7的jdk带的java启动就好了。

         接下来我们看输出,这个是启用指针压缩的,由于输出比较长,我们就只关心我们想看的几个:

Java代码  收藏代码
  1. Oop for com/tmall/buy/structure/FieldOffsetTest$MyClass @ 0x000000011bfce258 (object size = 40)  
  2.  - _mark:    {0} :1  
  3.  - _metadata._compressed_klass:  {8} :InstanceKlass for com/tmall/buy/structure/FieldOffsetTest$MyClass @ 0x0000000146d2a160  
  4.  - a:    {24} :Oop for java/lang/Object @ 0x000000011bf9bb90  
  5.  - b:    {28} :Oop for java/lang/Integer @ 0x000000011bf9bba8  
  6.  - c:    {12} :4  
  7.  - d:    {16} :5  
  8.  - e:    {32} :ObjArray @ 0x000000011bf9bbc0  
  9.  - f:    {36} :ObjArray @ 0x000000011bf9bbd8  
  10.   
  11. ...  
  12.   
  13. ObjArray @ 0x000000011bf9bbc0 (object size = 24)  
  14.  - _mark:    {0} :1  
  15.  - _metadata._compressed_klass:  {8} :ObjArrayKlass for InstanceKlass for java/lang/Long @ 0x0000000146d2b910  
  16.  - 0:    {16} :null  
  17.  - 1:    {20} :null  
  18.   
  19. ...  
  20.   
  21. ObjArray @ 0x000000011bf9bbd8 (object size = 16)  
  22.  - _mark:    {0} :1  
  23.  - _metadata._compressed_klass:  {8} :ObjArrayKlass for InstanceKlass for java/lang/String @ 0x0000000146b229c0  
  24.   
  25. ...  

         可以看到,MyClass这个类的大小是40个字节,不包括它引用的对象的大小,其中大括号是对象实例字段的偏移量,单位是字节。验证了对象头是12 bytes,其中_mark占8个字节_metadata._compressed_klass占用4个字节;剩下的就跟第一个例子中启用了压缩指针的结论一致。这里我们也可以看到数据对象占用的内存空间了,数组对象的头部占用了16个字节,_mark占8个,_metadata._compressed_klass占8个;另外也验证了,对象是8字节对齐的。

    在看不启用对象指针压缩的情况:

Java代码  收藏代码
  1. Oop for com/tmall/buy/structure/FieldOffsetTest$MyClass @ 0x000000011ad491e8 (object size = 64)  
  2.  - _mark:    {0} :1  
  3.  - _metadata._klass:     {8} :InstanceKlass for com/tmall/buy/structure/FieldOffsetTest$MyClass @ 0x0000000145a873d8  
  4.  - a:    {32} :Oop for java/lang/Object @ 0x000000011ad1e1a8  
  5.  - b:    {40} :Oop for java/lang/Integer @ 0x000000011ad211b8  
  6.  - c:    {24} :4  
  7.  - d:    {16} :5  
  8.  - e:    {48} :ObjArray @ 0x000000011ad201c8  
  9.  - f:    {56} :ObjArray @ 0x000000011ad211d0  
  10.   
  11. ...  
  12.   
  13. ObjArray @ 0x000000011ad201c8 (object size = 40)  
  14.  - _mark:    {0} :1  
  15.  - _metadata._klass:     {8} :ObjArrayKlass for InstanceKlass for java/lang/Long @ 0x0000000145a88120  
  16.  - 0:    {24} :null  
  17.  - 1:    {32} :null  
  18.   
  19. ...  
  20.   
  21. ObjArray @ 0x000000011ad211d0 (object size = 24)  
  22.  - _mark:    {0} :1  
  23.  - _metadata._klass:     {8} :ObjArrayKlass for InstanceKlass for java/lang/String @ 0x0000000145876ef0  
  24.   
  25. ...  

        MyClass这个类的大小是64个字节,不包括它引用的对象的大小,其中大括号是对象实例字段的偏移量,单位是字节。验证了对象头是16 bytes,其中_mark占8个字节_metadata._klass占用8个字节;剩下的就跟第一个例子中不启用了压缩指针的结论一致。数组对象的头部占用了24个字节,_mark占8个,_metadata._compressed_klass占16个;另外也验证了,对象是8字节对齐的。

        tips:在查找MyClass对象中数组类型实例字段的内存布局时,可以直接用后边的内存地址搜索@ 0x000000011ad201c8。

        关于对象更多描述信息,请移步到开篇引用的RednaxelaFX那篇文章中学习。enjoy it!

 

转http://yueyemaitian.iteye.com/blog/2034305

分享到:
评论

相关推荐

    Java对象内存布局 - 小分析

    jvm jvm内存布局

    JVM Dump与Java对象的内存布局

    介绍了heap dump和thread dump,以及详细介绍dump工具Memory Analyzer的使用,最后讲解了Java对象的内存布局。

    10Java对象的内存布局1

    // Foo 类构造器会调用其父类 Object 的构造器1 invokespecial java.lang.Object() [8]然后,子类的构造器需要调用

    深入理解java对象,包括对象创建和内存分配

    描述对象的创建过程,对象的内存布局,jvm指针压缩,对象访问

    深入理解JVM之Java对象的创建、内存布局、访问定位详解

    主要介绍了深入理解JVM之Java对象的创建、内存布局、访问定位,结合实例形式详细分析了Java对象的创建、内存布局、访问定位相关概念、原理、操作技巧与注意事项,需要的朋友可以参考下

    ObjectLayout, 记住,用可以优化对象布局设计的Java类.zip

    ObjectLayout, 记住,用可以优化对象布局设计的Java类 ObjectLayout[Gitter](https://badges.gitter.im/Join chat 。...ObjectLayout旨在创建一些有用的核心Java类,这些类是用可以优化的内存布局设计有关详细信息,请

    java源码包---java 源码 大量 实例

    通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥,通常应对私钥加密后再保存、如何从...

    java源码包2

    通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥,通常应对私钥加密后再保存、如何从...

    Java开发技术大全(500个源代码).

    showInstVar.java 演示不同的对象拥有不同的成员变量 showMain.java 演示main方法访问本类成员 showMethod.java 演示如何定义一个方法体 showReturn_1.java return语句示例1 showReturn_2.java return语句示例2...

    JAVA上百实例源码以及开源项目

    通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥,通常应对私钥加密后再保存、如何从...

    java源码包4

    通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥,通常应对私钥加密后再保存、如何从...

    JAVA上百实例源码以及开源项目源代码

    通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥,通常应对私钥加密后再保存、如何从...

    java源码包3

    通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥,通常应对私钥加密后再保存、如何从...

    JAVA面试题最全集

    方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize() ...

    Java开发详解.zip

    000000_【课程介绍 —— 写在前面的话】_Java学习概述笔记.pdf 010101_【第1章:JAVA概述及开发环境搭建】_JAVA发展概述笔记.pdf 010102_【第1章:JAVA概述及开发环境搭建】_Java开发环境搭建笔记.pdf 010201_【第2...

    成百上千个Java 源码DEMO 4(1-4是独立压缩包)

    通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥,通常应对私钥加密后再保存、如何从...

    Java在安卓技术方面的应用

    本文介绍了Java语言在安卓开发中的基础知识,包括面向对象编程、基本语法和异常处理。此外,还讨论了Android系统的架构以及常用的开发工具和框架,如Android SDK、Android Jetpack和Retrofit/RxJava。最后,提供了...

    疯狂JAVA讲义

    6.9.1 对象在内存中的状态 226 6.9.2 强制垃圾回收 227 6.9.3 finalize方法 228 6.9.4 对象的软、弱和虚引用 230 6.10 修饰符的适用范围 233 6.11 使用JAR文件 234 6.11.1 jar命令详解 235 6.11.2 创建可执行...

    JAVA基础课程讲义

    JAVA对象的序列化和反序列化 161 为什么需要序列化和反序列化 161 对象的序列化主要有两种用途 161 序列化涉及的类和接口 162 序列化/反序列化的步骤和实例 162 综合的序列化和反序列化练习 163 JAVA.IO包相关流对象...

    JVM-Java虚拟机

    对象内存布局;如何访问一个对象;GC基本原理;串行收集器;并行收集器; 能学到什么:1,JVM底层运行机制和原理;2JVM参数;3,垃圾回收原理;4,垃圾回收器的使用;5,调优实战案例 导语:平时我们所说的JVM广义上...

Global site tag (gtag.js) - Google Analytics