18

簡単な例があります。この例では、 10000000個のランダムな整数を含むArrayList<Integer>ファイルからをロードします。f

doLog("Test 2");
{
    FileInputStream fis = new FileInputStream(f);
    ObjectInputStream ois = new ObjectInputStream(fis);
    List<Integer> l = (List<Integer>) ois.readObject();
    ois.close();
    fis.close();
    doLog("Test 2.1");
    //l = null; 
    doLog("Test 2.2");
}
doLog("Test 2.3");
System.gc();
doLog("Test 2.4");

私が持っているときl = null、私はこのログを取得します:

Test 2                          Used Mem = 492 KB   Total Mem = 123 MB
Test 2.1                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.2                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.3                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.4                        Used Mem = 493 KB   Total Mem = 123 MB

しかし、それを削除すると、代わりにこのログを取得します。

Test 2                          Used Mem = 492 KB   Total Mem = 123 MB
Test 2.1                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.2                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.3                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.4                        Used Mem = 44 MB    Total Mem = 123 MB

Used Memoryによって計算されます:runTime.totalMemory() - runTime.freeMemory()

質問:l = null;が存在する場合、メモリリークはありますか ?lアクセスできないのに、なぜ解放できないのですか?

4

4 に答える 4

28

上記のコードにはメモリリークはありません。

コードブロックをで囲んだままにする{}と、変数lはスコープ外になり、最初Listに設定したかどうかに関係なく、ガベージコレクションの候補になりますnull

ただし、コードブロックの後、メソッドが返されるまで、はinvisibleListと呼ばれる状態になります。これは事実ですが、JVMが参照を自動的にヌルにしてのメモリを収集する可能性はほとんどありません。したがって、明示的に設定すると、メモリ計算を行う前にJVMがメモリを収集するのに役立ちます。それ以外の場合は、メソッドが戻ったときに自動的に発生します。 Listl = null

ガベージコレクターがいつ実行されるか正確にはわからないため、コードの実行ごとに異なる結果が得られる可能性があります。を使用して実行する必要があると考えることを提案できます(設定しなくても非表示をSystem.gc()収集する可能性もあります)が、約束はありません。System.gc()のjavadocに記載されています。 Listl = null

gcメソッドを呼び出すことは、Java仮想マシンが、現在占有しているメモリを迅速に再利用できるようにするために、未使用のオブジェクトのリサイクルに労力を費やすことを示唆しています。制御がメソッド呼び出しから戻ると、Java仮想マシンは、破棄されたすべてのオブジェクトからスペースを再利用するために最善を尽くしました。

于 2012-08-19T20:45:42.637 に答える
4

ここには少しセマンティクスの問題があると思います。「メモリ リーク」とは、一般に、プログラム (ソフトウェアなど) によって一部のデータがメモリに格納され、そのプログラムがメモリ内のデータにアクセスしてクリーンアップできない状態になり、状況に陥ることを意味します。そのメモリを将来の使用のために要求することはできません。私が知る限り、これは一般的な定義です。

「メモリ リーク」という用語の実際の使用は、通常、開発者がヒープに配置する予定のデータにメモリを手動で割り当てる必要があるプログラミング言語に関連しています。このような言語は、C、C++、Objective-C (*) などです。たとえば、「malloc」コマンドまたは「new」演算子はどちらも、ヒープ メモリ空間に配置されるクラスのインスタンスにメモリを割り当てます。そのような言語では、このように割り当てられたインスタンスへのポインターを保持する必要があります。後でそれらによって使用されたメモリをクリーンアップしたい場合 (インスタンスが不要になった場合)。上記の例を続けると、「new」を使用してヒープ上に作成されたインスタンスを参照するポインターは、後で「delete」を使用してメモリーから「削除」できます。

したがって、そのような言語の場合、メモリ リークは通常、データがヒープに配置され、その後次のいずれかが行われることを意味します。

  • そのデータへのポインターがなくなった状態に到達するか、
  • そのヒープ上のデータを手動で「割り当て解除」することを忘れる/無視する(ポインターを介して)

