ホームページ  >  記事  >  Java  >  Java のメモリ割り当てについての深い理解 (画像とテキスト)

Java のメモリ割り当てについての深い理解 (画像とテキスト)

黄舟
黄舟オリジナル
2017-03-27 10:50:501550ブラウズ

この記事は主に、Java メモリ割り当てを深く理解するための関連情報を紹介します。必要な友人は参照してください

Java メモリ割り当ての詳細な理解

この記事では、Java メモリの原理を紹介します。初心者が Java を簡単に学習できるように、浅いレベルから深いレベルまで詳細に割り当てられています。このような記事はオンライン上にたくさんありますが、そのほとんどは断片的です。この記事では、認知プロセスの観点から体系的に紹介します。

本題に入る前にまず知っておくべきことは、Java プログラムは JVM (Java 仮想マシン、Java 仮想マシン) 上で実行され、JVM はプラットフォームの独立性を実現するものとして理解できるということです。これは、JVM の重要性を示しています。したがって、Java メモリ割り当ての原理を学ぶときは、すべてが JVM で行われることを覚えておく必要があります。JVM はメモリ割り当て原理の基礎であり前提です。 , 簡単に言えば、完全な Java プログラムの実行プロセスには次のメモリ領域が含まれます:

L レジスタ:

JVM 内部仮想レジスタ、アクセス速度は非常に高速であり、プログラムを制御することはできません。

L スタック: 存 以下を含むローカル 変数 の値を保存します。 1. 基本データ型の値 2. Cricket インスタンス

、つまりヒープ領域の参照 (ポインター) を保存します

(ポインタ)。メソッドをロードするときにフレームを保存するためにも使用できます。 ヒープ: 新しい オブジェクト など、動的に生成されたデータを保存するために使用されます。作成されたオブジェクトにはそれぞれのメンバー変数のみが含まれ、メンバー メソッドは含まれないことに注意してください。同じクラスのオブジェクトは独自のメンバー変数を持ち、独自のヒープに格納されますが、クラスのメソッドを共有するため、オブジェクトが作成されるたびにメンバー メソッドがコピーされるわけではありません。

1

定数プール: JVM は、ロードされた型ごとに定数プールを維持します。定数プールは、この型で使用される定数の順序付けされたコレクションです。直接定数 (基本型、String) と、他の型、メソッド、フィールドへのシンボル参照 (1) が含まれます。プール内のデータは、配列と同様にインデックスを介してアクセスされます。定数プールには、ある型から他の型、メソッド、およびフィールドへのすべてのシンボリック参照が含まれるため、定数プールは Java の動的リンクにおいて中心的な役割を果たします。 定数プールはヒープ内に存在します

コードセグメント: ソースプログラムコードを保存するために使用されます。ハードディスクから読み取ります。 with with は、static

で定義された

static メンバーを格納するために使用されます。

以下はメモリ表現図です:

