一、JAVA仮想マシンのメモリを深く理解する

8701 ワード

一、javaメモリ領域区分
1、プログラムカウンタ
プログラムカウンタは、現在のスレッドが実行するバイトコードの行番号インジケータと見なす小さなメモリ領域です.各スレッドには独自のプログラムカウンタがあるので、メモリ領域はスレッドプライベートです.
スレッドがJavaメソッドを実行している場合、このカウンタの値は実行中の仮想マシンバイトコード命令のアドレスである.Nativeメソッドが実行されている場合、このカウンタ値は空です.このメモリ領域はJava仮想マシン仕様でOutOfMemoryErrorが規定されていない唯一の領域です.
2、仮想マシンスタック
仮想マシンスタックはスレッドプライベートであり、そのライフサイクルはスレッドと同じです.仮想マシンスタックはJavaメソッドが実行するメモリモデルを記述します.各メソッドが実行すると、ローカル変数テーブル、オペランドスタック、ダイナミックリンク、メソッド出口などの情報を格納するスタックフレームが作成されます.各メソッドは、呼び出しから実行が完了するまでのプロセス、すなわち、スタックフレームが仮想マシン内でスタックからスタックを出るまでのプロセスである.
ローカル変数テーブルには、コンパイル期間で知られる様々な基本データ型(boolean、byte、char、short、int、float、long、double)、オブジェクト参照とreturnAddressタイプ(バイトコード命令のアドレスを指す).64ビット長のlongとdoubleタイプのデータは2つのローカル変数空間を占有する(slot)残りのデータ型は1つです.ローカル変数テーブルに必要なメモリ領域はコンパイル中に割り当てが完了し、1つのメソッドに入ると、このメソッドがフレームにどれだけのローカル変数空間を割り当てる必要があるかは完全に決定され、メソッドの実行中にローカル変数テーブルのサイズは変更されません.
スレッド要求スタックの深さが仮想マシンが許容する深さより大きい場合、StackOverflowError異常が放出されます.メモリ放出OutOfMemoryError異常を申請できません.
3、ローカルメソッドスタック
ローカルメソッドスタックと仮想マシンスタックが果たす役割は非常に似ており、それらの違いは、仮想マシンがjavaメソッドを実行する仮想マシンスタックにすぎず、ローカルスタックは仮想マシンが使用するNativeメソッドサービスである.
4、Javaスタック
Javaスタックはスレッド共有で、仮想マシンの起動時に作成されます.この領域の唯一の目的は、ほとんどのオブジェクトインスタンスがメモリを割り当てているオブジェクトインスタンスを格納することです.
Javaヒープはゴミ収集器管理の主なエリアであるため、「GCヒープ」と呼ばれることも多い.現在、コレクターは基本的に世代別収集アルゴリズムを採用しているため、Javaスタックでは新生代と古い年代に細分化することもできます.もう少し細かいのはEdenスペース、From Survivorスペース、To Survivorスペースなどがあります.
実装時には、固定サイズでも拡張可能でもよいが、現在主流の仮想マシンは拡張可能に実装されている(-Xmxおよび-Xms制御による).
スタック内にインスタンス割り当てが完了しておらず、スタックも拡張できない場合は、OutOfMemoryErrorが放出されます.
5、メソッド領域(永続世代)
スレッド共有は、仮想マシンにロードされたクラス情報、定数、静的変数、インスタントコンパイラによってコンパイルされたコードなどのデータを格納するために使用されます.論理的にはスタックの一部ですが、「非スタック」という名前があります.
この領域のメモリ回収の目標は主に定数プールの回収とタイプのアンロードです!
jdk 1で.7では、文字列定数プールが削除されました.
6、運行時定数プール
クラスのバージョン、フィールド、メソッド、インタフェースなどの記述情報のほかに、クラスのバージョン、メソッド、インタフェースなどの記述情報のほかに、コンパイル中に生成された様々な字面量と記号参照を格納する定数プールがあります.この部分は、クラスがロードされた後、エンドメソッド領域の実行時定数プールに格納されます.
二、対象探秘
1、オブジェクトの作成
仮想マシンがnew命令に遭遇した場合、まず、この命令のパラメータが定数プールでクラスのシンボル参照に位置決めできるかどうかを確認し、このシンボル参照が表すクラスがロード、解析、初期化されているかどうかを確認します.ない場合は、まず対応するクラス・ロード・プロシージャを実行する必要があります.次に、JVMは新しいオブジェクトにメモリを割り当てます.
Javaヒープが整っている(使用済みのメモリはすべて片側にあり、使用されていないものは反対側にあり、メンテナンスが面倒である)場合は、「ポインタ衝突」の配分方式が使用され、そうでない場合は「空きリスト」の配分方式が使用されます.Javaヒープが整っているかどうかは、採用されているゴミ収集器が圧縮整理機能を持っているかどうかによって決まります.
ただし、メモリの割り当ては同期されており、あるスレッドがオブジェクトメモリを割り当てたばかりで、ポインタが指す位置が変更されていない場合は、別のスレッドがオブジェクトを割り当てるときにエラーが発生する可能性があります.解決策は2つあります一つはメモリ領域を割り当てる動作を同期処理(CAS方式)することである.もう一つはメモリ割り当ての動作をスレッド別に異なる空間に分割して行うことであり、各スレッドはjavaスタックに小さなメモリを予め割り当て、ローカルスレッド割り当てバッファと呼ばれる(TLAB).TLABが切れて新しいTLABが割り当てられた場合のみ同期が必要です.JVMがTLAB機能をオンにするかどうかは、-XX:+/-UseTLABパラメータで設定できます.
メモリ割り当てが完了したら、ゼロ値(オブジェクトヘッダを除く)を初期化し、TLABを使用する場合は、この作業手順をTLAB割り当て時に繰り上げることもできます.
次に、JVMは、このオブジェクトがどのクラスのインスタンスであるか、クラスのメタデータ情報をどのように見つけるか、オブジェクトのハッシュコード、オブジェクトのGC世代別年齢などの情報など、オブジェクトに必要な設定を行う.これらの情報はオブジェクトのオブジェクトヘッダに格納され、JVMの現在の動作状態によって、ヨーロックが有効になっているかどうかなど、オブジェクトヘッダには異なる設定があります.
new命令を実行した後にメソッドを実行し,オブジェクトをプログラマの意思で初期化すると,1つのオブジェクトの初期化が完了する.
2、オブジェクトのメモリレイアウト
HotSpot仮想マシンでは、メモリに格納されているオブジェクトのレイアウトを、オブジェクトヘッダ、インスタンスデータ、整列塗りつぶしの3つの領域に分けることができます.
HotSpot仮想マシンのオブジェクトヘッダには2つの情報が含まれています.第1部は、オブジェクト自身のランタイムデータ(ハッシュコード、GC世代別年齢、ロックステータスフラグ、スレッドが持つロック、バイアススレッドID、バイアスタイムスタンプなどを格納するためのものであり、このデータの格納は公式にMark Wordと呼ばれている)であり、もう1部はタイプポインタである(オブジェクトがそのクラスメタデータを指すポインタであり、JVMはこのポインタによってそのオブジェクトがどのクラスのインスタンスであるかを決定する).
オブジェクトがJava配列である場合、オブジェクトヘッダには配列長を記録するためのデータが必要です.
第3の部分の整列充填は必然的に存在するわけではなく、特に意味もなく、プレースホルダの役割を果たしているだけです.HotSpot VMの自動メモリ管理システムでは、オブジェクトの先頭アドレスが8バイトの整数倍(オブジェクトのサイズは8バイトの整数倍)でなければならないため、8バイト未満の場合に占有される.
3、オブジェクトのアクセス位置
Javaプログラムはスタック上のReferenceデータによってスタック上の特定のオブジェクトを操作する必要がある.Referenceがオブジェクトにアクセスする方法は、ハンドルとダイレクトポインタの2つが主流です.
ハンドルアクセスを直接使用すると、javaスタックにはハンドルプールとしてメモリが分割され、referenceにはオブジェクトのハンドルアドレスが格納され、ハンドルにはオブジェクトデータとタイプデータのそれぞれの具体的なアドレス情報が含まれています.下図に示します.
ダイレクトポインタアクセスを使用する場合、javaスタックオブジェクトのレイアウトでは、アクセスタイプデータの配置方法に関する情報を考慮する必要があります.referenceに格納されているのは、次の図に示すように、オブジェクトアドレスです.
違い:
ハンドルを使用してアクセスする最大の利点は、referenceに格納されている安定したハンドルアドレスであり、オブジェクトが移動されるとハンドル内のインスタンスデータポインタのみが変更され、reference自体は変更する必要はありません.
ダイレクトポインタアクセス方式を使用する最大の利点は、ポインタの位置決めにかかる時間のオーバーヘッドを節約する高速化です.
HotSpot仮想マシンは直接ポインタアクセス方式を使用しています.
三、OutOfMemoryError異常
1、javaスタック異常
/**
 * VM Args: -Xms20m      
 *          -Xmx20m      
 *          -XX:+HeapDumpOnOutOfMemoryError                ,Dump    
 *        
 */
