jvmmemorymodel
この記事では、主にJVM仕様で説明されているランタイムデータ領域(Runtimedataareas)を紹介します。これらの領域は、JVM自体が使用するデータまたはJVMで実行されているプログラムを保存するように設計されています。
まずJVMの概要を概要してから、ByteCodeを紹介し、最後にさまざまなデータ領域を導入しましょう。
概要
オペレーティングシステムの抽象化として、JVMは同じコードが異なるハードウェアまたはオペレーティングシステムで一貫して動作することを保証します。
例えば:
Basic Type intの場合、16ビット/32ビット/64ビットオペレーティングシステムに関係なく、32ビットの署名整数です。 -2^31から2^31-1の範囲
オペレーティングシステムまたはハードウェアが大規模なバイトの順序であるか小さいかに関係なく、JVMが保存および使用するメモリ内のデータが大規模または小さなバイトの順序であることが保証されます(最初にハイビットバイトを読んでください)
JVMの実装が異なる場合がありますが、一般的に同じです。
上記の写真は、JVMの概要です
JVMは、コンパイラ生成バイトコードを解釈します。 JVMはJava仮想マシンの略語ですが、Bytecodeにコンパイルできる言語である限り、Scala、Groovy <� "/kf/ware/vc/"などのJVMに基づいて実行できますターゲット= "_ blank"> vcd4ncjxwps6qwcux3mpixrw3sbxetmxfzekvt6os192slru+gxu2nsyxnzbg9hzgvyvnpu2lkiu7q05r w91mvq0mqxyv2+3cf41tc1xnk7upbh+npyo6zwqrxavnpu2mv8tcrjbgfzc2xvywrlcrg7z/q72bvy1d9kvk3no9a51mvq0kgjpc 9WPG0KPHA+VNPU2LXE19A92SLRZAI5/DA00NDS/CFMKGV4ZWN1DGLVBIBLBMDPBMUPVFJQ0L3IYS26ZDA00NA8L3A+DQO8CD7WT NDQ0V3H5TDO0QQ05RSIS8ZQ8SNPZ8LOXKOSSCJI57PM0PLWTNDQTB3EXNK70NCJRLVY1D/K/K/B7DVMBL47XE1TC85L3HUFS8L3A+D QO8CD7WTNDQ0V3H5TKYULRU8LSMWO3T67XXSUOY2DF3Z7XNS7XEVBU7PTWVCD4NCJXWPIOQUTY24EPWTBA8yRXP1SHLVLTKSBHG 0UU5PSTCKEPJVD1QDXN0IGLUIHRPBWUPOANKSVS+ZCRHSNG+RBOJ1RTQ0LXETPRC6YJIYILXJTPRC6IMX4NLRS8MXVRXYTPRC6YHO YXRPDMUGQ29KZSMHO7TMT8VKSVSX4NLRYFQZYBT6WUO1XMF40/KZXS6QPC9WPG0KPHA+TPRC67U6TOBH+CHDB2RLIENH Y2GPOAO8TMQXSEDS67Y8YVUOSKLUKBY2TPO1XMZHUN/BY0PWTBXE0NTE3COQ9WPG0KPGGYIGLKPQ == "スタックベースアーキテクチャ ">スタックベースのアーキテクチャ
JVMは、スタックベースのアーキテクチャを使用します。スタックは開発者に対して透明ですが、生成されたバイトコードとJVMに非常に重要な役割または影響があります。
私たちが開発したプログラムは、低レベルの操作を変換し、それらをBytecodeに保存します。 JVMのオペランドを介して操作手順にマップします。 JVM仕様によると、操作命令で必要なパラメーターは、オペランドスタックから取得されます。
2つの数字を追加する例を示しましょう。この操作はIADDと呼ばれます。以下は、Bytecodeの3+4のプロセスです
最初に3と4をオペランドスタックに押し込みます
IADD指令を呼び出します
IADD命令は、オペランドスタックの上部から2つの数字を表示します
3+4の結果は、後で使用するためにオペランドスタックに押し込まれます
このアプローチは、スタックベースのアーキテクチャと呼ばれます。レジスタベースのアーキテクチャなど、低レベルの操作を処理する他の方法があります。
bytecode
Java Bytecodeは、Javaソースコードを一連の低レベル操作に変換した結果です。各操作は、ゼロ以上のバイト長パラメーターを持つオペコードまたは操作コードで構成されています(ただし、ほとんどの操作はオペランドスタックで取得されたパラメーターを使用します)。 1つのバイトは、0x00〜0xffの256の数値を表すことができ、現在Java8には合計204が使用されています。
以下には、さまざまなタイプのバイトコードオペコードとその範囲と簡単な説明をリストしています
定数:定数プールの値または既知の値をオペランドスタックに押し込みます。 0x00-0x14
負荷:ローカル変数値をオペランドスタックに押し込みます。 0x15-0x35
ストア:オペランドスタックからローカル変数への値のロード0x36-0x56
スタック:プロセスオペランドスタック0x57-0x5f
数学:基本的な数学計算のためにオペランドスタックから値を取得する0x60-0x84
変換:タイプ0x85-0x 93間の変換
comaprisons:2つの値の比較操作0x94-0xa6
コントロール:goto、return、loopなどを実行します。コントロール操作0xa7 -0xb1
参照:割り当てオブジェクトまたは配列を実行し、オブジェクト、メソッド、および静的メソッドへの参照を取得またはチェックします。静的方法も呼び出すことができます。 0xb2 -oxc3
拡張:拡張:後に追加された他のカテゴリからの操作。値0xc4から0xc9まで
(この文はどういう意味ですか?。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
予約済み:JVM実装では、スロット0xca、oxfe、oxffを使用します
これらの204操作は非常に簡単です、いくつかの例を挙げてください
IFEQ(0x99)は、2つの値が等しいかどうかを決定します
IADD(0x60)は2つの数値を追加します
I2L(0x85)は、int int nogを変換します
arrayLength(0xbe)は配列の長さを返します
ポップ(0x57)オペランドスタックの上部から値をポップします
ByteCodeファイルを作成するコンパイラが必要で、標準のJavaコンパイラはJDKのJavacです。
public class test {public static void main(string [] args){int a = 1; int b = 15; int result = add(a、b); } public static int add(int a、int b){int result = a + b;返品結果; }}「test.class」のbytecodeファイルを「Javac test.java」を介して取得できます。 bytecodeファイルはバイナリであり、javapを介してバイナリバイトコードファイルをテキストフォームに変換できます
Java -verbose test.class
classfile /c:/tmp/test.class last modified 1 avr。 2015;サイズ367バイトMD5チェックサムADB9FF75F12FC6CE1CDDE222A9C4C7426パブリッククラスcom.codinggeek.jvm.test sourcefileから編集:「test.java "マイナーバージョン:0メジャーバージョン:51フラグ:#15#15 Java/Lang/Object。 "<init>" :()V#2 = MethodRef#3。#16 // com/codinggeek/jvm/test.add:(ii)i#3 = class#17 // com/codinggeek/jvm/test#4 = class#18 // java/lang/object#5 = utf8 <init#6 = utf8() Linenumbertable#9 = utf8 Main#10 = utf8([ljava/lang/string;)v#11 = utf8 add#12 = utf8(ii)i#13 = utf8 sourcefile#14 = utf8 test.java#15 = nameandtype#5:#6/" #17 = utf8 com/codinggeek/jvm/test#18 = utf8 java/lang/object {public com.codinggeek.jvm.test();フラグ:acc_publicコード:stack = 1、locals = 1、args_size = 1 0:aload_0 1:invokespecial#1 // method java/lang/object。 "<init>" :()v 4:return linenumbertable:return 3:0 public static void main(java.lang.string []);フラグ:acc_public、acc_static code:stack = 2、locals = 4、args_size = 1 0:iconst_1 1:bipush 15 4:istore_2 5:iload_1 6:iload_2 7:invokestatic#2 //方法:(II)I 10:ISTORE_3:0 ISTORE_3:0 ISTORE_3 11: 8:5行9:11 public static int add(int、int);フラグ:acc_public、acc_static code:stack = 2、locals = 3、args_size = 2 0:iload_0 1:iload_1 2:iadd 3:istore_2 4:iload_2 5:iReturn linenumbertable:行12:0行13:4}}}BytecodeはJavaコードの単なる翻訳ではなく、以下が含まれていることがわかります。
クラスの一定のプールの説明。定数プールは、クラス内のメソッド名、パラメーターリストなど、クラスメタデータを保存するために使用されるJVMデータ領域です。 JVMがクラスをロードすると、メタデータは定数プールにロードされます
行番号テーブルとローカル変数テーブルを介して、バイトコードの関数とtmall変数の特定の位置情報を提供します。
Javaコードの翻訳(隠された親クラス構成を含む)
オペランドスタックでより具体的な操作を提供し、パラメーターを通過および取得するためのより完全な方法を提供します
以下は、bytecodeファイルストレージ情報の簡単な説明です
classfile {u4 Magic; u2 minor_version; u2 major_version; u2 curntion_pool_count; cp_info curntion_pool [curntion_pool_count-1]; U2 Access_Flags; u2 this_class; u2 super_class; u2 interfaces_count; U2インターフェイス[interfaces_count]; u2 fields_count; field_info fields [fields_count]; u2 astributes_count;属性_INFO属性[attributes_count];}ランタイムデータエリア
ランタイムデータ領域は、データを保存するために設計されたメモリ領域です。このデータは、開発者またはJVMが内部で使用するためです。
ヒープ
ヒープは、JVMが開始されたときに作成され、すべてのJVMスレッドで共有されます。すべてのクラスインスタンスと配列は、ヒープ(新しい)に割り当てられます。
ヒープは、開発者によって作成されたオブジェクトのリリースを担当するゴミコレクターによって管理される必要があり、再び使用されません。
ゴミ収集戦略に関しては、JVMの実装によって決定されます(たとえば、Hotspotは複数のアルゴリズムを提供します)。
ヒープメモリには最大の制限があります。この値を超えると、JVMはOutOfMemroy例外をスローします。
メソッド領域
メソッド領域は、JVMのすべてのスレッドによっても共有されます。 JVMスタートアップでも同じことが作成されます。メソッド領域に保存されているデータは、クラスロードのロードが破壊されたりJVMが停止したりしない限り、アプリケーションの実行中に一貫して存在するクラスローダーによってバイトコードからロードされます。
メソッド領域には、次のデータが保存されます。
クラス情報(属性名、メソッド名、親クラス名、言い訳、バージョンなど)
メソッドと構築されたバイトコード
各クラスをロードするときに作成されたランタイム定数プール
JVM仕様は、メソッド領域をヒープに実装することを強制しません。 Java7の前に、HotspotはPermgenと呼ばれる領域を使用してメソッドゾーンを実装しました。永久バンドはヒープに隣接しています(メモリ管理はヒープと同じです)、デフォルトビットは64MBです
Java8から始めて、HPTSPOTは別のローカルメモリを使用してメソッド領域を実装し、メタデータ領域(Metaspace)に名前を付けます。メタデータエリアで利用可能な最大スペースは、システム全体の使用可能なメモリです。
メソッドが利用可能なメモリを適用できない場合、JVMはfotmemoryerrorを捨てます。
ランタイム定数プール
ランタイム定数プールはメソッド領域の一部です。メタデータに一定のプールを実行することの重要性があるため、メソッド領域の外側のJava仕様で個別に説明されています。ランタイム定数プールは、ロードされたクラスとインターフェイスとともに成長します。
一定のプールは、従来の言語では少しの構文テーブルです。言い換えれば、クラス、方法、またはプロパティが呼び出されると、JVMはランタイム定数プールを介してメモリ内のこのデータの実際のアドレスを検索します。ランタイム定数プールには、文字列リテラルまたはプリミティブタイプの定数も含まれています
myString stirng mystring = "これは文字列散布" static final int my_constant = 2;
PC(プログラムカウンター)レジスタ(スレッドごと)PCレジスタ(スレッドごと)
各スレッドには、スレッド作成とともに作成された独自のPC(プログラムカウンター)レジスタがあります。各スレッドは、スレッドの現在のメソッドと呼ばれる時点で1つのメソッドのみを実行できます。 PCレジスタには、現在命令を実行しているJVMのアドレスが含まれています(メソッド領域)。
現在実行されている方法がローカルメソッドである場合、PCレジスタの値は未定義です
スレッドあたりの仮想マシンスタック-Java-Virtual-Machine-Stacks-er-Thread ">仮想マシンスタック(スレッドごと)Java仮想マシンスタック(スレッドごと)
仮想マシンスタックには複数のフレームが保存されるため、スタックを説明する前に、最初にフレームを見てみましょう。
フレーム
フレームは、スレッドが実行されている現在のメソッドを表す複数のデータを含むデータ構造です。
オペランドスタック:前述のように、bytecode命令はオペランドスタックを使用してパラメーターを渡す
ローカル変数配列:この配列には、現在実行されているメソッドの範囲内にすべてのローカル変数が含まれています。この配列には、プリミティブタイプ、参照、または返信アドレスを含めることができます。ローカル変数配列のサイズは、コンパイル時に決定されます。 JVMは、メソッドを呼び出すときにローカル変数を使用してパラメーターを渡し、呼び出し方のローカル変数配列が呼び出し方法のオペランドスタックを介して作成されます。
ランタイム定数プール参照:現在のクラスの現在の方法の定数プールへの参照。 JVMは、一定のプール参照を使用して、信号を実際のメモリ参照に合格します。
スタック(スタック)
各JVMスレッドには、スレッドと同時に作成されるプライベートJVMスタックがあります。 Java Virtual Machine Stackはフレームを保存します。メソッドが呼び出されるたびに、フレームが作成され、仮想マシンスタックにプッシュされます。この方法が実行されると、フレームも破壊されます(メソッドが正常に実行されるか、例外がスローされているかに関係なく)
スレッドの実行中に使用可能なフレームは1つだけです。このフレームは現在のフレームと呼ばれます。
ローカル変数とオペランドスタックの操作には、通常、現在のフレームへの参照が伴います。
追加の別の例を見てみましょう
public int add(int a、int b){return a + b;} public void functiona(){// function offunction call call int result = add(2,3); //関数b //関数のないいくつかのコード呼び出し}メソッドAの内部では、フレームAは仮想マシンスタックの上部にある現在のフレームです。 ADDメソッドを呼び出す開始時に、新しいフレームBが作成され、仮想マシンスタックに押し込まれます。フレームBは新しい電流フレームになります。
フレームBのローカル変数配列は、フレームAのオペランドスタックのデータで満たされています。ADDメソッドが終了すると、フレームBが破壊され、フレームAが現在のフレームとして再確立されます。 ADDメソッドの結果は、フレームAのオペランドスタックに押し込まれているため、この方法AはフレームAのオペランドスタックを介してADDの結果を取得できます。
要約します
上記は、Java Virtual Machine Runtimeのデータ領域分析に関するすべてです。私はそれが誰にでも役立つことを願っています。
欠点がある場合は、それを指摘するためにメッセージを残してください。