インタビューで、HashMap
のメモリ使用量と、200万個のアイテムがある場合に消費される推定メモリ量を計算するように依頼されました。
例えば:
Map <String,List<String>> mp=new HashMap <String,List<String>>();
マッピングはこんな感じです。
key value
----- ---------------------------
abc ['hello','how']
abz ['hello','how','are','you']
JavaでこのHashMapオブジェクトのメモリ使用量をどのように推定しますか?
短い答え
オブジェクトの大きさを調べるには、プロファイラーを使用します。たとえばYourKitでは、オブジェクトを検索して、それを取得し、ディープサイズを計算できます。これにより、オブジェクトがスタンドアロンであり、オブジェクトのサイズが保守的である場合に使用されるメモリの量についての公正な考えが得られます。
クビレ
オブジェクトの一部が他の構造で再利用されている場合。文字列リテラル。これだけメモリを破棄しても解放されません。実際、HashMapへの1つの参照を破棄しても、メモリがまったく解放されない場合があります。
シリアライゼーションはどうですか?
オブジェクトをシリアル化することは、見積もりを取得するための1つのアプローチですが、シリアル化のオーバーヘッドとエンコードがメモリとバイトストリームで異なるため、大幅にオフになる可能性があります。使用されるメモリの量は、JVM(および32/64ビット参照を使用するかどうか)によって異なりますが、シリアル化形式は常に同じです。
例えば.
Sun/OracleのJVMでは、Integerはヘッダーに16バイト、数値に4バイト、4バイトのパディング(オブジェクトはメモリ内で8バイトに整列)、合計24バイトを取ることができます。ただし、1つの整数をシリアル化すると81バイト、2つの整数をシリアル化すると91バイトになります。つまり、最初のIntegerのサイズが大きくなり、2番目のIntegerがメモリで使用されているサイズよりも小さくなります。
文字列ははるかに複雑な例です。 Sun/Oracle JVMでは、3つのint
値とchar[]
参照が含まれています。したがって、16バイトのヘッダーとint
sに3 * 4バイト、char[]
に4バイト、char[]
のオーバーヘッドに16バイトを使用し、次に2バイトchar、8バイト境界に整列...
サイズを変更できるフラグは何ですか?
64ビット参照がある場合、char[]
参照は8バイト長で、結果として4バイトのパディングになります。 64ビットJVMを使用している場合は、+XX:+UseCompressedOops
を使用して32ビット参照を使用できます。 (したがって、JVMのビットサイズだけを見ても、その参照のサイズはわかりません)
-XX:+UseCompressedStrings
がある場合、JVMは可能な場合はchar配列の代わりにbyte []を使用します。これにより、アプリケーションの速度がわずかに低下する可能性がありますが、メモリ消費が劇的に改善される可能性があります。 byte []を使用した場合、消費されるメモリは1文字あたり1バイトです。 ;)注:4文字の文字列の場合、例のように、8バイトの境界があるため、使用されるサイズは同じです。
「サイズ」とはどういう意味ですか?
指摘したように、HashMapとListは、すべてではないにせよ、多くの文字列を再利用できます。 「サイズ」の意味は、それがどのように使用されるかによって異なります。つまり、構造が単独で使用するメモリの量はどれくらいですか?構造が破棄された場合、どれだけ解放されますか?構造をコピーした場合、どのくらいのメモリが使用されますか?これらの質問には、さまざまな答えがあります。
プロファイラーなしでできること
予想される保守的なサイズが十分に小さいと判断できる場合、正確なサイズは重要ではありません。保守的なケースは、すべての文字列とエントリを最初から作成する場合によくあります。 (HashMapは空であっても10億エントリの容量がある可能性があるため、私はおそらくそう言っています。単一の文字を含む文字列は、20億文字を含む文字列のサブ文字列にすることができます)
System.gc()を実行し、空きメモリを取得してオブジェクトを作成し、別のSystem.gc()を実行して、空きメモリがどれだけ減少したかを確認できます。オブジェクトを何度も作成して平均を取る必要がある場合があります。この演習を何度も繰り返しますが、それはあなたに公正な考えを与えることができます。
(ところで、System.gc()はヒントにすぎませんが、Sun/Oracle JVMはデフォルトで毎回フルGCを実行します)
HashMapのサイズとHashMap + HashMapに含まれるオブジェクトのサイズには違いがあるため、質問を明確にする必要があると思います。
HashMapのサイズを考慮する場合、提供した例では、HashMapは文字列「aby」への1つの参照とリストへの1つの参照を格納します。したがって、リスト内の複数の要素は重要ではありません。リストへの参照のみが値に格納されます。
32ビットのJVMでは、1つのMapエントリに、「aby」参照用の4バイト+ List参照用の4バイト+ Mapエントリの「hashcode」intプロパティ用の4バイト+「next」プロパティ用の4バイトがあります。マップエントリの。
また、4 *(X-1)バイト参照を追加します。「X」は、コンストラクターnew HashMap<String,List<String>>()
を呼び出したときにHashMapが作成した空のバケットの数です。 http://docs.Oracle.com/javase/6/docs/api/Java/util/HashMap.html によると、16である必要があります。
LoadFactor、modCount、threshold、およびsizeもあり、これらはすべてプリミティブintタイプ(さらに16バイト)およびヘッダー(8バイト)です。
したがって、最終的に、上記のHashMapのサイズは4 + 4 + 1 +(4 * 15)+ 16 + 8 = 93バイトになります。
これは、HashMapが所有するデータに基づく概算です。おそらくインタビュアーは、HashMapが機能する方法(たとえば、デフォルトのコンストラクターがMapエントリの16バケットの配列を作成すること、HashMapに格納されているオブジェクトのサイズが参照のみを格納するため、HashMapのサイズには影響しません)。
HashMapは非常に広く使用されているため、特定の状況では、初期容量と負荷係数を持つコンストラクターを使用する価値があります。
すべての文字列が何であるか、および各リストに含まれるアイテムの数がわからない場合、または文字列がすべて一意の参照であるかどうかがわからない場合は、事前に知ることはできません。
確実に知る唯一の方法は、全体をバイト配列(または一時ファイル)にシリアル化し、そのバイト数を正確に確認することです。