public class HeapOOM {

    public static void main(String[] args) {

        List list = new ArrayList<>();
        while(true){
            list.add(new OOMObject());
        }
    }
}

    :
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid4400.hprof ...
Heap dump file created [28248158 bytes in 0.207 secs]

2、仮想マシンスタックとローカル方法スタックオーバーフロー
HotSpot仮想マシンでは仮想マシンスタックとローカルメソッドスタックが区別されないため、HotSpotでは-Xossパラメータ(ローカルメソッドスタックサイズの設定)が存在するが、実際には効果がなく、スタック容量は-Xssパラメータのみで設定される.
仮想マシンスタックとローカルメソッドスタックについては、Java仮想マシン仕様で2つの例外が説明されています.
  • スレッド要求スタックの深さが仮想マシンによって許容される最大深さより大きい場合、StackOverflowError例外が放出されます.
  • 仮想マシンがスタックを拡張するときに十分なメモリ領域を申請できない場合、OutOfMemoryError異常が放出されます.

  • この2つの異常は実はいくつかの重なり合う場所が存在している.単一スレッドでは、スタックフレームが大きすぎても仮想マシンスタック容量が小さすぎても、メモリが割り当てられない場合、仮想マシンが投げ出すのはStackOverflowError異常です.テスト時に単一スレッドに限定されない場合は、スレッドを絶えず確立することでメモリオーバーフロー異常を発生させることができます.
    /**
     * VM Args: -Xss128k    
     */
    public class JavaVMStack {
    
        private int count;
        public void stackLeak(){
            count++;
            stackLeak();
        }
        public static void main(String[] args) {
            JavaVMStack javaVMStack = new JavaVMStack();
            try {
                javaVMStack.stackLeak();
            } catch (Throwable e) {
                System.out.println(javaVMStack.count);
                e.printStackTrace();
            }
        }
    }
        
    983
    java.lang.StackOverflowError
        at com.bw.oom.test.JavaVMStack.stackLeak(JavaVMStack.java:10)
        at com.bw.oom.test.JavaVMStack.stackLeak(JavaVMStack.java:11)

    3、方法エリアと運行時定数プールオーバーフロー
    メソッド領域はクラス名、アクセス修飾子、定数プール、フィールド記述、メソッド記述など、Classに関する情報を格納するために使用されます.
    CGLibを使用してバイトコードの実行を直接操作すると、大量のダイナミッククラスが生成され、メソッド領域をオーバーフローするまで満たすことができます.
    public class JavaMethodAreaOOM {
        public static void main(final String[] args) {
            while(true) {
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(OOMObject.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects,
                                        MethodProxy methodProxy) throws Throwable {
                        return methodProxy.invokeSuper(o, args);
                    }
                });
                enhancer.create();
            }
        } 
    }
        :
    Caused by: java.lang.OutOfMemoryError: PermGen space