Baidu Danielは、Javaの新しいオブジェクトでのインタビューに必要なスキルを教えてくれます...

この記事では主にインタビュアーを紹介します。JavaのnewObject()は最後に数バイトを占めます。この記事では、すべての人の研究や仕事のための特定の参照値を持つ詳細を紹介します。それを必要とする友人はそれを参照できます。

ヒープのレイアウトとメモリ内のJavaオブジェクトのレイアウトを分析してみましょう。

オブジェクトを指す*
最初にコードの一部を見てください。

package com.zwx.jvm;
 
public class HeapMemory {
    
    
  private Object obj1 = new Object();
 
  public static void main(String[] args) {
    
    
    Object obj2 = new Object();
  }
}

上記のコードでは、メモリ内のobj1とobj2の違いは何ですか?

JVMシリーズ1の記事で、メソッド領域には、ランタイム定数プール、属性、メソッドデータなどの各クラスの構造と、メソッドやコンストラクターなどのデータが格納されることが記載されていることを思い出してください。したがって、obj1にはメソッド領域があり、newはヒープに格納されるオブジェクトインスタンスを作成するため、次の図があります(メソッド領域はヒープを指します)。

ここに画像の説明を挿入

Obj2はメソッド内のローカル変数であり、Java仮想マシンスタックのスタックフレームのローカル変数テーブルに格納されます。これは、ヒープへの従来のスタックポインタです。
ここに画像の説明を挿入

ここでもう一度考えてみると、ヒープを指す変数があり、インスタンスオブジェクトのみがヒープに格納されています。次に、ヒープ内のサンプルオブジェクトは、どのクラスに属しているかをどのように認識しますか。このインスタンスは対応するクラスを知っていますか?メタ情報はどうですか?これには、Javaオブジェクトがメモリ内にどのように配置されるかが含まれます。

Javaメモリモデル

オブジェクトメモリは、オブジェクトヘッダー(ヘッダー)、インスタンスデータ(インスタンスデータ)、配置パディング(パディング)の3つの領域に分割できます。64ビットオペレーティングシステムを例にとると(ポインター圧縮がオンになっていない場合)、次の図に、Javaオブジェクトのレイアウトを示します。

ここに画像の説明を挿入

オブジェクトヘッダーのマークワードの詳細情報は、「同期ロックアップグレードの原則」の記事で詳しく説明されています。上の図の配置パディングは必ずしもそこにあるとは限りません。オブジェクトヘッダーとインスタンスデータの合計が8バイトの倍数になる場合、配置パディングは必要ありません。
Javaのメモリレイアウトを知ってから、インタビューの質問を見てみましょう

Object obj=new Object()占用字节

これはインターネット上の多くの人が言及する問題です。次に、上記のJavaメモリレイアウトと組み合わせて分析します。64ビットオペレーティングシステムを例にとると、新しいObject()の占有サイズは次の2つの状況に分けられます。

-ポインター圧縮が有効になっておらず、占有サイズは次のとおりです。8(マークワード)+ 8(クラスポインター)= 16バイト
-ポインター圧縮が有効(デフォルトが有効)ポインター圧縮が有効になった後、クラスポインターは4バイトに圧縮されます。最終的なサイズは次のとおりです。8(マークワード)+ 4(クラスポインタ)+ 4(整列されたパディング)= 16バイト。
これは結果ですか?確認しましょう。最初にpom依存関係を導入します。

<dependency>
      <groupId>org.openjdk.jol</groupId>
      <artifactId>jol-core</artifactId>
      <version>0.10</version>
    </dependency>
   

次に、簡単なデモを作成します。

package com.zwx.jvm;
 
import org.openjdk.jol.info.ClassLayout;
 
public class HeapMemory {
    
    
  public static void main(String[] args) {
    
    
    Object obj = new Object();
    System.out.println(ClassLayout.parseInstance(obj).toPrintable());
  }
}

出力は次のとおりです。

最終結果は16バイトで問題ありません。これは、ポインタ圧縮がデフォルトでオンになっているためです。ポインタ圧縮をオフにしてから、もう一度試してみましょう。

-XX:+ UseCompressedOopsはポインター圧縮をオンにします
-XX:-UseCompressedOopsはポインター圧縮をオフにします

ここに画像の説明を挿入

もう一度実行すると、次の結果が得られます。
ここに画像の説明を挿入

ご覧のとおり、現時点では位置合わせとパディングの部分はありませんが、占有サイズは16ビットのままです。

以下の属性を持つオブジェクトのサイズを示しましょう。

内部に1バイト属性のみを持つ新しいクラスを作成します。

package com.zwx.jvm;
 
public class MyItem {
    
    
  byte i = 0;
}

次に、ポインタ圧縮をオンにするシーンとポインタ圧縮をオフにするシーンで、このクラスのサイズをそれぞれ出力します。

