cari
RumahJavajavaTutorialJava对象大小浅析
Java对象大小浅析Mar 15, 2017 am 11:35 AM


 最近突发奇想,忽然对Java对象的内存大小感兴趣,去网上搜集了一些资料,并且做一下整理,希望能够各位帮助。
 如果:你能算出new String(“abc”)这个对象在JVM中占用内存大小(64位JDK7中压缩大小48B,未压缩大小64B), 那么看到这里就可以结束了~


Java对象的内存布局:对象头(Header),实例数据(Instance Data)和对齐填充(Padding)
 虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如hashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。这部分数据的长度在32位和64的虚拟机(未开启指针压缩)中分别为4B和8B,官方称之为”Mark Word”。
 对象的另一部分是类型指针(klass),即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。另外如果对象是一个Java数组,那再对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中却无法确定数组的大小。
对象头在32位系统上占用8B,64位系统上占16B。 无论是32位系统还是64位系统,对象都采用8字节对齐。Java在64位模式下开启指针压缩,比32位模式下,头部会大4B(mark区域变位8B,kclass区域被压缩),如果没有开启指针压缩,头部会大8B(mark和kclass都是8B),换句话说,
 HotSpot的对齐方式为8字节对齐:(对象头+实例数据+padding)%8 等于0 且 0<=padding<8。以下说明都是以HotSpot为基准。


 在参考资料2中提到,再JDK5之后提供的java.lang.instrument.Instrumentation提供了丰富的对结构的等各方面的跟踪和对象大小的测量API。但是这个东西需要采用java的agent代理才能使用,至于agent代理和Instrumentation这里就不阐述了,我这里只阐述其使用方式。
 在参考资料3中提供了这个类,个人觉得很实用,代码如下所附1所示(代码比较长,索性就放到文章最后了):
 这段代码可以直接拷贝,然后将其打成jar包(命名为agent.jar,如果没有打包成功,可以直接下载博主打包好的),注意在META-INF/MANIFEST.MF中添加一行:

Premain-Class: com.zzh.size.MySizeOf (注意":"后面的空格,否则会报错:invalid header field.)

 举个案例,代码如下(博主的系统是64位的,采用的是64位的JDK7):

import com.zzh.size.MySizeOf;public class ObjectSize
{    public static void  main(String args[])
    {
        System.out.println(MySizeOf.sizeOf(new Object()));
    }
}

 接下来进行编译运行,步骤如下:

  1. 编译(agent.jar放在当前目录下):javac -classpath agent.jar ObjectSize.java

  2. 运行:java -javaagent:agent.jar ObjectSize(输出结果:16,至于这个结果的分析,稍后再阐述)

 JDK6推出参数-XX:+UseCompressedOops,在32G内存一下默认会自动打开这个参数。可以在运行参数中添加-XX:-UseCompressedOops来关闭指针压缩。
 使用Instrumentation来测试对象的大小,只是为了更加形象的表示一个对象的大小,实际上当一个对象建立起来的时候可以手动计算其大小,代码案例实践用来证明理论知识的合理性及正确性,具体算法在下面的代码案例中有所体现。

补充:原生类型(primitive type)的内存占用如下:

Primitive Type Memory Required(bytes)
boolean 1
byte 1
short 2
char 2
int 4
float 4
long 8
double 8

 引用类型在32位系统上每个占用4B, 在64位系统上每个占用8B。


案例分析
 扯了这么多犊子,估计看的玄乎玄乎的,来几段代码案例来实践一下。

案例1:上面的new Object()的大小为16B,这里再重申一下,博主测试机是64位的JDK7,如无特殊说明,默认开启指针压缩。

new Object()的大小=对象头12B(8Bmak区,4Bkclass区)+padding的4B=16B

案例2

    static class A{        int a;
    }    static class B{        int a;        int b;
    }    public static void  main(String args[])
    {
        System.out.println(MySizeOf.sizeOf(new Integer(1)));
        System.out.println(MySizeOf.sizeOf(new A()));
        System.out.println(MySizeOf.sizeOf(new B()));
    }