次に、上の図は、Java プログラムがメモリ内でどのように実行されるかを例を使って詳しく説明します (注:以下の図は、Shangxuetang の Ma Bingbing 先生の J2SE コースウェアから引用しました。図の右側はプログラム コード、左側はメモリ割り当て図です。順次コメントを追加します。

予備知識:

Java のメモリ割り当てについての深い理解 (画像とテキスト) 1.

Java ファイルは、

main

entry メソッドを持つ限り、Java プログラムとみなされ、独立してコンパイルおよび実行できます。

2.

通常型変数でも参照型変数(通称インスタンス)でもローカル変数として使用でき、全てスタック上に出現することができます。通常の型変数は対応する値をスタックに直接格納しますが、参照型変数はヒープ領域へのポインタを格納し、このポインタを通じてヒープ領域内のこのインスタンスに対応するオブジェクトを見つけることができます。したがって、通常型変数はスタック領域のみを占有しますが、参照型変数はスタック領域とヒープ領域のメモリを占有します。

例:


ヒープオブジェクト 110925 へのポインター。

2. 一 int型変数Dateを作成します。基本型なので、対応する値9をスタックに直接格納します。 3. 2 つの BIRTHDATE クラス D1 および D2 のインスタンスを作成します。対応するポインターは、それぞれのオブジェクトを指すようにスタックに格納されます。インスタンス化時にパラメータを指定してコンストラクターを呼び出すため、オブジェクトにはカスタムの初期値が存在します。


テストオブジェクトのchange1メソッドを呼び出し、日付をパラメータとして受け取ります。 JVM がこのコードを読み取ると、i がローカル変数であることが検出されるため、i をスタックに置き、日付の値を i に割り当てます。 i に 1234 を代入します。とてもシンプルなステップです。 change1 メソッドの実行後、ローカル変数 i が占有していたスタック領域は直ちに解放されます。

Java のメモリ割り当てについての深い理解 (画像とテキスト)

インスタンス d1 をパラメーターとして使用して、テスト オブジェクトの change2 メソッドを呼び出します。 JVM は、change2 メソッドの b パラメータがローカル変数であることを検出し、それをすぐにスタックに追加します。これは参照型変数であるため、b はポインタを d1 に格納します。このとき、b と d1 はオブジェクトを指します。同じヒープ。 b と d1 の間で渡されるのはポインタです。

Java のメモリ割り当てについての深い理解 (画像とテキスト)

別の BirthDate オブジェクトがインスタンス化され、b に割り当てられます。内部実行プロセスは、ヒープ領域に新しいオブジェクトを作成し、そのオブジェクトのポインタをスタックの b に対応する領域に保存します。このとき、インスタンス b はインスタンス d1 が指すオブジェクトを指しなくなります。ただし、インスタンス d1 が指すオブジェクトには変更がないため、d1 に影響を与えることはできません。

Java のメモリ割り当てについての深い理解 (画像とテキスト)

change2メソッドが完了し、ローカル参照変数bが占有していたスタック領域は直ちに解放されます。解放されるのはスタック領域だけであり、ヒープ領域は自動リサイクルを待つ必要があることに注意してください。

Java のメモリ割り当てについての深い理解 (画像とテキスト)

インスタンス d2 をパラメーターとして使用して、テスト インスタンスの change3 メソッドを呼び出します。同様に、JVM はスタック上にローカル参照変数 b 用のスペースを割り当て、d2 のポインタを b に格納します。このとき、d2 と b は同じオブジェクトを指します。次に、インスタンス b の setDay メソッドを呼び出すと、実際には d2 が指すオブジェクトの setDay メソッドが呼び出されます。

Java のメモリ割り当てについての深い理解 (画像とテキスト)

インスタンス b の setDay メソッドを呼び出すと、両方とも同じオブジェクトを指しているため、d2 に影響します。 change3 メソッドの実行後、ローカル参照変数 b はすぐに解放されます。

上記は、Java プログラム実行時のメモリ割り当ての一般的な状況です。実際、アイデアをマスターすれば、それは非常に簡単です。変数には基本型と参照型の 2 種類しかありません。どちらもローカル変数としてスタックに配置されます。基本型は値を直接スタックに保存します。参照型はヒープ領域へのポインタのみを保存します。実際のオブジェクトはヒープ内にあります。パラメーターとして使用される場合、基本型は値によって直接渡され、参照型はポインターによって渡されます。 Java のメモリ割り当てについての深い理解 (画像とテキスト)

概要:

1.インスタンスとは何か、オブジェクトとは何かを明確にします。
Class

a= new Class();

このとき、aはインスタンスと呼ばれるものであり、aがオブジェクトであるとは言えません。インスタンスはスタック上にあり、オブジェクトはヒープ上にあります。インスタンスを操作すると、実際にはインスタンス ポインターを介してオブジェクトが間接的に操作されます。複数のインスタンスが同じオブジェクトを指すことができます。 Java のメモリ割り当てについての深い理解 (画像とテキスト)

2.スタック内のデータとヒープ内のデータは同期的に破棄されません。メソッドが終了すると、スタック内のローカル変数はすぐに破棄されますが、ヒープ内のオブジェクトは必ずしも破棄されるわけではありません。このオブジェクトを指している他の変数がある可能性があるため、ヒープ内のオブジェクトを指している変数がスタック内になくなるまでは破棄されません。また、ガベージ コレクション スキャンが行われるまではすぐには破棄されません。 。

3.上記のスタック、ヒープ、コードセグメント、データセグメントなどはすべてアプリケーションプログラムに関連しています。各アプリケーションは固有の JVM インスタンスに対応し、各 JVM インスタンスは独自のメモリ領域を持ち、相互に影響しません。そして、これらのメモリ領域はすべてのスレッドで共有されます。ここで述べたスタックとヒープは全体的な概念であり、これらのスタックは細分化することもできます。的

のメンバー変数 オブジェクトが異なればそれぞれ異なり、独自の記憶領域 (ヒープ内のメンバー変数) を持ちます。ただし、クラスのメソッドは、そのクラスのすべてのオブジェクトによって共有されます。メソッドが使用されない場合、メソッドは 1 つのセットのみスタックにプッシュされます。 上記の分析にはスタックとヒープのみが含まれており、非常に重要なメモリ領域である定数プールもあり、そこでは説明できない問題が頻繁に発生します。定数プールの目的は上で説明されていますが、ロードされたクラスの定数を維持することだけを覚えておいてください。詳しく理解する必要はありません。次に、コンスタントプールの特徴を例を挙げて説明します。
予備知識:

基本的な型は、byte、short、char、int、long、boolean です。パッケージ化クラスの基本的なタイプは、Byte、Short、Character、Integer、Long、Boolean です。大文字と小文字が区別されることに注意してください。両者の違いは、基本型は通常の変数としてプログラムに反映され、基本型のラッパークラスはクラスとなり、参照変数としてプログラムに反映されます。したがって、これらはメモリ内の異なる場所に格納されます。プリミティブ型はスタックに格納され、プリミティブ ラッパー クラスはヒープに格納されます。上記のラッパー クラスはすべて定数プール テクノロジを実装していますが、他の 2 つの浮動小数点数型ラッパー クラスは実装していません。さらに、String 型には定数プール テクノロジも実装されています。

例:

public class test { 
  public static void main(String[] args) {   
    objPoolTest(); 
  } 
 
  public static void objPoolTest() { 
    int i = 40; 
    int i0 = 40; 
    Integer i1 = 40; 
    Integer i2 = 40; 
    Integer i3 = 0; 
    Integer i4 = new Integer(40); 
    Integer i5 = new Integer(40); 
    Integer i6 = new Integer(0); 
    Double d1=1.0; 
    Double d2=1.0; 
     
    System.out.println("i=i0\t" + (i == i0)); 
    System.out.println("i1=i2\t" + (i1 == i2)); 
    System.out.println("i1=i2+i3\t" + (i1 == i2 + i3)); 
    System.out.println("i4=i5\t" + (i4 == i5)); 
    System.out.println("i4=i5+i6\t" + (i4 == i5 + i6));   
    System.out.println("d1=d2\t" + (d1==d2));  
     
    System.out.println();     
  } 
}

結果:

i=i0  true 
i1=i2  true 
i1=i2+i3    true 
i4=i5  false 
i4=i5+i6    true 
d1=d2  false

結果

分析:

スタックには非常に重要な機能があります:

スタック内のデータは共有できます。 int i = 40; を定義し、次に int i0 = 40; を定義すると、スタックに 40 があるかどうかが自動的にチェックされ、ある場合、i0 は i の 40 を直接指し、新しい 40 は追加されません。

2.i1 と i2 は両方とも参照型であり、Integer はラッパー クラスであるため、スタック上にポインターを格納します。 Integer パッケージング クラスは定数プール テクノロジを実装しているため、i1 と i2 の 40 は両方とも定数プールから取得され、同じアドレスを指すため、i1=12 になります。 3.明らかにこれは加算演算です。Javaの数学演算はすべてスタック上で実行されます

Javaは自動的にi1とi2をボックス化解除して整数に変換しますので、i1の数値は次と等しいです。 i2+i3。

4.i4 と i5 は両方とも参照型であり、Integer はラッパー クラスであるため、ポインターをスタックに格納します。ただし、これらはそれぞれ新しいため、定数プールからデータを検索するのではなく、ヒープからオブジェクトを新しく探し、その後、それぞれがオブジェクトへのポインターを保存します。そのため、i4 と i5 は異なるポインターを保存するため等しくありません。尖った物体が違います。

5.これも3と同じ加算演算です。

6.Double はラッパー クラスであるため、d1 と d2 は両方とも参照型であり、スタック上にポインターを格納します。ただし、Double パッケージ化クラスは定数プール テクノロジを実装していないため、Doubled1=1.0; はヒープからの新しいオブジェクトである Double d1=new Double(1.0); と同等であり、同じことが d2 にも当てはまります。したがって、d1 と d2 に格納されているポインターは異なり、異なるオブジェクトを指しているため、等しくありません。

🎜概要:🎜🎜

1.上記の基本的なタイプのパッケージング クラスはすべて定数プール テクノロジを実装していますが、維持される定数は [-128 ~ 127] の範囲の定数のみです。定数値がこの範囲を超えると、オブジェクトが作成されます。 をヒープから取得し、定数プールから取得しなくなりました。たとえば、上記の例を Integer i1 = 400; Integer i2 = 400; に変更すると、明らかに 127 を超えており、定数プールから定数を取得できないため、新しい Integer オブジェクトを作成する必要があります。この時点では、i1 と i2 は等しくありません。

2. String 型にも定数プール技術が実装されていますが、少し異なります。 String 型は、まず定数プールに対応する string があるかどうかを検出し、存在しない場合はそれを取り出し、現在のものをそれに追加します。

脚注:


(1)

シンボル参照は、名前が示すように、シンボル参照が使用される場合にのみ解析されます。 Linux または Unix システムに精通している場合は、このシンボリック参照をファイルへのソフト リンクとして考えることができます。このソフト リンクが使用されると、実際のファイルを見つけるために実際に解析および展開されます シンボリックの場合。クラス読み込みレベルでは多くの議論があり、ソース コード レベルは形式的な議論にすぎません。

クラスがロードされると、そのクラスで使用される他のクラスのシンボリック参照が定数プールに保存されます。実際のコードが実行されるとき、初めて別のクラスに遭遇したときに、JVM はその参照を保存します。定数プール内のクラスは、シンボリック参照が展開されて直接参照に変換されるため、次回同じ型が見つかったとき、JVM はそれを解析せず、解析された直接参照を直接使用します。

クラスロードプロセスにおける上記のシンボリック参照ステートメントに加えて、ソースコードレベルでは、コード内の特定のデータがシンボリック参照であるか直接参照であるかを区別する参照解析プロセスに基づいています。

System.out.println("test " + "abc");//

ここで発生する効果は直接参照と同等であり、特定の Strings = "abc"; System.out.println(" test" + s);//Here この効果はシンボリック参照と同等です。つまり、 s が展開されて解析されます。これは、 s が "abc" のシンボリック リンクであることと同等です。つまり、コンパイル中に、クラスファイルは を直接展開するのではなく、これを参照して、実際のコードが実行されるときに展開されるシンボルを作成します。

以上がJava のメモリ割り当てについての深い理解 (画像とテキスト)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。