ヒープとメモリの最適化
今日、私はプロジェクトの自動データソート機能をテストし、データベース内の数万のレコードと写真を整理しました。操作が最後に近づいたとき、java.lang.outofmemoryerrorでは、Java Heapスペースのエラーが明らかになりました。 Javaにはゴミコレクターメカニズムがあるため、私はそれにあまり注意を払っていないため、過去には執筆プログラムでそのようなメモリエラーに遭遇することはめったにありませんでした。今日、私はオンラインでいくつかの情報を検索し、これに基づいてそれを整理しました。
1。スタックとスタック
新しいガベージコレクターがヒップで構築されているのは、リサイクルを担当しています
1.プログラムが実行を開始すると、JVMはOSからメモリを取得します。その一部はヒープメモリです。ヒープメモリは通常、ストレージアドレスの下部に上向きに配置されます。
2。ヒープは「ランタイム」データ領域であり、クラスにインスタンス化されたオブジェクトはヒープからスペースを割り当てます。
3。ヒープにスペースを割り当てることは、「新しい」などの指示を通じて確立されます。ヒープは動的に割り当てられたメモリサイズであり、寿命は事前にコンパイラに通知する必要はありません。
4.C ++とは異なり、Javaはヒープとスタックを自動的に管理し、ガベージコレクターは使用されなくなったヒープメモリを自動的にリサイクルできます。
5.不利な点は、メモリが実行時に動的に割り当てられるため、メモリアクセス速度が遅くなることです。
スタック - 基本型と参照タイプを速く保存します
1。通常、データ構造を使用して使用して、メソッド内のパラメーターとローカル変数を保存するために使用されます。
2。Javaでは、基本タイプのすべての変数(短い、int、long、byte、float、double、boolean、char)と参照タイプがスタックに保存されます。
3.スタック内のデータのリビングスペースは、一般に現在のスコープにあります({...}で囲まれた領域。
4.スタックのアクセス速度は、ヒープよりも高速で、CPUに直接位置するレジスタに次ぐものです。
5.スタック内のデータは共有でき、複数の参照が同じアドレスを指すことができます。
6.不利な点は、スタックのデータサイズと寿命を決定し、柔軟性を欠く必要があることです。
2。メモリ設定
1.仮想マシンメモリステータスを確認します
long maxcontrol = runtime.getRuntime()。maxmemory(); //仮想マシンがlong culentime = runtime()。totalmemory(); //仮想マシンで現在使用されているメモリの量を取得できるメモリの最大量を取得します
デフォルトでは、MaxControl = 66650112B = 63.5625MのJava仮想マシン。
何もしない場合、私のマシンで測定された現在のもの= 5177344B = 4.9375M;
2。メモリサイズを設定するコマンド
-xms <size>セット初期Javaヒープサイズ:JVM初期化ヒープメモリサイズを設定します。この値は、Garbageコレクションが完了するたびにJVM再分配メモリを回避するために、-XMXと同じに設定できます。
-xmx <size>最大Javaヒープサイズを設定:JVMの最大ヒープメモリサイズを設定します。
-xmn <size>:若い世代のサイズ、ヒープサイズ全体=若い世代のサイズ +古い世代のサイズ +最後の世代のサイズを設定します。
-xss <size> set javaスレッドスタックサイズ:JVMスレッドスタックメモリサイズを設定します。
3。特定の操作(1)JVMメモリ設定:
Open MyeClipse(Eclipse)Window-Preferences-Java-Installed JRES-EDIT-DEFAULT VM引数
入力:-XMX128M -XMS64M -XMN32M -XSS16M
(2)IDEメモリ設定:
myeclipse.ini(またはeclipse.ini in the Eclipse rootディレクトリ)の-vmargsの下の構成を変更します。
(3)Tomcatメモリ設定
Tomcatのルートディレクトリでビンフォルダーを開き、Catalina.batを編集します
変更:java_opts = -xms256m -xmx512mを設定します
3。JavaHeapのOutFmemoryErrorエラー分析
JVMが開始されると、-XMSパラメーターによって設定されたヒープメモリが使用されます。プログラムが継続し、より多くのオブジェクトを作成すると、JVMはヒープメモリを拡張して、より多くのオブジェクトを保持し始めます。 JVMはまた、ゴミコレクターを使用してメモリをリサイクルします。 -XMXによって設定された最大ヒープメモリにほぼ到達すると、新しいオブジェクトにメモリを割り当てることができない場合、JVMはJava.lang.outofMemoryErrorを投げ、プログラムはクラッシュします。 JVMは、OutFmeMoryErrorを捨てる前に、Garbage Collectorで十分なスペースを解放しようとしますが、まだ十分なスペースがないことがわかったときにこのエラーを投げます。この問題を解決するには、どのオブジェクトが作成したオブジェクト、どのオブジェクトがどの程度のスペースを占有するかなど、プログラムオブジェクトに関する情報を明確にする必要があります。プロファイラーまたはヒープアナライザーを使用して、OutOFMEMoryErrorエラーを処理できます。 「java.lang.outofmemoryerror:Java Heap Space」とは、ヒープに十分なスペースがなく、拡大し続けることができないことを意味します。 「java.lang.outofmemoryerror:permgen space」とは、永続的な世代がいっぱいであり、プログラムがクラスをロードしたり、文字列を割り当てたりすることができなくなることを意味します。
4。ヒープとガベージコレクション
オブジェクトはヒープメモリで作成されていることがわかっています。ガベージコレクションは、ヒープスペースから死んだオブジェクトをクリアし、これらのメモリをヒープに戻すプロセスです。ゴミコレクターを使用するために、ヒープは主に3つの領域、つまり新世代、古い世代、または終身系の世代、およびパーマスペースに分かれています。 New Generationは、新しく作成されたオブジェクトを保存するために使用されるスペースであり、オブジェクトが新しく作成されたときに使用されます。長い間使用される場合、それらはゴミコレクターによって古い世代(または終身世代)に移動されます。 Perm Spaceは、JVMがクラス、メソッド、ストリングプール、クラスレベルの詳細などのメタデータを保存する場所です。
5。概要:
1。Javaヒープメモリは、オペレーティングシステムによってJVMに割り当てられたメモリの一部です。
2。オブジェクトを作成すると、Javaヒープメモリに保存されます。
3。ゴミ収集を促進するために、Java Heapスペースは、New Generation、Old GenerationまたはTenured Generation、Perm Spaceと呼ばれる3つの領域に分割されます。
4. JVMコマンドラインオプション-XMS、-XMX、および-XMNを使用して、Javaヒープスペースのサイズを調整できます。
5. jconsoleまたはruntime.maxmemory()、runtime.totalmemory()、およびruntime.freememory()を使用して、Javaのヒープメモリのサイズを表示できます。
6.コマンド「jmap」を使用してヒープダンプを取得し、「jhat」を使用してヒープダンプを分析できます。
7。Javaヒープスペースは、スタックスペースとは異なります。スタックスペースは、コールスタックとローカル変数を保存するために使用されます。
8. Java Garbage Collectorは、死んだオブジェクト(使用されなくなったオブジェクト)で占められているメモリを取り戻し、Javaヒープスペースにリリースするために使用されます。
9. java.lang.outofmemoryerrorに遭遇した場合、心配する必要はありません。ヒープスペースを増やす必要がある場合があります。しかし、それが頻繁に発生した場合、Javaプログラムにメモリリークがあるかどうかを確認する必要があります。
10。プロファイラーとヒープダンプ分析ツールを使用して、Javaヒープスペースを表示すると、各オブジェクトに割り当てられるメモリの量がわかります。
スタックストレージの詳細な説明
Javaスタックストレージには次の特性があります。
1.スタック内のデータサイズとライフサイクルを決定する必要があります。
たとえば、基本タイプのストレージ:int a = 1;この変数にはリテラル値が含まれています。Aはintタイプへの参照であり、リテラル値は3です。これらのリテラルデータのサイズと寿命のため、これらのリテラル値はプログラムブロックで正当に定義され、プログラムブロックが終了した後、リテラル値が消えます)は、速度を追求するためにスタックに存在します。
2。スタックに存在するデータを共有できます。
(1)基本型データストレージ:
のように:
int a = 3; int b = 3;
コンパイラはint a = 3を最初に処理します。最初に、スタック内の変数Aへの参照を作成し、リテラル値が3のアドレスがあるかどうかを確認します。1つが見つからない場合、リテラル値は3のアドレスを開き、3のアドレスをポイントします。 Bの参照変数を作成した後、スタックにはすでに3のリテラル値があるため、Bは3のアドレスを直接指します。このように、AとBは両方とも同時に3を指します。
注:この文字通りの参照は、クラスオブジェクトの参照とは異なります。 2つのクラスオブジェクトの参照が同時にオブジェクトを指していると仮定すると、1つのオブジェクト参照変数がオブジェクトの内部状態を変更した場合、他のオブジェクト参照変数はすぐにこの変更を反映します。代わりに、文字通りの参照を介してその価値を変更しても、それに応じて別の値が変更されません。上記の例のように、aとbの値を定義した後、a = 4とします。次に、bは4に等しく、3に等しくなりません。コンパイラ内で、a = 4が遭遇すると、スタックに4のリテラル値があるかどうかを再検索します。そうでない場合は、4の値を保存するためにアドレスを再開します。すでに存在する場合は、このアドレスを直接指してください。したがって、値aの変化は値bに影響しません。
(2)パッケージデータストレージ:
整数、二重、文字列などの対応する基本データ型をラップするクラス。これらのクラスデータはすべてヒープに存在します。 Javaは新しい()ステートメントを使用してコンパイラを表示し、実行時に必要に応じて動的にのみ作成するため、より柔軟になりますが、不利な点はより多くの時間がかかることです。
たとえば、例として文字列を取ります。
文字列は特別なパッケージデータです。つまり、string str = new String( "ABC")の形式で作成できます。または、string str = "abc";の形で作成できます。前者は標準化されたクラス作成プロセスです。つまり、Javaではすべてがオブジェクトであり、オブジェクトはクラスのインスタンスであり、すべてがnew()の形で作成されます。 DateFormatクラスなど、Javaの一部のクラスは、クラスのgetInstance()メソッドを介して新しく作成されたクラスを返すことができます。これは、この原則に違反しているようです。実際、そうではありません。このクラスでは、シングルトンパターンを使用してクラスのインスタンスを返しますが、このインスタンスはnew()を介してクラス内で作成され、外部からこの詳細を隠しています。
それでは、なぜstring str = "abc";?上記の原則に違反していますか?実際、ありません。
文字列str = "abc"の内部作業について。 Javaはこのステートメントを内部的に次の手順に変換します。
a。最初にSTRに名前が付けられたオブジェクト参照変数を文字列クラスに定義します:文字列str;
b。スタックに値「ABC」のアドレスがあるかどうかを見つけます。そうでない場合は、リテラル値「ABC」のアドレスを開き、文字列クラスの新しいオブジェクトOを作成し、oの文字列値をこのアドレスに指し、スタック内のこのアドレスの横にある参照オブジェクトOに注意してください。値「ABC」のアドレスがある場合は、オブジェクトOを探して、Oのアドレスを返します。
c。オブジェクトOのアドレスにstrをポイントします。
通常、文字列クラスの文字列値が直接保存されることは注目に値します。ただし、string str = "abc";などの状況では、その文字列値は、スタックに存在するデータへの参照を保持します(つまり、string str = "abc"、スタックストレージとヒープストレージの両方)。
この問題をよりよく説明するために、次のコードを介して検証できます。
string str1 = "abc"; string str2 = "abc"; System.out.println(str1 == str2); //真実
(両方の参照が同じオブジェクトを指す場合にのみ、真実値は返されます。STR1とSTR2は同じオブジェクトを指しています)
結果は、JVMが2つの参照STR1とSTR2を作成したことを示していますが、1つのオブジェクトのみが作成され、両方の参照がこのオブジェクトを指し示しました。
string str1 = "abc"; string str2 = "abc"; str1 = "bcd"; System.out.println(str1 + "、" + str2); // bcd、abc system.out.println(str1 == str2); //間違い
これは、割り当ての変更がクラスオブジェクトの参照の変更をもたらすことを意味します。STR1は別の新しいオブジェクトを指し、STR2は元のオブジェクトを指します。上記の例では、STR1の値を「BCD」に変更すると、JVMはスタックに値を保存するアドレスがないことを発見したため、このアドレスを開き、文字列値がこのアドレスを指す新しいオブジェクトを作成しました。
実際、文字列クラスは不変のクラスになるように設計されています。その値を変更したい場合は、JVMは実行時に新しい値に基づいて新しいオブジェクトを静かに作成し(元のメモリに基づいて変更することはできません)、このオブジェクトのアドレスを元のクラスの参照に返します。この作成プロセスは完全に自動ですが、結局より多くの時間をかけます。時間の要件により敏感な環境では、特定の悪影響があります。
string str1 = "abc"; string str2 = "abc"; str1 = "bcd"; string str3 = str1; System.out.println(str3); // bcd string str4 = "bcd"; System.out.println(str1 == str4); //真実
STR3オブジェクトへの参照は、STR1を指すオブジェクトを直接指します(STR3は新しいオブジェクトを作成しないことに注意してください)。 STR1がその値を変更した後、文字列参照STR4を作成し、STR1によって作成された新しいオブジェクトを値の変更によって作成します。今回はSTR4が新しいオブジェクトを作成しなかったため、スタック内のデータの共有が再び実現していることがわかります。
string str1 = new String( "ABC"); string str2 = "abc"; System.out.println(str1 == str2); //間違い
2つの参照が作成されました。 2つのオブジェクトが作成されました。 2つの参照は、2つの異なるオブジェクトを指します。
string str1 = "abc"; string str2 = new String( "ABC"); System.out.println(str1 == str2); //間違い
2つの参照が作成されました。 2つのオブジェクトが作成されました。 2つの参照は、2つの異なるオブジェクトを指します。
上記の2つのコードは、オブジェクトがnew()で作成されている限り、ヒープで作成され、その文字列は個別に保存されることを示しています。スタック内のデータと同じであっても、スタック内のデータと共有されません。
要約:
(1)string str = "abc"などの形式を使用してクラスを定義する場合、文字列クラスのオブジェクトSTRを作成したことを常に当たり前のことと見なします。トラップを心配してください!オブジェクトは作成されていない可能性があります!確かなことは、文字列クラスへの参照が作成されることです。この参照が新しいオブジェクトを指しているかどうかについては、新しいオブジェクトを明示的に作成するためにnew()メソッドを使用しない限り、コンテキストに基づいて考慮する必要があります。したがって、より正確にするために、文字列クラスのオブジェクトへの参照変数STRを作成します。これは、「ABC」の値を持つ文字列クラスを指します。これを認識することは、プログラムの難しいバグのトラブルシューティングに役立ちます。
(2)string str = "abc"を使用しています。 JVMは、スタック内のデータの実際の状況に基づいて新しいオブジェクトを作成する必要があるかどうかを自動的に決定するため、プログラムの実行速度をある程度改善できます。 string str = new String( "ABC");のコードの場合、新しいオブジェクトが等しいかどうかに関係なく、新しいオブジェクトが新しいオブジェクトを作成する必要があるかどうかに関係なく作成され、それによりプログラムの負担が増加します。
(3)文字列クラスの不変の性質のため(ラッパークラスの値を変更できないため)、文字列変数を頻繁に変換する必要がある場合、StringBufferクラスを検討してプログラムの効率を改善する必要があります。