输出结果:

(指针压缩) 16    16    24
(指针未压缩)24    24    24

分析1(指针压缩):

new Integer(1)的大小=12B对象头+4B的实例数据+0B的填充=16Bnew 
A()的大小=12B对象头+4B的实例数据+0B的填充=16B
new B()的大小=12B对象头+2*4B的实例数据=20B,填充之后=24B

分析2(指针未压缩):

new Integer(1)的大小=16B对象头+4B的实例数据+4B的填充=24B
new A()的大小=16B对象头+4B的实例数据+4B的填充=24B
new B()的大小=16B对象头+2*4B的实例数据+0B的填充=24B

案例3

System.out.println(MySizeOf.sizeOf(new int[2]));
System.out.println(MySizeOf.sizeOf(new int[3]));
System.out.println(MySizeOf.sizeOf(new char[2]));
System.out.println(MySizeOf.sizeOf(new char[3]));

输出结果:

(指针压缩) 24    32    24    24
(指针未压缩) 32    40    32    32

分析1(指针压缩):

new int[2]的大小=12B对象头+压缩情况下数组比普通对象多4B来存放长度+2*4B的int实例大小=24B
new int[3]的大小=12B对象头+4B长度+3*4B的int实例大小=28B,填充4B =32B
new char[2]的大小=12B对象头+4B长度+2*2B的实例大小=20B,填充4B=24B
new char[3]的大小=12B对象头+4B长度+3*2B的实例大小+2B填充=24B
(PS:new char[5]的大小=32B)

分析2(指针未压缩):

new int[2]的大小=16B对象头+未压缩情况下数组比普通对象多8B来存放长度+2*4B实例大小=32B
new int[3]的大小=16B+8B+3*4B+4B填充=40B
new char[2]的大小=16B+8B+2*2B+4B填充=32B
new char[2]的大小=16B+8B+3*2B+2B填充=32B
(PS:new char[5]的大小为40B)

案例4(sizeOf只计算本体对象大小,fullSizeOf计算本体对象大小和引用的大小,具体可以翻阅附录1的代码).

System.out.println(MySizeOf.sizeOf(new String("a")));
System.out.println(MySizeOf.fullSizeOf(new String("a")));
System.out.println(MySizeOf.fullSizeOf(new String("aaaaa")));

输出结果:

(指针压缩)24    48    56    
(指针未压缩)32    64   72

分析1(指针压缩):

翻看String(JDK7)的源码可以知道,
String有这几个成员变量:(static变量属于类,不属于实例,所以声明为static的不计入对象的大小)
private final char value[];
private int hash;
private transient int hash32 = 0;

MySizeOf.sizeOf(new String("a"))的大小=12B对象头+2*4B(成员变量hash和hash32)+4B(压缩的value指针)=24B
MySizeOf.fullSizeOf(new 
String("a"))的大小=12B对象头+2*4B(成员变量hash和hash32)+4B指针+
(value数组的大小=12B对象头+4B数组长度+1*2B实例大小+6B填充=24B)=12B+8B+4B+24B=48B
(PS: new String("aa"),new String("aaa"),new String("aaaa")的fullSizeOf大小都为48B)
MySizeOf.fullSizeOf(new String("aaaaa"))的大小=12B+2*4B+4B+(12B+4B+5*2B+6B填充)=24B+32B=56B

分析2(指针未压缩)

MySizeOf.sizeOf(new String("a"))的大小=16B+2*4B+8B(位压缩的指针大小) =32B
MySizeOf.fullSizeOf(new String("a"))的大小=16B对象头+2*4B(成员变量hash和hash32)+8B指针+(value数组的大小=16B对象头+8B数组长度+1*2B实例大小+6B填充=32B)=32B+32B=64B
(PS: new String("aa"),new String("aaa"),new String("aaaa")的fullSizeOf大小都为64B)
MySizeOf.fullSizeOf(new String("aaaaa"))的大小=16B+2*4B+8B+(16B+8B+5*2B+6B填充)=32B+40B=72B

 这些计算结果只会少不会多,因为在代码运行过程中,一些对象的头部会伸展,mark区域会引用一些外部的空间(轻量级锁,偏向锁,这里不展开),所以官方给出的说明也是,最少会占用多少字节,绝对不会说只占用多少字节。

