Javaオブジェクトサイズ

文字列がどのくらいのメモリを占有しているか知っていますか? 「わからない」から「2バイト*文字列の文字数」まで、この質問に対する答えを聞いたことがないほど多くの人がいます。 そして、空の文字列はどれくらいかかりますか? クラスIntegerのオブジェクトが占有する時間を知っていますか? また、3つの整数フィールドを持つ独自のクラスオブジェクトはどのくらい占有しますか? 面白いですが、これらの質問に答えることができるとわかっているJavaプログラマーは一人もいません...はい、ほとんどの人はこれをまったく必要としません。 しかし、これは結局のところ、あなたが運転する車のエンジン排気量を知らない方法です。 あなたは素晴らしいドライバーになることができ、あなたの車の数字2.4または1.6が何を意味するのかさえ疑いません。 しかし、これらの数字の意味をよく知らない人はほとんどいないと思います。 それでは、なぜJavaプログラマーはツールのこの部分についてほとんど知らないのでしょうか?



整数と整数


私たちは皆、Javaではすべてがオブジェクトであることを知っています。 おそらく、プリミティブとオブジェクト自体へのリンクを除きます。 2つの典型的な状況を見てみましょう。

//  int a = 300; //  Integer b = 301;
      
      





これらの単純な行では、JVMとOOPの両方で大きな違いがあります。 最初のケースでは、スタックからの値を含む4バイトの変数のみがあります。 2番目のケースでは、参照変数と、この変数が参照するオブジェクト自体があります。 したがって、最初のケースで占有サイズが次のとおりであることが明確にわかっている場合:

 sizeOf(int)
      
      





次に、2番目:

 sizeOf(reference) + sizeOf(Integer)
      
      





将来的には、2番目のケースでは、消費されるメモリ量は約5倍になり、JVMに依存すると言います。 では、なぜその違いが大きいのかを見てみましょう。



オブジェクトは何で構成されていますか?


消費されるメモリ量を決定する前に、JVMが各オブジェクトに対して保存するものを把握する必要があります。







オブジェクトのタイトル構造


クラスの各インスタンスにはタイトルが含まれます。 ほとんどのJVM(Hotspot、openJVM)の各タイトルは、2つの機械語で構成されています。 32ビットシステムの場合、ヘッダーサイズは8バイト、64ビットシステムの場合、それぞれ16バイトです。 各見出しには次の情報が含まれる場合があります。





Java仕様


Javaのプリミティブ型には事前に定義されたサイズがあることが知られていますが、これはコードの移植性のために仕様で要求されています。 したがって、上記のリンクですべてが完全に説明されているため、プリミティブについては説明しません。 そして、オブジェクトの仕様は何と言っていますか? 各オブジェクトにタイトルがあること以外は何もありません。 つまり、クラスのインスタンスのサイズはJVMごとに異なる場合があります。 実際、プレゼンテーションを簡単にするために、32ビットOracle HotSpot JVMの例を示します。 次に、最もよく使用されるクラスであるIntegerとStringを見てみましょう。



整数と文字列


したがって、Integerクラスのオブジェクトが32ビットHotSpot JVMでどれだけ占有するかを計算してみましょう。 これを行うには、クラス自体を調べる必要があります。静的として宣言されていないすべてのフィールドに関心があります。 これらから、1つのことだけがわかります-int値。 さて、上記の情報に基づいて、以下を取得します。

 : 8   int: 4     8 : 4  : 16 
      
      





次に、回線クラスを見てください。

  private final char value[]; private final int offset; private final int count; private int hash;
      
      





そしてサイズを計算します:

 : 8   int: 4  * 3 == 12      : 4  : 24 
      
      





それだけではありません...文字列には文字の配列への参照が含まれているため、実際には、クラスStringのオブジェクトと文字列を格納する配列自体の2つの異なるオブジェクトを扱っています。 これは、OOPの観点からは事実ですが、メモリの側面から見ると、文字に割り当てられた配列のサイズを受信サイズに追加する必要があります。 そして、これは配列オブジェクト自体ごとにさらに12バイト+文字列の各文字ごとに2バイトです。 もちろん、8バイトの多重度のアライメントを追加することを忘れないでください。 全体として、一見単純なストリングnew String( "a")の結果は次のようになります。

 new String() : 8   int: 4  * 3 == 12      : 4  : 24  new char[1] : 8  + 4     == 12   char: 2  * 1 == 2     8 : 2  : 16  , new String("a") == 40 
      
      





新しい文字列( "a")と新しい文字列( "aa")は同じ量のメモリを占有することに注意することが重要です。 これは理解することが重要です。 この事実を活用する典型的な例は、Stringクラスのハッシュフィールドです。 そうでない場合、文字列オブジェクトは位置合わせのために何らかの理由で24バイトを占有します。 したがって、これらの4バイトには非常に価値のあるアプリケーションがあったことがわかります。 素晴らしい決断ですね。



リンクサイズ


参照変数について予約したいのですが。 原則として、JVM内のリンクのサイズはそのビット深度に依存します。最適化のためだと思います。 したがって、32ビットJVMの場合、リンクのサイズは通常4バイトであり、64ビットJVMの場合は8バイトです。 ただし、この条件は必要ありません。



フィールドのグループ化


JVMはオブジェクトのフィールドを事前にグループ化することにも注意してください。 つまり、クラスのすべてのフィールドは、宣言された順序ではなく、特定の順序でメモリに配置されます。 グループ化の順序は次のようになります。





なんでこんなこと?


辞書などのさまざまなオブジェクトを格納するためのメモリのおおよその量を推定する必要がある状況が発生する場合がありますが、この小さなヘルプはすばやくナビゲートするのに役立ちます。 また、特に設定へのアクセスが利用できない環境では、これは潜在的な最適化方法です。



結論


Javaでのメモリのトピックは非常に興味深く、広範です。この記事を書き始めたとき、結論のあるいくつかの例に収まると思いました。 しかし、深く深く掘るほど、それはますます興味深いものになります。 一般に、オブジェクトにメモリがどのように割り当てられているかを知ることは、メモリの節約、そのような問題の防止不可能と思われる場所でのプログラムの最適化に役立つため、非常に役立ちます。 もちろん、そのような最適化を使用できる場所は非常にまれですが、それでも...この記事があなたにとって興味深いものであったことを願っています。



All Articles