package com.zwx.jvm;
 
import org.openjdk.jol.info.ClassLayout;
 
public class HeapMemory {
    
    
  public static void main(String[] args) {
    
    
    MyItem myItem = new MyItem();
    System.out.println(ClassLayout.parseInstance(myItem).toPrintable());
  }
}

16バイトを占めるポインタ圧縮をオンにします。
ここに画像の説明を挿入

24バイトを占めるポインタ圧縮をオフにします。
ここに画像の説明を挿入

このとき、ポインタ圧縮の利点が有効になっていることがわかります。多数のオブジェクトが連続して作成されている場合でも、ポインタ圧縮によってパフォーマンスがある程度最適化されます。

オブジェクトへのアクセスオブジェクトを
作成したは、もちろんそれにアクセスする必要があります。では、オブジェクトにアクセスする必要がある場合、どのようにオブジェクトを見つけるのでしょうか。現在、オブジェクトにアクセスするには、主に2つの方法があります。ハンドルアクセスと直接ポインタアクセスです。

ハンドルアクセスはハンドルアクセスを使用し、Java仮想マシンはヒープ内のメモリを分割してハンドルプールを格納し、次にハンドルアドレスをオブジェクトに格納し、次にオブジェクトインスタンスデータとオブジェクトタイプデータアドレスをハンドルプールに格納します。
ここに画像の説明を挿入

直接ポインタアクセス(ホットスポット仮想マシンで使用される方法)直接ポインタアクセスは、オブジェクトタイプデータをオブジェクトに直接格納します。

ここに画像の説明を挿入

ハンドルアクセスと直接ポインタアクセスの
比較上図では、簡単に比較できます。つまり、ハンドルアクセスを使用すると、ポインタの位置がもう1つ増えますが、オブジェクトを移動すると(アドレスが変更された場合)、ハンドルプールのポインタを変更するだけで、参照オブジェクトのポインタを変更する必要はありません。直接ポインタアクセスを使用する場合は、で参照ポインタも変更する必要があります。ローカル変数テーブル。

ヒープメモリ
前述のように、Javaオブジェクトヘッダーのマークワードはオブジェクトの世代年齢を格納します。世代年齢とは何ですか?

オブジェクトの世代年齢は、ガベージコレクションの数として理解できます。ガベージコレクション後もオブジェクトが存在する場合、世代年齢は1増加します。64ビット仮想マシンでは、世代年齢は4つの場所を占めます。最大値は15です。デフォルトの生成年齢は0000ですが、ガベージコレクションの数とともに徐々に増加します。

Javaヒープメモリは、世代の年齢に応じて若い領域と古い領域に分割されます。オブジェクトの割り当ては、最初に若い領域に移動し、特定の世代の年齢に達します(-XX:MaxTenuringThresholdはサイズを設定でき、デフォルトは15です)。古い領域(注:オブジェクトが大きすぎる場合、オブジェクトは直接古い領域に入ります)。

この分割の理由は、ヒープ全体に1つの領域しかない場合、ガベージコレクション中にヒープ内のすべてのオブジェクトを毎回スキャンする必要があるためです。これはパフォーマンスの無駄です。実際、ほとんどのJavaオブジェクトのライフサイクルは非常に短いです。オブジェクトを何度もリサイクルできなくなると、次のガベージコレクションではリサイクルできない可能性があると考えられるため、ヤングエリアとオールドエリアのガベージコレクションは次のようになります。これとは別に、ガベージコレクション後にヤングエリアにまだスペースがない場合にのみ、オールドエリアのガベージコレクションがトリガーされます。

ここに画像の説明を挿入

ヤングエリア
ヤングエリアに分割されました。次のシーンを見てみましょう。以下のヤングエリアは、ガベージコレクション後の全体像です。

ここに画像の説明を挿入

オブジェクトが今来て、2つのオブジェクトのサイズを占めると言うと、それを置くことができないことがわかります。このとき、GC(ガベージコレクション)がトリガーされますが、GC(ガベージコレクション)がトリガーされます。 )がトリガーされると、ユーザースレッドに影響します。はい。GCプロセスでは、オブジェクト参照が絶えず変更されないようにするために、すべてのユーザースレッドを停止する必要があります。Sunはこのイベントを呼び出します:Stop the World(STW )。これらについては、ガベージコレクションに関する次の記事で詳しく説明するので、ここでは詳しく説明しません。

したがって、一般的には、GCが少ないほど良いです。実際、上の図では少なくとも3つのオブジェクトを配置できることがわかります。オブジェクトが順番に配置されている限り、オブジェクトを配置できるので、これが結果です。 。問題は、明らかにスペースがあることですが、スペースが連続していないため、オブジェクトはメモリの適用に失敗し、GCがトリガーされます。この問題を解決するにはどうすればよいですか?