さて、このような「メモリ リーク」の定義のコンテキストでは、これは Java ではほとんど発生しません。技術的には、Java では、ガベージ コレクターのタスクは、ヒープに割り当てられたインスタンスが参照されなくなるか範囲外になるかを判断し、それらをクリーンアップすることです。Java には C++ の「削除」コマンドに相当するものはなく、開発者がヒープからインスタンス/データを手動で「割り当て解除」することさえ可能です。インスタンスのすべてのポインタを null にしても、そのインスタンスのメモリがすぐに解放されるわけではありませんが、代わりに「ガベージ コレクション可能」になるだけで、ガベージ コレクタ スレッドがスイープを行うときにメモリをクリーンアップします。

さて、Java で起こりうるもう 1 つのことは、特定のインスタンスへのポインターは、特定の時点以降は必要なくなったとしても、絶対に離さないことです。または、特定のインスタンスに、それらが使用されるものに対して大きすぎるスコープを与えること。このようにして、それらは必要以上に (または永遠に、JDK プロセスが強制終了されるまで永遠に) メモリ内に留まるため、機能的な観点からクリーンアップする必要がある場合でも、ガベージ コレクターによって収集されません。これは、より広い意味での「メモリ リーク」に似た動作につながる可能性があります。「メモリ リーク」とは、単に「不要になったときにメモリにデータを保持し、それをクリーンアップする方法がない」ことを表します。

ご覧のとおり、「メモリリーク」はややあいまいですが、私が見ることができるように、あなたの例にはメモリリークが含まれていません(l = nullを作成しないバージョンでも)。すべての変数は、accolade ブロックで区切られた狭いスコープ内にあり、そのブロック内で使用され、ブロックが終了するとスコープ外になるため、"適切に" ガベージ コレクションされます (機能の観点から)あなたのプログラム)。@Keppil が述べているように: ポインターを null にすると、対応するインスタンスをいつクリーンアップするかについて GC により良いヒントが与えられますが、null にしない場合でも、コードは (不必要に) インスタンスにハングアップしません。そこにメモリリークはありません。

Java メモリ リークの典型的な例は、コードを Java EE アプリケーション サーバーにデプロイした場合です。これにより、アプリケーション サーバーの制御外でスレッドが生成されます (Quartz ジョブを開始するサーブレットをイメージします)。アプリケーションが複数回デプロイおよびアンデプロイされると、一部のスレッドがアンデプロイ時に強制終了されず、デプロイ時に (再) 開始される可能性があります。そのため、それらのスレッドとそれらが作成した可能性のあるインスタンスがメモリ内で無用にハングアップします。

(*) Objective-C の新しいバージョンでは、Java のガベージ コレクション メカニズムと同様の方法で、ヒープ メモリを自動的に管理することもできます。

于 2012-08-19T21:29:38.950 に答える
2

本当の答えは、コードが JIT されない限り、すべてのローカル変数がメソッド本体内で「到達可能」であるということです。

さらに、中括弧はバイトコードではまったく何もしません。それらはソース レベルにのみ存在します。JVM はそれらをまったく認識しません。null に設定lすると、参照がスタックから効果的に解放されるため、実際には GC されます。幸せなこと。

インライン ブロックの代わりに別のメソッドを使用した場合、すべてが予期せず通過したことになります。

コードが JIT され、JVM コンパイラーが到達定義 (これも)を構築している場合、どちらの場合も設定l=nullは効果がなく、メモリーが解放される可能性があります。

于 2012-08-22T16:12:16.530 に答える
1

質問: l = null; を削除する場合 (このコード行はありません)、これはメモリ リークですか?

いいえ、しかし、この「パターン」を実行すると、gc がメモリを要求するのが容易になります

于 2012-08-19T20:54:50.697 に答える