如果是32位的JDK,可以算一下或者运行一下上面各个案例的结果。

 看来上面的这些我们来手动计算下new String()的大小:
1. 指针压缩的情况

12B对象头+2*4B实例变量+4B指针+(12B对象头+4B数组长度大小+0B实例大小)=24B+16B=40B
  1. 指针未压缩的情况

16B+2*4B+8B指针+(16B+8B数组长度大小+0B)=32B+24B=56B

 所以一个空的String对象最少也要占用40B的大小,所以大家在以后应该编码过程中要稍微注意下。其实也并不要太在意,相信能从文章开头看到这里的同学敲的代码也数以万计了,不在意这些也并没有什么不妥之处,只不过如果如果你了解了的话对于提升自己的逼格以及代码优化水平有很大的帮助,比如:能用基本类型的最好别用其包装类。

附:agent.jar包源码

package com.zzh.size;import java.lang.instrument.Instrumentation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;
public class MySizeOf{
     static Instrumentation inst;  

        public static void premain(String args, Instrumentation instP) {  
            inst = instP;  
        }  

        /** 
         * 直接计算当前对象占用空间大小,包括当前类及超类的基本类型实例字段大小、<br></br> 
         * 引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小;<br></br> 
         * 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小 <br></br> 
         * 
         * @param obj 
         * @return 
         */  
        public static long sizeOf(Object obj) {  
            return inst.getObjectSize(obj);  
        }  

        /** 
         * 递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小 
         * 
         * @param objP 
         * @return 
         * @throws IllegalAccessException 
         */  
        public static long fullSizeOf(Object objP) throws IllegalAccessException {  
            Set<Object> visited = new HashSet<Object>();  
            Deque<Object> toBeQueue = new ArrayDeque<>();  
            toBeQueue.add(objP);  
            long size = 0L;  
            while (toBeQueue.size() > 0) {  
                Object obj = toBeQueue.poll();  
                //sizeOf的时候已经计基本类型和引用的长度,包括数组  
                size += skipObject(visited, obj) ? 0L : sizeOf(obj);  
                Class<?> tmpObjClass = obj.getClass();  
                if (tmpObjClass.isArray()) {  
                    //[I , [F 基本类型名字长度是2  
                    if (tmpObjClass.getName().length() > 2) {  
  
  for (int i = 0, len = Array.getLength(obj); i < len; i++) {  
                            Object tmp = Array.get(obj, i);  
                            if (tmp != null) {  
                                //非基本类型需要深度遍历其对象  
                                toBeQueue.add(Array.get(obj, i));  
                            }  
                        }  
                    }  
                } else {  
                    while (tmpObjClass != null) {  
                        Field[] fields = tmpObjClass.getDeclaredFields();  
                        for (Field field : fields) {  
      
       if (Modifier.isStatic(field.getModifiers())   //静态不计  
                                    || field.getType().isPrimitive()) {    //基本类型不重复计  
                                continue;  
                            }  

                            field.setAccessible(true);  
                            Object fieldValue = field.get(obj);  
                            if (fieldValue == null) {  
                                continue;  
                            }  
                            toBeQueue.add(fieldValue);  
                        }  
                        tmpObjClass = tmpObjClass.getSuperclass();  
                    }  
                }  
            }  
            return size;  
        }  

        /** 
         * String.intern的对象不计;计算过的不计,也避免死循环 
         * 
         * @param visited 
         * @param obj 
         * @return 
         */  
        static boolean skipObject(Set<Object> visited, Object obj) {  
            if (obj instanceof String && obj == ((String) obj).intern()) {  
                return true;  
            }  
            return visited.contains(obj);  
        }  
}

