ホームページ > Java > &#&チュートリアル > Java オブジェクト サイズの簡単な分析

Java オブジェクト サイズの簡単な分析

黄舟
リリース: 2017-03-15 11:35:57
オリジナル
1685 人が閲覧しました


最近、気まぐれにJavaオブジェクトのメモリサイズに興味を持ち、インターネットで情報を集めて整理しました。お役に立てれば幸いです。
JVM 内の新しい String ("abc") オブジェクトが占有するメモリ サイズ (64 ビット JDK7 の圧縮サイズ 48B、非圧縮サイズ 64B) を計算できる場合は、ここで終了できます~


Javaオブジェクトのメモリ レイアウト: オブジェクト ヘッダー (Header)、インスタンス データ (Instance Data)、および位置合わせパディング (Padding)
仮想マシンのオブジェクト ヘッダーには 2 つの部分の情報が含まれています。最初の部分は、ハッシュコード、GC 生成期間、ロック ステータス フラグ、スレッドが保持するロック、バイアスなど、オブジェクト自体の実行時データを保存するために使用されます。スレッド ID、バイアスされたタイムスタンプの待機。この部分のデータの長さは、32 ビットと 64 ビットの仮想マシンではそれぞれ 4B と 8B です (ポインタ圧縮はオンになっていません)。正式には「Mark Word」と呼ばれます。 オブジェクトの他の部分は、タイプ ポインター (klass)
です。これは、オブジェクトのクラス メタデータへのポインターであり、仮想マシンはこのポインターを使用して、オブジェクトがどのクラスのインスタンスであるかを判断します。さらに、オブジェクトが Java 配列の場合、仮想マシンは通常の Java オブジェクトのメタデータ情報を通じて Java オブジェクトのサイズを決定できるため、オブジェクト ヘッダーに配列の長さを記録するデータが必要です。ただし、配列のメタデータからは配列のサイズを決定できません。 オブジェクト ヘッダーは、32 ビット システムでは 8B、64 ビット システムでは 16B を占めます。 32 ビット システムでも 64 ビット システムでも、オブジェクトは 8 バイト アライメントされます。 Java が 64 ビット モードでポインタ圧縮をオンにすると、ヘッダーは 32 ビット モードより 4B 大きくなります (マーク領域は 8B シフトされ、kclass 領域は圧縮されます)。ポインタ圧縮がオンになっていない場合、ヘッダーは 8B 大きくなります (マークと kclass は両方とも 8B です)。
つまり、 HotSpot のアライメントは 8 バイトのアライメントです: (オブジェクト ヘッダー + インスタンス データ + パディング)%8 は 0 および 0< に等しくなります。 =パディング

参考 2 で述べたように、JDK5 以降に提供される java.lang.instrument.Instrumentation は、構造のさまざまな側面を追跡し、オブジェクト サイズを測定するための豊富な API を提供します。ただし、これには Java エージェントの使用が必要です。エージェントとインストルメンテーションについては、ここでは説明しません。使用方法のみを説明します。
このクラスは参考資料 3 で提供されています。個人的には、コードは以下の付録 1 に示されています (コードは比較的長いため、記事の最後に簡単に記載します):

このコードは次のことができます。直接コピーして、jar パッケージに入力します (agent.jar という名前ですが、正常にパッケージ化されていない場合は、ブロガーによってパッケージ化されたものを直接ダウンロードできます)。META-INF/MANIFEST.MF に次の行を追加してください。たとえば、コードは次のとおりです (ブロガーのシステムは 64 ビットで、64 ビットの JDK7 を使用しています):

Premain-Class: com.zzh.size.MySizeOf (注意":"后面的空格,否则会报错:invalid header field.)
ログイン後にコピー

次に、コンパイルして実行します。手順は次のとおりです:

コンパイル (agent.jar が配置されます)現在のディレクトリにあります): javac -classpath Agent.jar ObjectSize .java

  1. Run: java -javaagent:agent.jar ObjectSize (出力結果: 16、この結果の分析については後で詳しく説明します)

  2. JDK6 は、32G メモリでパラメータ -XX:+UseCompressedOops を起動します。このパラメータはデフォルトで自動的にオンになります。ポインター圧縮は、実行パラメーターに -XX:-UseCompressedOops を追加することでオフにできます。

    インストルメンテーションを使用してオブジェクトのサイズをテストすることは、オブジェクトのサイズをより鮮明に表現することだけであり、オブジェクトの作成時にそのサイズを手動で計算することで、その合理性と正確性を証明できます。具体的には、アルゴリズムを以下のコード例で示します。
補足: プリミティブ型のメモリ使用量は以下の通りです:


Primitive TypeMemory Required(bytes)boolean1122448 8
byte
short
char
int
float
long
ダブル

 引用类型在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);  
        }  
}
ログイン後にコピー

以上がJava オブジェクト サイズの簡単な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート