この記事では、Javaメモリ管理の原則と、すべてのJava開発者に役立つことを願っています。
Javaメモリ管理メカニズム
C ++では、メモリを動的に割り当てるためにメモリが必要な場合、プログラマーはこのメモリのライフサイクル全体に責任を負う必要があります。割り当てのアプリケーションから使用、使用、最終リリースまで。このプロセスは非常に柔軟ですが、非常に面倒です。 Java言語は、ゴミ収集メカニズムであるメモリ管理のために独自の最適化を行っています。 Javaのほぼすべてのメモリオブジェクトは、ヒープメモリに割り当てられ(基本データ型を除く)、GC(Gage Collection)は、使用されなくなったメモリを自動的にリサイクルする責任があります。
上記は、Javaメモリ管理メカニズムの基本的な状況です。しかし、これを理解しているだけでも、実際のプロジェクト開発でメモリリークに遭遇します。 Javaのゴミ収集メカニズムは自動的にメモリをリサイクルできるので、なぜまだメモリリークがあるのかを疑う人もいるかもしれません。この質問では、GCがメモリオブジェクトをリサイクルし、どのようなメモリオブジェクトがGCによって「もはや使用されていない」と見なされるかを知る必要があります。
Javaのメモリオブジェクトへのアクセスは、参照方法を使用します。 Javaコードでは、この参照変数の値を使用して、メモリオブジェクトの参照変数を維持します。 Javaプログラムでは、この参照変数自体は、ヒープメモリおよびコードスタックのメモリ(基本データ型と同じ)に保存できます。 GCスレッドは、コードスタック内の参照変数から追跡を開始し、使用されているメモリを決定します。 GCスレッドがこの方法でヒープメモリの一部を追跡できない場合、GCはこのメモリがもはや使用されなくなると考えています(コードがこのメモリにアクセスできなくなるため)。
この指示されたグラフメモリ管理方法により、メモリオブジェクトがすべての参照を失うと、GCはリサイクルできます。逆に、オブジェクトにまだ参照がある場合、Java Virtual MachineがOutFmeMoryErrorを投げたとしても、GCによってリサイクルされません。
Javaメモリリーク
一般的に言えば、メモリリークには2つの状況があります。あるケースでは、C/C ++言語では、ヒープ内のすべての割り当てられたメモリが削除されます(ポインター再割り当てなど)。メモリとそのアクセス方法(参照)。最初のケースは、ゴミ収集メカニズムの導入により、Javaで十分に解決されたことです。したがって、Javaのメモリリークは、主に2番目のケースを指します。
たぶん、概念について話すことは抽象的すぎるかもしれません。そのような例を見ることができます。
コードコピーは次のとおりです。
ベクトルv = new Vector(10);
for(int i = 1; i <100; i ++){
オブジェクトo = new object();
v.add(o);
o = null;
}
この例では、ベクトルオブジェクトVへの参照とコードスタック内のオブジェクトオブジェクトOへの参照があります。 forループでは、新しいオブジェクトを継続的に生成し、ベクトルオブジェクトに追加してから、o参照を空にします。問題は、o参照が空の後にGCが発生した場合、作成するオブジェクトオブジェクトはGCによってリサイクルされるのでしょうか?答えはノーです。 GCがコードスタック内の参照を追跡すると、Vリファレンスが見つかり、追跡し続けるため、v参照によって指されるメモリスペースにオブジェクトオブジェクトへの参照があることがわかります。つまり、o参照は空でしたが、オブジェクトオブジェクトにはまだ他の参照があり、アクセスできるため、GCはそれをリリースできません。このループの後にオブジェクトオブジェクトがプログラムに影響を与えない場合、このJavaプログラムでメモリリークが発生したと思います。
Javaメモリリークは、C/C ++のメモリリークに対して破壊的ではありませんが、プログラムがクラッシュするいくつかのケースを除き、プログラムは依然として正常に実行できます。ただし、モバイルデバイスがメモリとCPUに厳密な制限がある場合、Javaメモリオーバーフローはプログラムの非効率性と、大量の不要なメモリの占有率につながります。これにより、マシン全体のパフォーマンスが悪化し、深刻な場合にはOutFmemoryErrorが投げられ、プログラムがクラッシュします。
一般的にメモリリークを避けてください
一般に、複雑なデータ構造を含めることなく、メモリオブジェクトのライフサイクルがプログラムが必要とする時間を超えると、Javaメモリが漏れます。 「オブジェクトフリー」と呼ぶこともあります。
例えば:
コードコピーは次のとおりです。
パブリッククラスfilesearch {
プライベートバイト[]コンテンツ;
プライベートファイルmfile;
public filesearch(file file){
mfile = file;
}
public boolean hasstring(string str){
int size = getFileSize(mfile);
content = new byte [size];
loadfile(mfile、content);
文字列s = new String(content);
s.contains(str)を返します。
}
}
このコードには、filesearchクラスには、ドキュメントに指定された文字列が含まれているかどうかを判断する関数のhasttringがあります。プロセスは、最初にMFILEをメモリにロードし、次に判断を下すことです。ただし、ここでの問題は、コンテンツがローカル変数ではなくインスタンス変数として宣言されることです。したがって、この関数が戻った後、ファイル全体のデータはメモリに存在します。将来これらのデータを必要としなくなったことは明らかであり、それが記憶の不合理な無駄につながる。
この場合のメモリリークを回避するには、C/C ++メモリ管理思考で割り当てられたメモリを管理する必要があります。まず、オブジェクト参照を宣言する前に、メモリオブジェクトの効果的な範囲を明確にすることです。関数内で有効なメモリオブジェクトは、ローカル変数として宣言される必要があり、クラスインスタンスと同じライフサイクルを持つオブジェクトはインスタンス変数などとして宣言する必要があります。次に、メモリオブジェクトが不要になったら手動で空にすることを忘れないでください。
複雑なデータ構造におけるメモリリーク問題
実際のプロジェクトでは、プログラム操作中に必要なデータ情報をキャッシュするために、より複雑なデータ構造を使用してしばしば使用します。データ構造の複雑さや、特別なニーズがあるため(たとえば、プログラムの実行速度を改善するためのキャッシュ情報など)、データに対処することは困難です。データ構造で。この時点で、Javaの特別なメカニズムを使用して、メモリの漏れを防ぐことができます。
JavaのGCメカニズムは、メモリを追跡する参照メカニズムに基づいていることを紹介しました。その前に、使用した参照は、「オブジェクトo;」の形式のみを定義しました。実際、これはJava参照メカニズムのデフォルトの状況にすぎず、さらに他にもいくつかの参照方法があります。これらの特別な参照メカニズムを使用し、GCメカニズムと組み合わせることにより、必要な効果のいくつかを達成できます。
Javaのいくつかの参照方法
Javaにはいくつかの異なる方法があります。つまり、強い引用、柔らかい引用、弱い引用、仮想引用です。次に、最初にこれらの引用方法の重要性を詳細に理解します。
強い引用
以前に導入したコンテンツで使用されていた引用はすべて、使用される最も一般的な引用である強力な引用でした。オブジェクトに強い参照がある場合、それは日常の必需品に似ており、ガベージコレクターはそれをリサイクルしません。メモリスペースが不十分な場合、Java仮想マシンは、メモリ問題を解決するための強い参照でオブジェクトをリサイクルするよりも、プログラムを異常に終了させるために、outofmemoryerrorエラーを投げたいと思います。
softreference
ソフトレファレーションクラスの典型的な使用は、記憶に敏感なキャッシュのためです。ソフトランスの原則は、JVMがオブジェクトへの参照を保持するときに不十分なメモリを報告する前に、すべてのソフト参照がクリアされることを保証することです。重要なポイントは、ガベージコレクターが実行時にソフトアクセス可能なオブジェクトをリリースする(またはそうでない場合がある)ことです。オブジェクトが解放されているかどうかは、ガベージコレクターのアルゴリズムと、ガベージコレクターが実行されているときに利用可能なメモリの量に依存します。
弱者
WeakReferenceクラスの典型的な使用は、マッピング(正規化されたマッピング)を正規化することです。さらに、弱い参照は、比較的長い寿命と低いレクリエーションのオーバーヘッドを持つオブジェクトにも役立ちます。重要なポイントは、ガベージコレクターの実行中に弱くアクセス可能なオブジェクトに遭遇した場合、WeakReferenceによって参照されるオブジェクトがリリースされることです。ただし、ゴミコレクターは、弱くアクセス可能なオブジェクトを見つけて放出する前に、複数回実行する必要がある場合があることに注意してください。
幻想
Phantomreferenceクラスは、参照されるオブジェクトの今後のコレクションを追跡するためにのみ使用できます。同様に、それはまた、死前のクリアリング操作を実行するために使用することもできます。 Phantomreferenceは、参照クラスで使用する必要があります。通知メカニズムとして機能する可能性があるため、リファレンスキューが必要です。ガベージコレクターがオブジェクトが仮想アクセスオブジェクトであると判断すると、ファントミレファレンスオブジェクトがその参照に配置されます。 Phantomreferenceオブジェクトを参照キューに置くことは、Phantomreferenceオブジェクトによって参照されるオブジェクトが終了し、収集に利用できることを示す通知です。これにより、オブジェクトが占めるメモリがリサイクルされる直前にアクションを実行できます。参照とリファレンスキューは、ReferenceQueueと組み合わせて使用されます。
GC、リファレンスおよびリファレンスキュー
A. GCは、強い参照でオブジェクトのメモリを削除することはできません。
B. GCは、ソフト参照のみでオブジェクトメモリを見つけました。
softReferenceオブジェクトの参照フィールドはnullに設定されているため、オブジェクトはヒープオブジェクトを参照しなくなります。
softreferenceによって参照されるヒープオブジェクトは、最終化可能であると宣言されます。
heapオブジェクトのfinalize()メソッドが実行され、オブジェクトによって占有されているメモリがリリースされると、softReferenceオブジェクトが参照キューに追加されます(後者が存在する場合)。
C. GCは、参照が弱いだけのオブジェクトメモリを発見します。
weakRewreferenceオブジェクトの参照フィールドはnullに設定されているため、オブジェクトはヒープオブジェクトを参照しなくなります。
weakRewreferenceによって参照されるヒープオブジェクトは、最終化可能であると宣言されます。
heapオブジェクトのfinalize()メソッドが実行され、オブジェクトによって占有されているメモリがリリースされると、weakreferenceオブジェクトが参照キューに追加されます(後者が存在する場合)。
D. GCは、仮想参照のみを持っているオブジェクトメモリを発見します。
phantomreferenceによって参照されるヒープオブジェクトは、最終化可能であると宣言されます。
Heapオブジェクトがリリースされる前に、Phantomreferenceが参照キューに追加されます。
次のポイントは注目に値します。
1。GCは、一般的にソフト参照のメモリオブジェクトを見つけません。
2。GCの弱い参照の発見とリリースは、すぐにGCを繰り返す必要がない場合があります。
3.ソフト参照と弱い参照がReferenceQueueに追加されると、実際のメモリへの参照が空に設定され、関連するメモリがリリースされました。 ReferenceQueueに仮想参照を追加すると、メモリはまだリリースされておらず、引き続きアクセスできます。
上記の紹介を通して、Java引用メカニズムといくつかの引用方法の類似点と相違点を特定して理解していると思います。概念だけが抽象的であるかもしれません。例を使用して、コードで参照メカニズムを使用する方法を示しましょう。
コードコピーは次のとおりです。
string str = new String( "Hello");
ReferenceQueue <String> rq = new ReferenceQueue <String>();
weakreference <string> wf = new weakreference <string>(str、rq);
str = null; //「hello」オブジェクトの強力な参照
string str1 = wf.get();
//「hello」オブジェクトがリサイクルされていない場合、rq.poll()がnullを返します
参照<?
上記のコードでは、2つの場所に注意してください。 「hello」オブジェクトがリサイクルされていない場合、wf.get()が「hello」文字列オブジェクトを返し、rq.poll()はnullを返します。 null、rq.poll()は参照オブジェクトを返しますが、この参照オブジェクトにはstrオブジェクトへの参照はありません(ファントマレファレンスは、弱者やソフレファレンスとは異なります)。
引用メカニズムと複雑なデータ構造の共同適用
GCメカニズム、参照メカニズム、および参照キューと組み合わせることにより、メモリオーバーフローを防ぐ複雑なデータ型を実装できます。
たとえば、Softreferenceにはキャッシュシステムの構築の特性があるため、ハッシュテーブルと組み合わせて単純なキャッシュシステムを実装できます。これにより、多くの情報がキャッシュできるだけでなく、Java仮想マシンがメモリの漏れのためにOffmeMoryErrorを捨てないことを保証します。このキャッシングメカニズムは、メモリオブジェクトのライフサイクルが長く、メモリオブジェクトを生成する時間が比較的長い状況に特に適しています。ライフサイクルが長いが、メモリオブジェクトを生成するオーバーヘッドが大きくない場合には、弱者を使用するとメモリ管理が向上する可能性があります。
Softhashmapのソースコードのコピーは、それを読んだ後、参照メカニズムの適用をより深く理解できると思います。
コードコピーは次のとおりです。
パッケージcom。
//:softhashmap.java
java.utilをインポートします。
java.lang.refをインポートします。
Android.util.logをインポートします。
パブリッククラスのsofthashmapはabstractmapを拡張します{
/**ソフトレファレンスを保持する内部ハッシュマップ。
プライベート最終マップhash = new Hashmap();
/**内部的に保持する「ハード」参照の数。
プライベートファイナルint hard_size;
/**ハード参照のFIFOリスト、最後のアクセスの順序。
プライベートファイナルLinkedList hardCache = new LinkedList();
/**クリアされたsoftreferenceオブジェクトの参照キュー。
プライベートリファレンスキュー= new ReferenceQueue();
//強力な参照番号
public softhashmap(){this(100);
public softhashmap(int hardsize){hard_size = hardsize;
パブリックオブジェクトGET(オブジェクトキー){
オブジェクト結果= null;
//そのキーで表されるソフトラファレンスを取得します
softreference soft_ref =(softreference)hash.get(key);
if(soft_ref!= null){
// softreferenceから値を取得します。
//マップにない場合、またはで削除された場合はnull
//以下に定義されているProcessQueue()メソッド
result = soft_ref.get();
if(result == null){
//値が収集された場合、
//ハッシュマップからのエントリ。
Hash.Remove(key);
} それ以外 {
//このオブジェクトをハードの始まりに追加します
//参照キュー
//一度、fifoキューのルックアップが遅いので、
//削除するたびにそれを検索したくない
// Duplicates。
//最近の使用オブジェクトをメモリに保ちます
hardcache.addfirst(result);
if(hardcache.size()> hard_size){
// hard_sizeより長いリストの場合、最後のエントリを削除します
hardcache.removelast();
}
}
}
返品結果;
}
/**私たちは、私たち自身のソフトレファレンスのサブクラスを定義します。
値だけでなく、見つけやすくするための鍵も
ハッシュマップが収集された後のエントリ。
private static class softvalueはsoftreferenceを拡張します{
プライベートオブジェクトキー
/**外側のクラスがプライベートデータにアクセスできることをご存知ですか
内側のクラスのメンバーと方法はわかりませんでした!
アクセスできるのは内側のクラスだけだと思いました
アウタークラスの個人情報もできます
内側の内部の内側のクラスのプライベートメンバーにアクセスする
クラス。 */
プライベートソフトバリュー(オブジェクトK、オブジェクトキー、リファレンスキューQ){
スーパー(k、q);
this .key = key;
}
}
/**ここでリファレンスキューを通過してごみを削除します
HashmapからSoftValueオブジェクトを検索して収集しました
softvalue.keyデータメンバーを使用してください。
public void processqueue(){
SoftValue SV;
while((sv =(softvalue)queue.poll())!= null){
if(sv.get()== null){
log.e( "processqueue"、 "null");
} それ以外 {
log.e( "processqueue"、 "not null");
}
Hash.Remove(sv.Key);
log.e( "softhashmap"、 "release" + sv.key);
}
}
/**ここで、キー、バリューペアをハッシュマップに使用して配置します
ソフトバリューオブジェクト。
パブリックオブジェクトプット(オブジェクトキー、オブジェクト値){
ProcessQueue();
log.e( "softhashmap"、 "put into" + key);
return hash.put(key、new softValue(value、key、queue));
}
パブリックオブジェクト削除(オブジェクトキー){
ProcessQueue();
Hash.Remove(key)を返す;
}
public void clear(){
hardcache.clear();
ProcessQueue();
hash.clear();
}
public int size(){
ProcessQueue();
hash.size()を返します。
}
public set entreperset(){
//いいえ、あなたはそれをしないかもしれません!!!
新しいunsupportedoperationexception()を投げる;
}
}