Javaメモリの割り当てと管理は、Javaのコアテクノロジーの1つです。以前に、Javaの記憶管理、メモリリーク、Java Garbage Collectionの知識を紹介しました。今日、私たちは再びJavaコアに深く入り込み、メモリ割り当てにおけるJavaの知識を詳細に紹介します。一般的に、Javaはメモリを割り当てるときに次の領域を含みます。
◆登録:プログラムで制御することはできません
◆スタック:基本的なタイプのデータとオブジェクトへの参照を保存しますが、オブジェクト自体はスタックに保存されていませんが、ヒープに保存されます(新しいオブジェクトは新しいオブジェクト)
◆ヒープ:新しいを使用して生成されたデータを保存します
◆静的ドメイン:静的で定義されたオブジェクトに保存された静的メンバー
◆一定のプール:定数を保存します
◆非RAMストレージ:ハードディスクなどの永続的なストレージスペース
Javaメモリ割り当てのスタック
オブジェクトの関数と参照変数で定義されたいくつかの基本的なタイプの変数データはすべて、関数のスタックメモリに割り当てられます。
変数がコードのブロックで定義されている場合、Javaはスタック内の変数のメモリスペースを割り当てます。変数がスコープを終了すると、Javaは変数に割り当てられたメモリスペースを自動的にリリースし、メモリスペースはすぐに個別に使用できます。スタック内のデータサイズとライフサイクルは確実であり、このデータはデータを指し示していないと消えます。
Javaメモリ割り当てのヒープ
ヒープメモリは、新しいによって作成されたオブジェクトと配列を保存するために使用されます。ヒープに割り当てられたメモリは、Java Virtual Machineの自動ゴミコレクターによって管理されます。
アレイまたはオブジェクトがヒープで生成された後、スタック内のこの変数の値がヒープメモリ内のアレイまたはオブジェクトの最初のアドレスに等しくなり、スタック内の変数がアレイまたはオブジェクトの参照変数になります。参照変数は、配列またはオブジェクトに与えられた名前と同等です。プログラムのスタック内の参照変数を使用して、ヒープ内の配列またはオブジェクトにアクセスできます。参照変数は、配列またはオブジェクトに与えられた名前と同等です。
参照変数は通常の変数であり、定義されたときにスタックに割り当てられます。参照変数は、プログラムが範囲外で実行された後にリリースされます。アレイとオブジェクト自体はヒープに割り当てられます。プログラムが、配列またはオブジェクトを生成するために新しいステートメントを使用しているコードブロックの外側で実行されたとしても、配列とオブジェクト自体が占めるメモリはリリースされません。配列とオブジェクトは、それを指す参照変数がない場合にのみゴミになり、使用できませんが、メモリスペースを占有します。不確実な時間にゴミコレクターによって収集(リリース)されます。これが、Javaがより多くの記憶を取り上げる理由でもあります。
実際、スタックの変数は、Javaのポインターであるヒープメモリの変数をポイントします!
ヒープとスタック
Javaのヒープはランタイムデータ領域であり、そこからオブジェクトがスペースを割り当てます。これらのオブジェクトは、New、NewArray、Anewarray、MultianWarrayなどの指示を通じて確立されます。プログラムコードを明示的にリリースする必要はありません。ヒープはゴミ収集を担当します。ヒープの利点は、メモリサイズを動的に割り当てることができ、実行時にメモリを動的に割り当てるため、コンパイラに事前に指示する必要がないことです。 JavaのGarbage Collectorは、使用されなくなったデータを自動的に収集します。しかし、欠点は、実行時にメモリを動的に割り当てる必要があるため、アクセス速度が遅くなることです。
スタックの利点は、アクセス速度がヒープよりも高速で、レジスタに次ぐものであり、スタックデータを共有できることです。しかし、欠点は、スタックのデータサイズと寿命が決定論的であり、柔軟性がないことです。スタックは主に、いくつかの基本的なタイプの変数データ(int、short、long、byte、float、double、boolean、char)とオブジェクトハンドル(参照)を保存します。
スタックの非常に重要な特別な機能は、スタックに存在するデータを共有できることです。同時に定義するとします。
Javaコード
int a = 3;
int b = 3;
コンパイラはint a = 3を最初に処理します。最初に、変数Aを持つスタックに参照を作成し、次にスタックに3の値があるかどうかを確認します。発見されていない場合は、3を保存してから3を指します。 bの参照変数を作成した後、スタックにはすでに3の値があるため、Bは直接3に指されます。このように、AとBは両方とも同時に3を指します。
この時点で、a = 4が再び設定されている場合。その後、コンパイラは、スタックに4値があるかどうかを再度検索します。そうでない場合は、4を保存し、Aを4をポイントします。すでに存在する場合は、このアドレスを直接指してください。したがって、値aの変化は値bに影響しません。
このデータの共有は、同時に1つのオブジェクトを指す2つのオブジェクトからの参照の共有とは異なることに注意してください。この場合、Aの変更はBに影響しないため、スペースを節約するのに役立つコンパイラによって行われます。オブジェクト参照変数は、このオブジェクトの内部状態を変更し、別のオブジェクト参照変数に影響します。
Javaコード
1.int i1 = 9;
2.int i2 = 9;
3.int i3 = 9;
4.public static final int1 = 9;
5.public static final int2 = 9;
6.パブリック静的最終int3 = 9;
メンバー変数とローカル変数の場合:メンバー変数は、メソッドとクラス内で定義されています。ローカル変数は、メソッドまたはステートメントブロック内で定義された変数です。ローカル変数を初期化する必要があります。
正式なパラメーターはローカル変数であり、ローカル変数のデータはスタックメモリに存在します。メソッドが消えると、スタックメモリ内のローカル変数が消えます。
メンバー変数はヒープ内のオブジェクトに保存され、ガベージコレクターによって収集されます。
次のコードのように:
Javaコード
クラスの生年月日{private int day;プライベートインターマンス;プライベートイント年; Public Birthdate(int d、int m、int y){day = d;月= m; year = y; } ofit get、set method……} public class test {public static void main(string args []){int date = 9;テストテスト= new Test(); test.change(date);生年月日D1 =新しい生年月日(7,7,1970); } public void change1(int i){i = 1234; }上記のコードの場合、日付はローカル変数です。I、D、M、Yはすべてローカル変数として正式なパラメーターであり、日、月、年はメンバー変数です。コード実行中の変更を分析しましょう。
1.メインメソッドは実行を開始します:int date = 9;
ローカル変数、基本タイプ、参照、および値はすべてスタックに存在します。
2。テストテスト= new Test();
テストはオブジェクトの参照であり、スタックに存在し、オブジェクト(新しいテスト())がヒープに存在します。
3。test.change(date);
私はローカル変数であり、参照と値はスタックに存在します。メソッドの変更が実行されると、スタックから消えます。
4。生年月日D1 =新年生(7,7,1970);
D1はオブジェクト参照であり、スタックに存在します。オブジェクト(新しい誕生日())がヒープに存在します。ここで、d、m、yはスタックに保存されているローカル変数であり、そのタイプはベースタイプであるため、データもスタックに保存されます。日、月、年はメンバー変数であり、それらはヒープに保存されます(new birthdate())。生年月日コンストラクターが実行されると、d、m、yはスタックから消えます。
5.メインメソッドが実行された後、日付変数、テスト、およびD1リファレンスがスタックから消え、新しいTest()はGarbage Collectionを待機します。
一定のプール
定数プールは、コンパイル期間中に決定され、コンパイルされた.classファイルに保存されるデータを指します。
コードで定義されているさまざまな基本タイプ(int、longなどなど)およびオブジェクトタイプ(文字列や配列など)の定数値(最終)を含めることに加えて、次のようなテキスト形式にいくつかのシンボリック参照も含まれています。
◆クラスとインターフェイスの完全に適格な名前。
◆フィールドの名前と記述子。
◆メソッドと名前と記述子。
コンパイル期間が作成されている場合(二重引用符で直接定義)、一定のプールに保存され、実行期間(新しい)で決定できる場合、ヒープに保存されます。等しい文字列の場合、一定のプールには常に1つのコピーとヒープに複数のコピーがあります。
文字列は特別なパッケージデータです。使用できます:
Javaコード
string str = new String( "abc"); string str = "abc";
作成する2つのフォームがあります。 1つ目は、new()を使用して新しいオブジェクトを作成することです。これはヒープに保存されます。新しいオブジェクトが呼び出されるたびに作成されます。 2番目のタイプは、最初にスタック内の文字列クラスのオブジェクトに可変STRを作成し、次に象徴的な参照を使用して、文字列定数プールに「ABC」があるかどうかを調べることです。そうでない場合は、「ABC」を文字列定数プールに保存し、strを「ABC」を指します。既に「ABC」がある場合は、STRを「ABC」を直接指すようにします。
クラスの値が等しいかどうかを比較する場合、equals()メソッドを使用します。 2つのラッパークラスの参照が同じオブジェクトを指しているかどうかをテストするときは、==を使用し、以下の例を使用して上記の理論を説明します。
Javaコード
string str1 = "abc"; string str2 = "abc"; system.out.println(str1 == str2); //真実
Str1とStr2が同じオブジェクトを指していることがわかります。
Javaコード
string str1 = new String( "abc"); string str2 = new String( "abc"); system.out.println(str1 == str2); // 間違い
新しい方法は、異なるオブジェクトを生成することです。一度に1つずつ生成します。
したがって、2番目の方法では、複数の「ABC」文字列が作成され、メモリには1つのオブジェクトのみがあります。この執筆方法は有益であり、メモリスペースを保存します。同時に、JVMはスタック内のデータの実際の状況に基づいて新しいオブジェクトを作成する必要があるかどうかを自動的に決定するため、プログラムの実行速度をある程度改善できます。 string str = new String( "ABC");のコードの場合、新しいオブジェクトが等しいかどうかに関係なく、新しいオブジェクトが新しいオブジェクトを作成する必要があるかどうかに関係なく作成され、それによりプログラムの負担が増加します。
一方、注:文字列str = "abc"などの形式を使用してクラスを定義する場合、文字列クラスのオブジェクトSTRを作成することを常に当たり前のことと考えています。トラップを心配してください!オブジェクトは作成されていない可能性があります!そして、以前に作成されたオブジェクトを指すだけかもしれません。新しい()メソッドを介してのみ、毎回新しいオブジェクトが作成されるようにすることができます。
文字列の定数プーリングの問題のいくつかの例
例1:
Javaコード
string s0 = "kvill"; string s1 = "kvill"; string s2 = "kv" + "ill"; system.out.println(s0 == s1); system.out.println(s0 == s2);結果は次のとおりです
分析:まず第一に、結果はJavaが文字列定数に1つのコピーのみを確保することを保証することを知る必要があります。
例のS0とS1は両方とも弦定数であるため、コンパイル期間中に決定されるため、S0 == S1は真です。 「KV」と「Ill」も文字列定数です。文字列が複数の文字列定数で接続されている場合、それは間違いなく文字列定数自体であるため、S2はコンピレーション期間中に文字列定数に解析されるため、S2は定数プールの「kvill」への参照でもあります。したがって、s0 == s1 == s2を取得します。
例2:
例:
Javaコード
分析: new string()で作成された文字列は定数ではなく、コンピレーション期間中に決定することはできないため、new string()によって作成された文字列は定数プールに配置されておらず、独自のアドレススペースがあります。
S0は、一定のプールでの「kvill」の適用でもあります。 S1は、コンピレーション期間中に決定することができないため、実行時に作成された新しいオブジェクト「kvill」への参照です。 S2は、新しい文字列(「Ill」)の後半があるため、コンピレーション期間中に決定することはできません。したがって、新しく作成されたオブジェクト「Kvill」の適用でもあります。これらを理解すると、この結果が得られる理由がわかります。
例3:
Javaコード
string a = "a1"; string b = "a" + 1; system.out.println((a == b)); // result = true string a = "true"; string b = "a" + "true"; system.out.println((a == b)); // result = true string a = "a3.4"; string b = "a" + 3.4; system.out.println((a == b)); // result = true
分析:文字列定数のJVM接続の場合、JVMは、プログラムコンピレーション期間後の定数文字列の接続値への「+」接続を最適化します。例として「a」 + 1を取ります。コンパイラによる最適化の後、クラスのA1はすでにA1です。編集期間中、文字列定数の値が決定されるため、上記のプログラムの最終結果は真です。
例4:
Javaコード
string a = "ab"; string bb = "b"; string b = "a" + bb; system.out.println((a == b)); // result = false
分析:JVMの文字列参照の場合、文字列の「 +」接続に文字列参照があるため、参照される値はプログラムコンパイル期間中に決定することはできません。したがって、上記のプログラムの結果は偽です。
例5:
Javaコード
string a = "ab"; final string bb = "b"; string b = "a" + bb; system.out.println((a == b)); // result = true
分析:[4]の唯一の違いは、BBストリングが最終的な変更で装飾されていることです。最終的な変更変数の場合、コンパイル時に一定の値のローカルコピーとして解析され、独自の一定のプールに保存されるか、バイトコードストリームに埋め込まれています。したがって、この時点で、「A」 + BBと「A」 +「B」の影響は同じです。したがって、上記のプログラムの結果は当てはまります。
例6:
Javaコード
string a = "ab"; final string bb = getbb(); string b = "a" + bb; system.out.println((a == b)); // result = falsePrivate static string getbb(){return "b"; }分析:JVMは文字列についてBBを参照し、その値は編集期間中に決定することはできません。プログラムランタイム中にメソッドを呼び出した後にのみ、メソッドと「a」の返品値が動的に接続され、アドレスはbに割り当てられます。したがって、上記のプログラムの結果は偽です。
文字列については不変です
上記の例から、私たちは知ることができます:
文字列s = "a" + "b" + "c";
string s = "abc"に相当します。
文字列a = "a";
文字列b = "b";
文字列c = "c";
文字列s = a + b + c;
これは異なり、最終結果は以下に等しくなります。
Javaコード
StringBuffer Temp = new StringBuffer(); Temp.Append(a).Append(b).Append(c); string s = temp.toString();
上記の分析結果から、Stringが接続演算子(+)を使用して、このコードのように非効率性の理由を分析することを推測することは難しくありません。
Javaコード
public class test {public static void main(string args []){string s = null; for(int i = 0; i <100; i ++){s+= "a"; }}}+ +が完了するたびに、stringbuilderオブジェクトが生成され、それを追加して捨てます。次にループが到着すると、stringbuilderオブジェクトが再生され、文字列を追加し、ループが終了するまで完了します。 StringBuilderオブジェクトを直接使用して追加すると、N -1時間を節約してオブジェクトを作成および破壊できます。したがって、ループで文字列連結を必要とするアプリケーションの場合、一般に、stringbufferまたはstringbuliderオブジェクトを使用して追加の操作が実行されます。
文字列クラスの不変の性質のため、これについて多くのことを言うことがあります。たとえば、文字列のインスタンスが生成されると、文字列のインスタンスが変化しないことを知っている限り:文字列str =” kv”+” lill”+” ans”; 4つの文字列定数があります。まず、「KV」と「Ill」がメモリで「KVill」を生成し、次に「KVill」と「」「「KVILL」と「」と「KVILL ANS」を生成します。この文字列のアドレスはSTRに割り当てられます。
文字列での最終的な使用と理解
Javaコード
final stringbuffer a = new StringBuffer( "111"); final Stringbuffer b = new StringBuffer( "222"); a = b; //この文は完了するまでコンパイルされません。 final stringbuffer a = new StringBuffer( "111"); a.Append( "222"); ///完成後にコンパイル
ファイナルは、参照される「値」(つまり、メモリアドレス)に対してのみ有効であることがわかります。参照は、最初に指摘されたオブジェクトを指すだけに強制します。ポインティングを変更すると、コンパイル時間エラーが発生します。それが指すオブジェクトの変更に関しては、最終は無責任です。
要約します
スタックは、元のデータ型のローカル変数データとオブジェクトへの参照(文字列、配列、オブジェクトなど)を保存するために使用されますが、オブジェクトコンテンツを保存しません
新しいキーワードを使用して作成されたオブジェクトは、ヒープに保存されます。
文字列は特別なラッパークラスであり、その参照はスタックに保存され、オブジェクトコンテンツは作成方法(定数プールとヒープ)に従って決定する必要があります。コンパイル時に作成され、文字列の定数プールに保存されるものもありますが、実行時にのみ作成されるものもあります。新しいキーワードを使用して、ヒープに保存します。
上記の記事では、Java+メモリの割り当てと可変ストレージの場所の違いについて簡単に説明しています。参照を提供できることを願っています。wulin.comをもっとサポートできることを願っています。