記事のディレクトリ
どのように多くのJavaオブジェクトの占領バイト?
最近読ん「で、深さJava仮想マシンの理解、」心が自然に非常に一般的な問題があるので、より良い、Javaオブジェクトのメモリのレイアウトの理解を持って、最終的にはJavaオブジェクトは多くのメモリを取るのですか?
上記の問題を把握しようとすると、基本を補完することから始めます。
1、Javaオブジェクトのレイアウト
ホットスポットVMにおいて、レイアウトオブジェクトがメモリに格納されている3つの領域に分けることができる:オブジェクトヘッダ(ヘッダ)、インスタンスデータ(インスタンスデータ)とアライメントパディング(パディング)
いくつかのケースでは、JVMは単にヒープにオブジェクトではありません。例えば:原則として、小さなスレッドローカルスタック上のオブジェクトではなく、ヒープインチ
オブジェクト占有メモリサイズは、オブジェクトの現在の状態に依存します。たとえば、次の同期ロックのオブジェクトが有効であるか、またはオブジェクトがリサイクルされているかどうか。
レッツのような単一スタックオブジェクトの外観を見て
ヒープには、各オブジェクトが4個のドメイン(A、B、C、及びD)、一枚ずつで構成され、我々は以下の説明:
A:オブジェクトヘッダは、ほとんど占めバイト、表現オブジェクトの現在の状態情報
B:スペースの基本的なタイプのドメインを占有(等ネイティブINTのドメイン、ブール、短い、)
C:基準空間は、タイプフィールドによって占有さ(タイプフィールドの参照は、他のオブジェクトを参照するために参照し、それぞれ参照4バイト)
Dは:スペースが(後述するもの充填剤である)充填剤によって占め
1.1オブジェクトヘッダ(ヘッダ):
JavaオブジェクトヘッダにMarkword(8バイト)+クラスポインタkclass(タイプ面積法における細胞型へのポインタ、オープン8バイトではなく、デフォルトの4バイトの圧縮によってイネーブル)組成物、それ12byteデフォルト。
そのようなハッシュコード(ハッシュコード)として、データ自体をオブジェクトのランタイムを格納するため、GC世代年齢ロック状態フラグスレッドはロック、スレッドIDバイアス、バイアスのタイムスタンプを保持します。64ビットの仮想マシン(開いていない圧縮ポインタ)で64ビットです。
Markword:
クラスポインタkclass:
オブジェクトのkclassクラスは、アドレスが処理領域に属するで圧縮されていない場合は、4バイトの記憶領域と、それは、ポインタ、JVMが圧縮されているデフォルトのポインタであり、格納された8バイトです。圧縮おっとの知識は、あなたが自分自身の理解を深め、関連情報にアクセスすることができます。
オブジェクトは、Java配列である場合、その中のオブジェクトは、アレイのデータ長を記録するためのヘッドを持っている必要があり、これは4つのバイトを占める(配列に対して即ち、デフォルトは4 + 4 + = 8 16バイト)。仮想マシンが決定することができるためのJavaの大きさは、メタデータ情報通常のJavaオブジェクトを通してオブジェクトが、メタデータ配列から配列のサイズを決定することはできません。
(必ずしもすべての仮想マシンの実装では、オブジェクトのメタデータは、オブジェクト自体を通過する必要がいないことが判明、つまり、オブジェクト上のポインタのデータ型を保持しなければならない、参照オブジェクトの場所にアクセスすることができます)
1.2インスタンス・データ(インスタンスデータ)
データ部分の例は、このクラスの親クラスのメンバ変数とメンバ変数を含むメンバ変数の値です。つまり、非静的がヒープに格納されたオブジェクトと可変領域と定常値に静的メソッドの除去、変数値、です。
変更静的変数は、それゆえ、おそらくスタティック変数および定数に格納されたオブジェクトへの参照をメソッド領域におけるクラス内のデータ構造に反映することになるので。
1.3アライメントパディング(パディング)
オブジェクトの全体の長さは、8バイトの整数倍ことを確実にするために使用されます。
要求されたホットスポットオブジェクトの全体の長さは、8バイトの整数倍でなければなりません。オブジェクトヘッダは、8バイトの整数倍でなければならないが、インスタンスデータの部分の長さは任意であるからです。補助的なアライメントは、オブジェクト全体のフィールドの合計長さが8の整数倍であることを保証するために必要とされます。
2、Javaのデータ型います
- 基礎となるデータ型(プリミティブ型)
- 参照型(基準型)
基礎となるデータ・タイプとして2.1フットプリント
2.2参照型メモリフットプリントとして次の
異なる基礎となるデータ型の参照型、オブジェクト自体に加えて、それへの参照(ポインタ)もあり、ポインタが仮想マシン上で64 8バイトのメモリを占有圧縮が4バイトである場合、ポインタがオンされデフォルトでは開くことです。
2.3フィールドの並べ替え
メモリをより効率的に利用するために、データのインスタンスフィールドを並べ替えます。長い=二重> INT =フロート:として優先 >チャー=短い>バイト>ブーリアン>オブジェクト参照
を以下に示すタイプ
class FieldTest{
byte a;
int c;
boolean d;
long e;
Object f;
}
これは、(オープンCompressedOopsオプション)に並べ替えされます。
OFFSET SIZE TYPE DESCRIPTION
16 8 long FieldTest.e
24 4 int FieldTest.c
28 1 byte FieldTest.a
29 1 boolean FieldTest.d
30 2 (alignment/padding gap)
32 8 java.lang.Object FieldTest.f
確認します3.
概念の上に完成し、我々は確認するために行くことができます。
3.1フルーツクラス継承したオブジェクトのクラスがあり、我々は、彼らが多くのメモリを占有していることが、新しいオブジェクトや果物を作成することでしたか?
class Fruit extends Object {
private int size;
}
Object object = new Object();
Fruit f = new Fruit();
最初のルック・オブジェクト・オブジェクト、上記知見により、それMarkwordは8バイト、kclass 4バイトであり、それはスペースを占めるように、12バイト+4バイトアライメントパディングまで追加16バイトです。
フルーツオブジェクト、同じで見てみましょうが、それのMarkwordは8バイトで、kclassは4バイトですが、それはまた、メンバ変数のサイズが、int型は4つのバイトを占め、正確に16バイトまで追加アライメントパディングが必要とされていません。
次に、どのように私たちの結論を検証するには?結局のところ、我々はまだその百聞は一見にしかずと信じて!幸いなことに、JDKは、ツール提供JOLコアは、私たちは、オブジェクトヘッダ情報によって占有されるメモリを分析することを可能にします。具体的な基準
JOLも非常に簡単です:
プリントヘッドの情報
public static void main(String[] args) {
System.out.print(ClassLayout.parseClass(Fruit.class).toPrintable());
System.out.print(ClassLayout.parseClass(Object.class).toPrintable());
}
出力
com.zzx.algorithm.tst.Fruit object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 int Fruit.size N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
私たちは、出力が私たちの以前の結果と一致して16バイトである見ることができます。
配列の最大長はInteger.MAXなるように3.2は、オブジェクト・クラスおよびインタフェース・タイプのタイプに加えて、(ストレージアレイの4バイトの長さがあり、オブジェクトのアレイ・タイプ・フィールドに加えて、上記発現、Java配列種類のオブジェクトが存在します)。オブジェクトによって占められるメモリのアレイであるので、8 + 4 + 4 = 16バイト、メモリアレイのメンバーが含まれていないもちろん、の。
また、テストを実行します。
public static void main(String[] args) {
String[] strArray = new String[0];
System.out.println(ClassLayout.parseClass(strArray.getClass()).toPrintable());
}
出力:
[Ljava.lang.String; object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 16 (object header) N/A
16 0 java.lang.String String;.<elements> N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
出力オブジェクトヘッダ16の長さは、我々の分析と一致しています。
3.3次ルックオブジェクトインスタンスデータの一部:
説明の便宜上、我々はアップルフルーツクラス上の継承するクラスを作成します
public class Apple extends Fruit {
private int size;
private String name;
private Apple brother;
private long create_time;
}
アップル//印刷対象分布情報
System.out.println(ClassLayout.parseClass(Apple.class).toPrintable());
//出力
com.zzx.algorithm.tst.Apple object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 int Fruit.size N/A
16 8 long Apple.create_time N/A
24 4 int Apple.size N/A
28 4 java.lang.String Apple.name N/A
32 4 com.zzx.algorithm.tst.Apple Apple.brother N/A
36 4 (loss due to the next object alignment)
Instance size: 40 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
リンゴのオブジェクトは、プロパティクラス果実の大きさ(サイズフルーツがプライベートであるか、または継承されており、アップル独自のサイズ共存が)、ならびにそれらの定義から継承され、それぞれ、最初の12のバイトを見ることができます4つのプロパティ、基本データ型に直接割り当てられたオブジェクトの型がポインタを格納している4バイト(ポインタ圧縮がデフォルトで有効になっています)私たちは新しいAppleのオブジェクトを持っているので、最終的には、40バイトであるが、直接取り上げますメモリの40バイトをスタック、メモリ割り当ての対象は明らかに、私たちはコードを書くとき、それは時間を意識し、メモリの最適化がどうあるべきか何を知ってみましょう!
特別な注意:クラスのメンバとして文字列変数は、析出、参照、4バイトのサイズであるが、文字列S =「string」を算出した場合、サイズ24byte
ここで再び上記すでに出てマークされ、小さな知識につながります。
親クラスがサブクラスに継承するかどうかのプライベートメンバ変数?
当然の答えは、私たち以上はい、アップルのクラス分析で、親フルーツ型サイズのプライベートメンバ変数を持っている、アップル自体はサイズのメンバ変数を持って、彼らは共存できます。計画、民間のコントロールのクラスのメンバ変数への専用アクセスが、コンパイラレベル制限の焦点は、実メモリに、プライベートまたはパブリックかどうか、ルールによってまとめて格納され、仮想マシンがないことに注意してください違いは何ですか!
4は、新しいメソッドの内部ヒープまたはスタック上のオブジェクトのですか?
オブジェクトはヒープに割り当てられていることで、最終的には、スタック上に(すなわち、そのアドレスを格納する)オブジェクトへの参照点があるでしょう私たちの従来の理解は、それがテストに、ではありません!
私たちは、億のアップルオブジェクト内のループを作成し、すでにアップルのオブジェクトが40バイト、スペースの4ギガバイトの合計を占めていることを計算すると、サイクルの実行時間を記録します。
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
newApple();
}
System.out.println("take time:" + (System.currentTimeMillis() - startTime) + "ms");
}
public static void newApple() {
new Apple();
}
私たちは、JVMは、-XXを追加与える:+ PrintGCは、GCログログのコンパイラの出力を実行するように、コンフィギュレーションを実行している
//操作した結果が、どのGCログをエクスポートしませんでした
take time:6ms
億個のオブジェクトは、割り当てを完了するために6msの、そしてオブジェクトはヒープ上に割り当てられている場合は何のGCは、明らかに、それは実際には、上記の例のコードは、アップルのオブジェクトは、すべての概念を提案し、ここで、スタックに割り当てられ、不可能ではありませんスタックに割り当てられるように最適化されるようにポインタ脱出、newApple新しいメソッドオブジェクトAppleは、使用済みの外にされていない、我々は実行方法のスタックフレームの完了後に空になることを知っているので、何のGCはないだろう。
私たちは、テストへの仮想マシンの動作パラメータを設定することができます。
//ポインタは、仮想マシンのエスケープ分析を閉じます
-XX:-DoEscapeAnalysis
//近い仮想マシンのスカラー置換
-XX:-EliminateAllocations
そこVMオプションで上記の2つのパラメータを追加し、再びそれを実行
[GC (Allocation Failure) 236984K->440K(459776K), 0.0003751 secs]
[GC (Allocation Failure) 284600K->440K(516608K), 0.0004272 secs]
[GC (Allocation Failure) 341432K->440K(585216K), 0.0004835 secs]
[GC (Allocation Failure) 410040K->440K(667136K), 0.0004655 secs]
[GC (Allocation Failure) 491960K->440K(645632K), 0.0003837 secs]
[GC (Allocation Failure) 470456K->440K(625152K), 0.0003598 secs]
take time:5347ms
この時間は、Appleは、ヒープ上のオブジェクトを割り当てると、ヒープがすべてのスレッドで共有されているので、時間に割り当てられた同期メカニズムがなければならない、とトリガーためGCは、ログの多くを見ると、以前よりも多くの長い時間を実行することができますGCの数が多いので、非効率性がたくさん。
要約すると:優先順位がスタックに割り当てられた、またはヒープ上に割り当てられたときに、仮想マシンのポインタエスケープ解析がデフォルトで有効になって、オブジェクトは逃れられないだろう。
ここでは、上の「どのくらいのメモリオブジェクトが占め?」この質問はかなり包括的に答えることができました。
参考記事:
https://blog.csdn.net/zzx410527/article/details/93646925
どのくらいのHashMapオブジェクトバイトの?
= +ターゲットオブジェクトヘッダアライメントパディングメンバ変数+
オブジェクトヘッダ構造:ストレージのハッシュコード、同期、GCの:C、オブジェクトヘッダと同じであるオブジェクトのボディ構造を有するオブジェクトヘッダ本体とオブジェクトは、二つのドメインから構成される:Javaは構造内部ヒープのオブジェクトは、そのようなものです--_のklassポインタフィールド_MASKドメイン、およびオブジェクトクラスのオブジェクトのメソッドを対象領域は、64ビットシステムのため、理論的にはヘッド8 + 8 = 16バイトの長さであるべきです。しかしjava6u23後に開始して、64ビットマシンは、ポインタ圧縮機能、4バイトの参照ポインタ長を開始します。したがって、オブジェクトヘッダ長は4 + 8 = 12であるべきです。
メンバー変数:基本などのint、long.byte、短い、ブール値、などの種類、ならびにそのような文字列、日付の基準となる基準の種類、を含む2つのカテゴリに分かれて。それが参照型である場合、それは現在のオブジェクトに尖った参照型のオブジェクトであるべきです。
アライメントパディング:JVM所定の、オブジェクトの大きさが不十分な場合、充填され、8バイトの整数倍でなければなりません。
さらに、アレイのため、フィールド長の配列が示されます。実際には、配列は、それは後で紹介するクラスもあります。
理論的基礎として、我々は一般的に使用されるオブジェクトは、スペースを占有計算します。
整数
図クラス構造は:、int型の唯一のプライベートなデータを見ることができます
したがって、整数の長さ:ヘッド(8 + 4)+ INT(4)= 16バイト
長いです
図のクラス構造。
整数、プライベートメンバーの唯一のロングタイプのように。
所以总长度为:头(8+4)+long(8)+padding(4)=24字节
Object
类结构图
没有成员变量,所以占用空间头(8+4)+padding(4)=16字节
String:“string”
类结构图
这个结构稍微有点复杂,涉及到了数组成员。其实数组也是一种类型,只不过这种类型是JVM在运行时生成的类型,并不在class文件中定义,我们将其当做一种特殊的类就可以了。既然涉及到了成员变量是对象,那么,我们就要把String分成两部分来计算:
String类型:头部(8+4)+int(4)+int(4)+指向char[]对象的引用类型(4)=24字节
char[]类型:数组类型比普通对象多一个标示数组长度的字段,占4个字节。对于字符串“String”来说,头部(8+4)+数组长度(4)+“String”(2*6)+padding(4)=32字节
因此,它的总占用空间为56字节
ArrayList
类结构图
其实,还有一个 modCount成员,继承自AbstractList类,那么对于一个 list = new ArrayList(); list.add(“String”);的list来说,它拥有两个int,一个大小为10的数组(当 list.add() 第一个元素的时候,它会初始化elementData为一个长度10的数组)
ArrayList: 头部(8+4)+int(4)+int(4)+数组引用(4)=24字节
elementData[] : 头部(8+4)+长度(4)+string引用(4*10)=56字节
"String"字符串:这个我们之前计算过了,为56字节
所以,总空间大小为24+56+56=136字节
HashMap
类结构图
HashMap内部结构比较复杂,除了一些基本的类型,还有比较复杂一点的集合类型。如table,是一个Entry数组,用来存放键值对,所有put进map中key-value都会被封装成一个entry放入到table中去。而还有一些辅助对象,如entry,继承自AbstractMap的keySet,values,这些都是在遍历map元素时用到的集合,他们的主要功能是通过在自己内部维护一个迭代器向外输出table中的数据,并不实际储存key-value数据。
以 Map<String,String> map = new HashMap<String,String>(); 这时候我们计算一下他的占用空间情况:
总空间为:48+16=64字节
hashmap:头部(8)+int(4*4)+float(4)+table数组引用(4)+entrySet引用(4)+keySet引用(4)+values引用(4)+padding(4)=48字节
table:头部(8+4)+长度(4)=16字节
然后我们put进去一条数据:map.put( “100002”, “张明”);
当HashMap初始化的时候,他会开辟一个长度为16的table数组,每当put一个新的key-value的时候,他会根据当前threshold来判断是否需要扩容,如果需要扩容,则会以倍数增长的方式扩容table数组。如16、32、64.具体原理请参考 http://blog.csdn.net/zq602316498/article/details/39351363
接下来让我们计算一下这个map多占用的空间
hashmap:头部(8)+int(4*4)+float(4)+table数组引用(4)+entrySet引用(4)+keySet引用(4)+values引用(4)+padding(4)=48字节
table: 80+32+16+16+56+48+0= 216字节
table:头部(8+4)+长度(4)+entry(4*16)=80字节
entry:头部(8+4)+k(4)+value(4)+next(4)+int(4)+padding(4)=32字节
key(String):56字节
value(String) :48字节
次の1つのだけの要素なので、次はnull、0、バイトので、
entrySet:バイトにnullポインタ0
keySet:NULLポインタ、バイト0
値:NULLポインタ、バイト0
要約すると、占有率は216 + 48 + 0 + 0 + 0 = 264のバイトをマップ
その後、我々はmap.keySet()メソッドをコールし続けて次のように、この時間は、キーセットはタイプのHashMap $キーセット、オブジェクトの構造のオブジェクトに与えられます。
見ることができ、それは、しかし、一連のツールを横断するために複雑ではありませんが、キーマップ
keySet:ヘッド(8 + 4)+パディング(4)= 16バイト
したがって、合計サイズは264 + 16 = 280バイトであります
その後、我々はmap.values()を動員し続け、そして上記と同様
値:ヘッド(8 + 4)+パディング(4)は、16バイト=
したがって、合計サイズは280 + 16 = 296バイトであります
その後、我々は()map.entrySetを呼び出すために続け、
entrySet:ヘッド(8 + 4)+パディング(4)= 16バイト
合計サイズは296 + 16 = 312バイトであるよう
参考記事:
https://cloud.tencent.com/developer/article/1441801