解決策は、ヤングエリアにオブジェクトを順番に配置することであるため、ヤングエリアを再びエデンエリアとサバイバーエリアの2つのエリアに分割する方法が開発されました。

ここに画像の説明を挿入

具体的な操作は次のとおりです。オブジェクトが到着すると、オブジェクトはエデンエリアに割り当てられます。エデンエリアがいっぱいになると、GCがトリガーされます。GCの後、スペースの不連続性を防ぐために、存続するオブジェクトをサバイバーエリアにコピーします。もちろん、完全にクリーンアップするための前提条件があります。つまり、ほとんどのオブジェクトのライフサイクルは非常に短く、エデンエリアのほとんどのオブジェクトは次の方法でリサイクルできます。基本的なガベージコレクション(この前提はテストを通じて要約されます)。

GCがトリガーされると、サバイバーエリアもリサイクルされます。エデンエリアだけがトリガーされるわけではありませんが、この問題が再び発生します。エデンエリアは、スペースが基本的に連続していることを保証しますが、サバイバーエリアが生成する可能性があります。スペースが断片化し、不連続性が生じたため、サバイバーエリアを再び2つに分割しました。

ここに画像の説明を挿入

このとき、ワークフローは再び次のようになります。まず、エデン領域にスペースを割り当てます。エデン領域がいっぱいになると、GCがトリガーされます。GCの後、残っているオブジェクトがS0領域(S1領域)にコピーされます。が空)の場合、オブジェクトはEden領域に割り当てられます。、GCを再度トリガーした後、S0領域が収まらないことが判明した場合(スペースの断片化、実際にはスペースがあります)、S0領域オブジェクトをにコピーします。 S1エリア、残りのオブジェクトをS1エリアにコピーします。このとき、S0エリアは空です。はい、順番に操作を繰り返します。コピーして移動した後、S0またはS1エリアのスペースオブジェクトを置くことができない場合は、それは、この時点で本当に満員であることを意味し、古いエリアに十分でない場合は、高齢者エリアに行ってスペースを借ります(これは保証メカニズムです。老人はこの種のスペース割り当て保証を提供する必要があります)。フルGCがトリガーされますが、それでも十分でない場合は、OutOfMemeoyError例外がスローされます。

注:2つの領域S0とS1の間で各コピーがスムーズに進行するようにするには、2つの領域S0とS1のサイズが同じであり、1つの領域が同時に空である必要があります。このアプローチではスペースが少し無駄になりますが、他のパフォーマンスの改善を統合することは価値があります。

古いエリア
若いエリアのオブジェクトが設定された世代年齢に達すると、オブジェクトは古いエリアに入ります。古いエリアがいっぱいになると、フルGCがトリガーされます。それでもスペースをクリーンアップできない場合は、OutOfMemeoyErrorがスローされます。 。

名詞リテラシー

上記では多くの新しい名詞が言及されていますが、実際にはこれらの名詞の多くは他の名前を持っています。それでもこれを理解する必要があると思います。

ガベージコレクション:GCと呼ばれます。

マイナーGC:新世代のGC

メジャーGC:古い年齢のGCの場合、通常、古い年齢はGCをトリガーし、マイナーGCもトリガーします。これはフルGCをトリガーするのと同じです。

フルGC:GCは、新世代と旧世代で同時に発生します。

若い地区:新世代

旧市街:老後

エデン地区:中国語の翻訳はありません(エデンガーデン?)

サバイバーディストリクト:サバイバルディストリクト

S0とS1:fromエリアとtoエリアとも呼ばれます。fromとtoの2つのエリアは常にIDを変更しており、S0とS1は等しくなければならず、エリアは空であることが保証されていることに注意してください。

オブジェクトの寿命の軌跡図

上記の紹介から、エデンエリア、S0エリア、S1エリア、オールドエリア(もちろん、最初にリサイクルされる短命のオブジェクトを除く)でオブジェクトが循環し続けるという一般的な印象を誰もが持っているはずです。 、次の1つのフローチャートを取得できます。
ここに画像の説明を挿入

この記事では、主にJavaオブジェクトがヒープに格納される方法を紹介し、Javaオブジェクトのメモリレイアウトを組み合わせて、共通オブジェクトのサイズを示します。次に、ヒープ内のスペース分割と分割の理由についても分析します。記事にはGC関連が含まれます知識は詳細に説明されていません。GCおよびGCアルゴリズムとGCコレクターに関する関連知識は次の記事で詳細に分析されます。

これまでのところ、インタビュアーに関するこの記事:Javaの新しいObject()は数バイトを占めます。関連するJavaの新しいObject()バイトについては、エディターに従うか、QQ1411943564を追加してください。今後さらにサポートされることを願っています。エディター!!

おすすめ

転載: blog.csdn.net/dcj19980805/article/details/115242323