整数と整数
私たちは皆、Javaではすべてがオブジェクトであることを知っています。 おそらく、プリミティブとオブジェクト自体へのリンクを除きます。 2つの典型的な状況を見てみましょう。
// int a = 300; // Integer b = 301;
これらの単純な行では、JVMとOOPの両方で大きな違いがあります。 最初のケースでは、スタックからの値を含む4バイトの変数のみがあります。 2番目のケースでは、参照変数と、この変数が参照するオブジェクト自体があります。 したがって、最初のケースで占有サイズが次のとおりであることが明確にわかっている場合:
sizeOf(int)
次に、2番目:
sizeOf(reference) + sizeOf(Integer)
将来的には、2番目のケースでは、消費されるメモリ量は約5倍になり、JVMに依存すると言います。 では、なぜその違いが大きいのかを見てみましょう。
オブジェクトは何で構成されていますか?
消費されるメモリ量を決定する前に、JVMが各オブジェクトに対して保存するものを把握する必要があります。
- オブジェクトのタイトル。
- プリミティブ型のメモリ。
- 参照型のメモリ。
- オフセット/アライメント-実際、これらはオブジェクト自体のデータの後に配置される未使用のバイトです。 これは、メモリアドレスが常にマシンワードの倍数になるように行われ、メモリからの読み取りを高速化し、オブジェクトへのポインタのビット数を減らし、おそらくメモリの断片化を減らします。 Javaでは、オブジェクトのサイズは8バイトの倍数であることに注意してください。
オブジェクトのタイトル構造
クラスの各インスタンスにはタイトルが含まれます。 ほとんどのJVM(Hotspot、openJVM)の各タイトルは、2つの機械語で構成されています。 32ビットシステムの場合、ヘッダーサイズは8バイト、64ビットシステムの場合、それぞれ16バイトです。 各見出しには次の情報が含まれる場合があります。
- マーキングワード(マークワード)-残念ながら、この情報の目的を見つけることができませんでした。これは、将来のために予約されているヘッダーの一部にすぎないと思われます。
- ハッシュコード-各オブジェクトにはハッシュコードがあります。 デフォルトでは、Object.hashCode()メソッドを呼び出した結果はメモリ内のオブジェクトのアドレスを返しますが、一部のガベージコレクターはメモリ内のオブジェクトを移動できますが、オブジェクトヘッダーの場所を使用できるため、ハッシュコードは常に同じままですハッシュコードの元の値を保存します。
- ガベージコレクション情報-各Javaオブジェクトには、メモリ管理システムに必要な情報が含まれています。 これは多くの場合1つまたは2つのフラグビットですが、たとえば、オブジェクトへの参照の数を格納するビットの特定の組み合わせにすることもできます。
- タイプ情報ブロックポインター-オブジェクトのタイプに関する情報が含まれています。 このブロックには、仮想メソッドテーブル、型を表すオブジェクトへのポインター、および追加の構造体へのポインターに関する情報が含まれており、より効率的なインターフェイス呼び出しと動的な型チェックが可能です。
- ロック-各オブジェクトには、ロック状態に関する情報が含まれています。 これは、ロックオブジェクトへのポインタまたはロックの直接表現です。
- 配列の長さ-オブジェクトが配列の場合、ヘッダーは配列の長さを格納するために4バイトで拡張されます。
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はオブジェクトのフィールドを事前にグループ化することにも注意してください。 つまり、クラスのすべてのフィールドは、宣言された順序ではなく、特定の順序でメモリに配置されます。 グループ化の順序は次のようになります。
- 1. 8バイトタイプ(ダブルおよびロング)
- 2. 4バイト型(intおよびfloat)
- 3. 2バイト型(shortおよびchar)
- 4. 1バイトタイプ(
ブールおよびバイト) - 5.参照変数
なんでこんなこと?
辞書などのさまざまなオブジェクトを格納するためのメモリのおおよその量を推定する必要がある状況が発生する場合がありますが、この小さなヘルプはすばやくナビゲートするのに役立ちます。 また、特に設定へのアクセスが利用できない環境では、これは潜在的な最適化方法です。
結論
Javaでのメモリのトピックは非常に興味深く、広範です。この記事を書き始めたとき、結論のあるいくつかの例に収まると思いました。 しかし、深く深く掘るほど、それはますます興味深いものになります。 一般に、オブジェクトにメモリがどのように割り当てられているかを知ることは、メモリの節約、そのような問題の防止、不可能と思われる場所でのプログラムの最適化に役立つため、非常に役立ちます。 もちろん、そのような最適化を使用できる場所は非常にまれですが、それでも...この記事があなたにとって興味深いものであったことを願っています。