Atas ialah kandungan terperinci Java对象大小浅析. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
带你搞懂Java结构化数据处理开源库SPL带你搞懂Java结构化数据处理开源库SPLMay 24, 2022 pm 01:34 PM

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于结构化数据处理开源库SPL的相关问题,下面就一起来看一下java下理想的结构化数据处理类库,希望对大家有帮助。

Java集合框架之PriorityQueue优先级队列Java集合框架之PriorityQueue优先级队列Jun 09, 2022 am 11:47 AM

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于PriorityQueue优先级队列的相关知识,Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的,下面一起来看一下,希望对大家有帮助。

完全掌握Java锁(图文解析)完全掌握Java锁(图文解析)Jun 14, 2022 am 11:47 AM

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于java锁的相关问题,包括了独占锁、悲观锁、乐观锁、共享锁等等内容,下面一起来看一下,希望对大家有帮助。

一起聊聊Java多线程之线程安全问题一起聊聊Java多线程之线程安全问题Apr 21, 2022 pm 06:17 PM

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于多线程的相关问题,包括了线程安装、线程加锁与线程不安全的原因、线程安全的标准类等等内容,希望对大家有帮助。

详细解析Java的this和super关键字详细解析Java的this和super关键字Apr 30, 2022 am 09:00 AM

本篇文章给大家带来了关于Java的相关知识,其中主要介绍了关于关键字中this和super的相关问题,以及他们的一些区别,下面一起来看一下,希望对大家有帮助。

Java基础归纳之枚举Java基础归纳之枚举May 26, 2022 am 11:50 AM

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于枚举的相关问题,包括了枚举的基本操作、集合类对枚举的支持等等内容,下面一起来看一下,希望对大家有帮助。

java中封装是什么java中封装是什么May 16, 2019 pm 06:08 PM

封装是一种信息隐藏技术,是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法;封装可以被认为是一个保护屏障,防止指定类的代码和数据被外部类定义的代码随机访问。封装可以通过关键字private,protected和public实现。

归纳整理JAVA装饰器模式(实例详解)归纳整理JAVA装饰器模式(实例详解)May 05, 2022 pm 06:48 PM

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于设计模式的相关问题,主要将装饰器模式的相关内容,指在不改变现有对象结构的情况下,动态地给该对象增加一些职责的模式,希望对大家有帮助。

See all articles

Alat AI Hot

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

AI Hentai Generator

AI Hentai Generator

Menjana ai hentai secara percuma.

Alat panas

Pelayar Peperiksaan Selamat

Pelayar Peperiksaan Selamat

Pelayar Peperiksaan Selamat ialah persekitaran pelayar selamat untuk mengambil peperiksaan dalam talian dengan selamat. Perisian ini menukar mana-mana komputer menjadi stesen kerja yang selamat. Ia mengawal akses kepada mana-mana utiliti dan menghalang pelajar daripada menggunakan sumber yang tidak dibenarkan.

DVWA

DVWA

Damn Vulnerable Web App (DVWA) ialah aplikasi web PHP/MySQL yang sangat terdedah. Matlamat utamanya adalah untuk menjadi bantuan bagi profesional keselamatan untuk menguji kemahiran dan alatan mereka dalam persekitaran undang-undang, untuk membantu pembangun web lebih memahami proses mengamankan aplikasi web, dan untuk membantu guru/pelajar mengajar/belajar dalam persekitaran bilik darjah Aplikasi web keselamatan. Matlamat DVWA adalah untuk mempraktikkan beberapa kelemahan web yang paling biasa melalui antara muka yang mudah dan mudah, dengan pelbagai tahap kesukaran. Sila ambil perhatian bahawa perisian ini

SublimeText3 versi Inggeris

SublimeText3 versi Inggeris

Disyorkan: Versi Win, menyokong gesaan kod!

EditPlus versi Cina retak

EditPlus versi Cina retak

Saiz kecil, penyerlahan sintaks, tidak menyokong fungsi gesaan kod

SublimeText3 Linux versi baharu

SublimeText3 Linux versi baharu

SublimeText3 Linux versi terkini