私はインタビューを受けたばかりで、Javaで メモリリーク を作成するように依頼されました。
言うまでもありませんが、作成を開始する方法さえわからない場合は、かなり愚かな気がしました。
例は何でしょうか?
純粋なJavaで真のメモリリーク(コードを実行してもアクセスできないが、まだメモリに格納されているオブジェクト)を作成するための良い方法は次のとおりです。
new byte[1000000]
)を割り当て、それに対する強い参照を静的フィールドに格納してから、それ自体への参照をThreadLocalに格納します。追加のメモリを割り当てるのは任意です(Classインスタンスをリークするだけで十分です)が、リークが非常に速くなります。これは、ThreadLocalがオブジェクトへの参照を保持し、オブジェクトがそのClassへの参照を保持し、オブジェクトがClassLoaderへの参照を保持するためです。 ClassLoaderは、ロードしたすべてのクラスへの参照を保持します。
(特にJava 7より前の多くのJVM実装では、クラスとClassLoaderがpermgenに直接割り当てられ、GCが行われることは決してなかったため、さらに悪くなりました。クラスオブジェクトが回収されないようにします。)
このパターンのバリエーションは、ThreadLocalsを頻繁に使用するアプリケーションを頻繁に再デプロイすると、アプリケーションコンテナ(Tomcatなど)がふるいのようにメモリをリークする可能性があることです。 (説明したようにアプリケーションコンテナはスレッドを使用しているので、アプリケーションを再デプロイするたびに新しいClassLoaderが使用されます。)
更新 :多くの人がそれを求め続けているので、 これが実際の動作を示すコード例です 。
オブジェクト参照を保持する静的フィールド[esp final field]
class MemorableClass {
static final ArrayList list = new ArrayList(100);
}
長い文字列で String.intern()
を呼び出す
String str=readString(); // read lengthy string any source db,textbox/jsp etc..
// This will place the string in memory pool from which you can't remove
str.intern();
(非公開)オープンストリーム(ファイル、ネットワークなど)
try {
BufferedReader br = new BufferedReader(new FileReader(inputFile));
...
...
} catch (Exception e) {
e.printStacktrace();
}
未接続接続
try {
Connection conn = ConnectionFactory.getConnection();
...
...
} catch (Exception e) {
e.printStacktrace();
}
JVMのガベージコレクタからアクセスできない領域 、ネイティブメソッドを通じて割り当てられたメモリなど
Webアプリケーションでは、アプリケーションが明示的に停止または削除されるまで、一部のオブジェクトはアプリケーションスコープに格納されます。
getServletContext().setAttribute("SOME_MAP", map);
不正または不適切なJVMオプション (未使用のクラスガベージコレクションを妨げるIBM JDKのnoclassgc
オプションなど)
IBM jdk settings を参照してください。
簡単なことは、正しくない(または存在しない)hashCode()
またはequals()
を指定してHashSetを使用してから、「重複」を追加し続けることです。必要に応じて重複を無視するのではなく、セットは増え続けるだけで、削除することはできません。
これらの悪いキー/要素をハングアップさせたい場合は、次のような静的フィールドを使用できます。
class BadKey {
// no hashCode or equals();
public final String key;
public BadKey(String key) { this.key = key; }
}
Map map = System.getProperties();
map.put(new BadKey("key"), "value"); // Memory leak even if your threads die.
以下は、忘れられたリスナー、静的参照、ハッシュマップ内の偽の/変更可能なキー、またはライフサイクルを終わらせる機会がまったくないままのスレッドの標準的なケースの他に、Javaがリークする明白でないケースです。
File.deleteOnExit()
- 常に文字列をリークします char[]
もコピーされるため、後者は を適用しません。 @ダニエル、投票の必要はありません。私は主にアンマネージドスレッドの危険性を示すためにスレッドに集中します。
Runtime.addShutdownHook
そして削除しないでください...そしてremoveShutdownHookを使用しても、ThreadGroupクラスの未起動スレッドに関するバグのために収集されない可能性があるため、事実上ThreadGroupをリークします。 JGroupにGossipRouterのリークがあります。
Thread
を作成するが開始はしませんが、上記と同じカテゴリに入ります。
スレッドの作成はContextClassLoader
とAccessControlContext
、さらにThreadGroup
と任意のInheritedThreadLocal
を継承します。これらの参照はすべて、クラスローダーによってロードされたクラス全体とすべての静的参照、およびja-jaと共に潜在的なリークです。この効果は、非常に単純なThreadFactory
インターフェースを備えたj.u.c.Executorフレームワーク全体で特に顕著ですが、ほとんどの開発者は潜在的な危険性についての手がかりを持っていません。また、多くのライブラリが要求に応じてスレッドを起動します(業界で人気のあるライブラリが多すぎます)。
ThreadLocal
キャッシュ。それらは多くの場合悪である。 ThreadLocalをベースにした単純なキャッシュがかなり多く見られたことは間違いありません。悪いことに、スレッドが期待される以上の状況を続けている場合、それは純粋なNiceの小さなリークです。本当に必要な場合以外はThreadLocalキャッシュを使用しないでください。
ThreadGroupにスレッド自体がないときにThreadGroup.destroy()
を呼び出すが、それでも子ThreadGroupを保持します。 ThreadGroupがその親から削除するのを妨げるような悪いリークですが、すべての子は列挙不可能になります。
WeakHashMapと値(in)を使用すると、キーが直接参照されます。これはヒープダンプなしで見つけるのは難しいです。これは、保護されたオブジェクトへのハードリファレンスを保持する可能性があるすべての拡張Weak/SoftReference
に適用されます。
HTTP(S)プロトコルでJava.net.URL
を使用し、(!)からリソースをロードします。これは特別で、KeepAliveCache
はシステムThreadGroupに現在のスレッドのコンテキストクラスローダーをリークする新しいスレッドを作成します。生きているスレッドが存在しないとき、スレッドは最初のリクエストで作成されます、それであなたはラッキーになるか単にリークするかもしれません。 リークはJava 7ではすでに修正されており、スレッドを作成するコードによってコンテキストクラスローダーが正しく削除されます。 あといくつかのケースがあります(imageFetcherのような、 同様のスレッドを作成することも修正されました )。
InflaterInputStream
を使用して、new Java.util.Zip.Inflater()
をコンストラクタに渡し(PNGImageDecoder
など)、インフレータのend()
を呼び出さないようにします。 new
だけでコンストラクタを渡しても、チャンスはありません。そして、ストリーム上でclose()
を呼び出しても、手動でコンストラクタパラメータとして渡された場合は、インフレータは閉じません。ファイナライザによってリリースされるので、これは本当のリークではありません。その瞬間まで、それはネイティブメモリを非常にひどく食べます、それはLinux oom_killerが無責任でプロセスを強制終了させることができます。主な問題は、Javaでのファイナライズが非常に信頼できず、G1では7.0.2まで悪化したことです。物語の教訓:できるだけ早くネイティブリソースを解放する。ファイナライザはあまりにも貧弱です。
Java.util.Zip.Deflater
の場合も同じです。これは、DeflaterがJavaではメモリを大量に消費するため、さらに悪いことになります。つまり、常に15ビット(最大)および8メモリレベル(9は最大)を使用し、数百KBのネイティブメモリを割り当てます。幸い、Deflater
はあまり使われていませんし、私の知る限りJDKには誤用がありません。手動でDeflater
またはInflater
を作成する場合は、必ずend()
を呼び出してください。最後の2つの最良の部分: 利用可能な通常のプロファイリングツールでそれらを見つけることができません。
(私は私が要求に応じて遭遇した時間の浪費をさらに追加することができます)
頑張って安全を守ってください。リークは悪です!
ここのほとんどの例は「複雑すぎます」。彼らはEdgeのケースです。これらの例では、プログラマーは(equals/hashcodeを再定義しないように)間違いをしたか、JVM/Javaのコーナーケース(静的クラスのロード)に噛み付かれました。インタビュアーが望んでいるような例ではなく、最も一般的な例でもないと思います。
しかし、メモリリークは本当に簡単な場合があります。ガベージコレクタは、参照されなくなったものだけを解放します。 Java開発者としての私たちはメモリを気にしません。必要に応じて割り当て、自動的に解放させます。いいですね。
しかし、寿命の長いアプリケーションは状態を共有する傾向があります。それは何でも、static、singletons ...でもよくありますが、重要でないアプリケーションは複雑なオブジェクトをグラフにする傾向があります。参照をnullに設定するのを忘れたり、より頻繁にコレクションから1つのオブジェクトを削除するのを忘れるだけで、メモリリークが発生します。
もちろん、あらゆる種類のリスナー(UIリスナーなど)、キャッシュ、または長期にわたる共有状態では、適切に処理しないとメモリリークが発生する傾向があります。理解すべきことは、これはJavaのコーナーケースでもガベージコレクタの問題でもないということです。それはデザインの問題です。長寿命のオブジェクトにリスナーを追加するように設計していますが、不要になったときにはリスナーを削除しません。オブジェクトをキャッシュしますが、それらをキャッシュから削除する方法はありません。
計算に必要な以前の状態を格納する複雑なグラフがあるかもしれません。しかし、前の州自体は前の州とリンクしています。
SQL接続やファイルを閉じる必要があるように。適切な参照をnullに設定し、コレクションから要素を削除する必要があります。適切なキャッシュ戦略(最大メモリサイズ、要素数、またはタイマー)を設定します。リスナーへの通知を許可するすべてのオブジェクトは、addListenerメソッドとremoveListenerメソッドの両方を提供する必要があります。そしてこれらの通知が使われなくなったら、リスナーリストをクリアしなければなりません。
メモリリークは本当に可能であり、完全に予測可能です。特別な言語機能やコーナーケースは必要ありません。メモリリークは、何かが足りない可能性があること、または設計上の問題であることを示すものです。
答えはインタビュアーが彼らが尋ねていると思ったことに完全に依存しています。
実際にはJavaがリークする可能性はありますか?もちろんそうです、そして他の答えにはたくさんの例があります。
しかし、質問されているかもしれない複数のメタ質問がありますか?
私はあなたのメタ質問を「このインタビューの状況で使用できたはずの答えは何か」と読んでいます。したがって、私はJavaではなくインタビューのスキルに焦点を当てます。私はあなたがインタビューで質問に対する答えを知らないという状況を繰り返すことがあなたがJavaリークを作る方法を知る必要がある場所にいることよりもあなたがたぶん本当らしいと信じます。それで、うまくいけば、これは役立つでしょう。
面接のために開発できる最も重要なスキルの1つは、質問に積極的に耳を傾けることと、インタビュアーと協力して彼らの意図を引き出すことです。これはあなたが彼らが望むように彼らの質問に答えることを可能にするだけでなく、あなたがいくつかの重要なコミュニケーションスキルを持っていることも示しています。そして、同じように才能のある多くの開発者の間での選択になると、私は彼らが毎回対応する前に耳を傾け、考え、そして理解する人を雇うつもりです。
あなたが _ jdbc _ を理解していないならば、以下はかなり無意味な例です。あるいは少なくともConnection
の実装に頼るのではなく、JDBCがStatement
、ResultSet
、finalize
のインスタンスを破棄または参照を失う前に閉じることをJDBCがどのように期待するか。
void doWork()
{
try
{
Connection conn = ConnectionFactory.getConnection();
PreparedStatement stmt = conn.preparedStatement("some query"); // executes a valid query
ResultSet rs = stmt.executeQuery();
while(rs.hasNext())
{
... process the result set
}
}
catch(SQLException sqlEx)
{
log(sqlEx);
}
}
上記の問題点は、Connection
オブジェクトが閉じられていないため、ガベージコレクタが到達して到達不能になるまで、物理的な接続が開いたままになることです。 GCはfinalize
メソッドを呼び出しますが、少なくともConnection.close
が実装されているのと同じ方法ではないfinalize
を実装していないJDBCドライバがあります。その結果、到達不能なオブジェクトが収集されるためにメモリが回収されますが、Connection
オブジェクトに関連付けられているリソース(メモリを含む)は単に回収されない可能性があります。
Connection
のfinalize
メソッドですべてがクリーンアップされない場合、実際にはデータベースサーバへの物理的な接続が数回ガベージコレクションサイクル続くことがあります。そして)閉じる必要があります。
JDBCドライバがfinalize
を実装したとしても、ファイナライズ中に例外がスローされる可能性があります。その結果、finalize
は一度だけ呼び出されることが保証されているので、今は "休止中"のオブジェクトに関連付けられたメモリは再利用されません。
オブジェクトのファイナライズ中に例外が発生するという上記のシナリオは、メモリリークを引き起こす可能性がある別のシナリオ(オブジェクトの復活)に関連しています。オブジェクトの復活は、他のオブジェクトから、ファイナライズされないようにオブジェクトへの強い参照を作成することによって意図的に行われることがよくあります。オブジェクトの復活が悪用されると、他のメモリリークの原因と組み合わせてメモリリークが発生します。
あなたが想起させることができるたくさんのより多くの例があります - のように
List
インスタンスを管理します(ただし、必要なくなった要素は削除します)。Socket
sまたはFile
sを開きますが、不要になった場合は閉じません(Connection
クラスを含む上記の例と同様)。おそらく、潜在的なメモリリークの最も簡単な例の1つ、およびその回避方法は、ArrayList.remove(int)の実装です。
public E remove(int index) {
RangeCheck(index);
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index + 1, elementData, index,
numMoved);
elementData[--size] = null; // (!) Let gc do its work
return oldValue;
}
自分で実装している場合は、使用されなくなった配列要素(elementData[--size] = null
)を消去することを考えましたか?その参照は巨大なオブジェクトを生かし続けるかもしれません...
不要になったオブジェクトへの参照を保持していると、メモリリークが発生します。メモリリークがJavaでどのように発生するか、およびそれに対して何ができるかの例については、 Javaプログラムでのメモリリークの処理 を参照してください。
Sun.misc.Unsafe classでメモリリークを起こすことができます。実際、このサービスクラスはさまざまな標準クラス(たとえば Java.nio classes)で使用されています。 このクラスのインスタンスを直接作成することはできません しかし、 リフレクションを使用してそれを行うことができます 。
コードがEclipseでコンパイルされないIDE - コマンドjavac
を使用してコンパイルします(コンパイル中に警告が表示されます)
import Java.lang.reflect.Constructor;
import Java.lang.reflect.Field;
import Sun.misc.Unsafe;
public class TestUnsafe {
public static void main(String[] args) throws Exception{
Class unsafeClass = Class.forName("Sun.misc.Unsafe");
Field f = unsafeClass.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
System.out.print("4..3..2..1...");
try
{
for(;;)
unsafe.allocateMemory(1024*1024);
} catch(Error e) {
System.out.println("Boom :)");
e.printStackTrace();
}
}
}
ここから私の答えをコピーすることができます: Javaでメモリリークを引き起こす最も簡単な方法は?
「コンピュータサイエンスにおけるメモリリーク(またはこの文脈ではリーク)は、コンピュータプログラムがメモリを消費するが、それをオペレーティングシステムに解放することができないときに発生します。」 (ウィキペディア)
簡単な答えは:あなたはできません。 Javaは自動メモリ管理を行い、不要なリソースを解放します。これを止めることはできません。それは常にリソースを解放することができるでしょう。手動でメモリ管理を行うプログラムでは、これは異なります。 malloc()を使用してC言語でメモリを確保できます。メモリを解放するには、mallocが返したポインタが必要で、それにfree()を呼び出します。あなたがもうポインタを持っていない(上書きされた、または寿命を超えた)場合でも、あなたは残念ながらこのメモリを解放することができないので、メモリリークがあります。
これまでのところ他のすべての答えは私の定義では本当にメモリリークではありません。彼ら全員は、無意味なものでメモリを本当に速く満たすことを目指しています。しかし、いつでも作成したオブジェクトを間接参照してメモリを解放することができます - > NO LEAK。 acconradの答え 彼の解決策は、それを無限ループに強制することによってガベージコレクタを単に "クラッシュ"させることであるので、私は認めざるを得ないが、非常に近い。
長い答えは次のとおりです。JNIを使用してJava用のライブラリを作成すると、メモリリークが発生する可能性があります。これには、手動によるメモリ管理が必要で、メモリリークが発生する可能性があります。このライブラリを呼び出すと、Javaプロセスはメモリをリークします。または、JVMにバグがあり、JVMがメモリを失う可能性があります。 JVMにはおそらくバグがあります。ガベージコレクションはそれほど簡単ではないので、いくつかの既知のバグもあるかもしれませんが、それでもまだバグです。設計上、これは不可能です。あなたはそのようなバグによって影響されるいくつかのJavaコードを求めているかもしれません。すみません、私はそれを知りません、そしてそれはとにかく次のJavaバージョンでもうバグではないかもしれません。
これは http://wiki.Eclipse.org/Performance_Bloopers#String.substring.28.29 を介した単純な/不吉なものです。
public class StringLeaker
{
private final String muchSmallerString;
public StringLeaker()
{
// Imagine the whole Declaration of Independence here
String veryLongString = "We hold these truths to be self-evident...";
// The substring here maintains a reference to the internal char[]
// representation of the original string.
this.muchSmallerString = veryLongString.substring(0, 1);
}
}
部分文字列は元のはるかに長い文字列の内部表現を参照するため、元の文字列はメモリに残ります。したがって、StringLeakerを使用している限り、元の文字列全体がメモリ内に存在することになります。ただし、1文字の文字列をそのまま使用していると考える場合もあります。
元の文字列への不要な参照を格納しないようにするには、次のようにします。
...
this.muchSmallerString = new String(veryLongString.substring(0, 1));
...
さらに悪いことに、サブストリングを.intern()
することもできます。
...
this.muchSmallerString = veryLongString.substring(0, 1).intern();
...
そうすることで、StringLeakerインスタンスが破棄された後でも、元の長い文字列と派生部分文字列の両方がメモリに保持されます。
GUIコードでのこれの一般的な例は、ウィジェット/コンポーネントを作成して静的/アプリケーションスコープのオブジェクトにリスナーを追加し、その後ウィジェットが破棄されてもリスナーを削除しない場合です。メモリリークが発生するだけでなく、リスニングしているものがすべてイベントを発生させると、以前のリスナーもすべて呼び出されるため、パフォーマンスが低下します。
任意のサーブレットコンテナ(Tomcat、Jetty、Glassfishなど)で実行されているWebアプリケーションをすべて使用します。アプリを10回または20回続けて再デプロイします(サーバーの自動デプロイディレクトリにあるWARに触れるだけで十分な場合があります)。
誰かが実際にこれをテストしていない限り、アプリケーションは自動的にクリーンアップすることに注意を払っていないので、2、3回の再デプロイの後にOutOfMemoryErrorを受け取る可能性が高いです。このテストであなたのサーバーにバグが見つかるかもしれません。
問題は、コンテナの有効期間がアプリケーションの有効期間より長いことです。あなたは、コンテナがあなたのアプリケーションのオブジェクトまたはクラスに対して持っているかもしれないすべての参照がガベージコレクションされることができることを確認しなければなりません。
Webアプリケーションのアンデプロイ後も参照が1つしかない場合、対応するクラスローダー、つまりWebアプリケーションのすべてのクラスをガベージコレクションすることはできません。
アプリケーションによって開始されたスレッド、ThreadLocal変数、ロギングアペンダは、クラスローダリークを引き起こす一般的な疑いのあるものです。
JNIを介して外部のネイティブコードを使用することによって?
純粋なJavaでは、それはほとんど不可能です。
しかし、これは「標準」タイプのメモリリークに関するものです。もうメモリにアクセスできない場合でも、アプリケーションによって所有されています。代わりに、未使用のオブジェクトへの参照を保持するか、後でそれらを閉じずにストリームを開くことができます。
PermGenとXMLの解析に関連して、ニースの "メモリリーク"がありました。私たちが使ったXMLパーサ(どちらを使ったのか覚えていません)はタグ名に対してString.intern()をして比較を速くしました。私たちの顧客の1人は、XMLの属性やテキストではなくタグ名としてデータ値を保存するという素晴らしいアイデアを持っていたので、私たちは次のようなドキュメントを持っていました:
<data>
<1>bla</1>
<2>foo</>
...
</data>
実際、彼らは数字ではなく、より長いテキストID(約20文字)を使用していました。これはユニークで、1日に1000万から1500万の割合で入ってきました。これは1日に200 MBのゴミとなり、これは二度と必要とされず、GCされることもありません(PermGenにあるため)。 permgenを512 MBに設定したので、メモリ不足例外(OOME)が発生するまでに約2日かかりました。
私は最近log4jによる方法で引き起こされたメモリリークの状況に遭遇しました。
Log4jには ネスト診断コンテキスト(NDC)と呼ばれるこのメカニズムがあります。 はインターリーブされたログ出力をさまざまなソースから区別するための手段です。 NDCが機能する粒度はスレッドであるため、ログ出力をさまざまなスレッドから別々に区別します。
スレッド固有のタグを格納するために、log4jのNDCクラスは(スレッドIDとは対照的に)Threadオブジェクト自身によってキー設定されたHashtableを使用します。オブジェクトもメモリに残ります。私たちのWebアプリケーションでは、ログを単一の要求と区別するために、NDCを使用してlogoutputsに要求IDをタグ付けしています。 NDCタグをスレッドに関連付けるコンテナは、リクエストからの応答を返す際にもNDCタグを削除します。次のコードのように、リクエストの処理中に子スレッドが生成されたときに問題が発生しました。
pubclic class RequestProcessor {
private static final Logger logger = Logger.getLogger(RequestProcessor.class);
public void doSomething() {
....
final List<String> hugeList = new ArrayList<String>(10000);
new Thread() {
public void run() {
logger.info("Child thread spawned")
for(String s:hugeList) {
....
}
}
}.start();
}
}
そのため、NDCコンテキストは、生成されたインラインスレッドに関連付けられていました。このNDCコンテキストの鍵となったスレッドオブジェクトは、largeListオブジェクトがぶら下がっているインラインスレッドです。したがって、スレッドが実行していたことを実行し終えた後でも、hugeListへの参照はNDCコンテキストHastableによって存続しているため、メモリリークが発生していました。
メモリリークとは
典型例:
オブジェクトのキャッシュは、物事を台無しにするための良い出発点です。
private static final Map<String, Info> myCache = new HashMap<>();
public void getInfo(String key)
{
// uses cache
Info info = myCache.get(key);
if (info != null) return info;
// if it's not in cache, then fetch it from the database
info = Database.fetch(key);
if (info == null) return null;
// and store it in the cache
myCache.put(key, info);
return info;
}
あなたのキャッシュはどんどん大きくなっていきます。そしてすぐにデータベース全体がメモリに吸い込まれます。より良い設計はLRUMapを使用します(最近使用されたオブジェクトのみをキャッシュに保持します)。
確かに、物事はもっと複雑にすることができます。
よくあること:
このInfoオブジェクトが他のオブジェクトへの参照を持ち、それが他のオブジェクトへの参照を持つ場合。ある意味では、これはある種のメモリリークであると考えることもできます(設計上の問題による)。
内部クラスの例を誰も使用しなかったのは面白いと思いました。内部クラスがある場合包含クラスへの参照を本質的に維持します。もちろん、Javaは最終的にそれをクリーンアップするので、技術的にはメモリリークではありません。しかし、これは予想以上に長くクラスをハングアップさせる可能性があります。
public class Example1 {
public Example2 getNewExample2() {
return this.new Example2();
}
public class Example2 {
public Example2() {}
}
}
Example1を呼び出し、Example1を破棄してExample2を取得した場合でも、本質的にはまだExample1オブジェクトへのリンクがあります。
public class Referencer {
public static Example2 GetAnExample2() {
Example1 ex = new Example1();
return ex.getNewExample2();
}
public static void main(String[] args) {
Example2 ex = Referencer.GetAnExample2();
// As long as ex is reachable; Example1 will always remain in memory.
}
}
また、特定の期間より長く存在する変数があると噂が聞こえます。 Javaは、それが常に存在することを前提としており、コードで到達できなくなっても実際にはクリーンアップしようとしません。しかし、それは完全に検証されていません。
静的マップを作成し、それにハード参照を追加し続けます。それらは決してGCされません。
public class Leaker {
private static final Map<String, Object> CACHE = new HashMap<String, Object>();
// Keep adding until failure.
public static void addToCache(String key, Object value) { Leaker.CACHE.put(key, value); }
}
インタビュアーは、おそらく以下のコードのような循環参照を探していました(これは、偶然にも参照カウントを使用していた非常に古いJVMのメモリリークのみです。しかし、それはかなりあいまいな質問です。そのため、JVMのメモリ管理についての理解を披露する絶好の機会です。
class A {
B bRef;
}
class B {
A aRef;
}
public class Main {
public static void main(String args[]) {
A myA = new A();
B myB = new B();
myA.bRef = myB;
myB.aRef = myA;
myA=null;
myB=null;
/* at this point, there is no access to the myA and myB objects, */
/* even though both objects still have active references. */
} /* main */
}
次に、参照カウントを使用すると、上記のコードでメモリリークが発生することを説明できます。しかし、最近のほとんどのJVMはもはや参照カウントを使用せず、実際にはこのメモリーを収集するスイープガベージコレクタを使用しています。
次に、以下のように、基礎となるネイティブリソースを持つObjectの作成について説明します。
public class Main {
public static void main(String args[]) {
Socket s = new Socket(InetAddress.getByName("google.com"),80);
s=null;
/* at this point, because you didn't close the socket properly, */
/* you have a leak of a native descriptor, which uses memory. */
}
}
これは技術的にはメモリリークであると説明できますが、実際にはJVM内のネイティブコードが基盤となるネイティブリソースを割り当てているために発生します。
結局のところ、現代のJVMでは、通常のJVMの認識範囲外のネイティブリソースを割り当てるJavaコードを書く必要があります。
そのクラスのfinalizeメソッドでクラスの新しいインスタンスを作成することでムービングメモリリークを作成できます。ファイナライザが複数のインスタンスを作成した場合はボーナスポイント。これは、ヒープサイズに応じて数秒から数分の間にヒープ全体をリークする単純なプログラムです。
class Leakee {
public void check() {
if (depth > 2) {
Leaker.done();
}
}
private int depth;
public Leakee(int d) {
depth = d;
}
protected void finalize() {
new Leakee(depth + 1).check();
new Leakee(depth + 1).check();
}
}
public class Leaker {
private static boolean makeMore = true;
public static void done() {
makeMore = false;
}
public static void main(String[] args) throws InterruptedException {
// make a bunch of them until the garbage collector gets active
while (makeMore) {
new Leakee(0).check();
}
// sit back and watch the finalizers chew through memory
while (true) {
Thread.sleep(1000);
System.out.println("memory=" +
Runtime.getRuntime().freeMemory() + " / " +
Runtime.getRuntime().totalMemory());
}
}
}
誰もがいつもネイティブコードの道を忘れる。これがリークの簡単な式です。
malloc
を呼び出します。 free
を呼び出さないでください。ネイティブコードでのメモリ割り当てはJVMヒープから来ることを忘れないでください。
私は誰もこれを言っていないと思います:finalize()がこの参照をどこかに保存するようにfinalize()メソッドをオーバーライドすることでオブジェクトを復活させることができます。ガベージコレクタはオブジェクトに対して一度だけ呼び出されるので、その後オブジェクトは破棄されません。
私は最近、もっと微妙な種類のリソースリークに遭遇しました。クラスローダーのgetResourceAsStreamを介してリソースを開きますが、入力ストリームハンドルが閉じられていないことが起こりました。
うーん、あなたは何をばかだと言うかもしれません。
これがおもしろいのは、JVMのヒープからではなく、基礎となるプロセスのヒープメモリをリークする可能性があるからです。
必要なのは、その中にJavaコードから参照されるファイルを含むjarファイルだけです。 jarファイルが大きいほど、メモリーは早く割り当てられます。
以下のクラスを使って、このようなjarを簡単に作成できます。
import Java.io.File;
import Java.io.FileOutputStream;
import Java.io.IOException;
import Java.util.Zip.ZipEntry;
import Java.util.Zip.ZipOutputStream;
public class BigJarCreator {
public static void main(String[] args) throws IOException {
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("big.jar")));
zos.putNextEntry(new ZipEntry("resource.txt"));
zos.write("not too much in here".getBytes());
zos.closeEntry();
zos.putNextEntry(new ZipEntry("largeFile.out"));
for (int i=0 ; i<10000000 ; i++) {
zos.write((int) (Math.round(Math.random()*100)+20));
}
zos.closeEntry();
zos.close();
}
}
BigJarCreator.Javaという名前のファイルに貼り付け、コンパイルしてコマンドラインから実行します。
javac BigJarCreator.Java
java -cp . BigJarCreator
その他:現在の作業ディレクトリに2つのファイルがあるjarアーカイブが見つかりました。
2番目のクラスを作りましょう:
public class MemLeak {
public static void main(String[] args) throws InterruptedException {
int ITERATIONS=100000;
for (int i=0 ; i<ITERATIONS ; i++) {
MemLeak.class.getClassLoader().getResourceAsStream("resource.txt");
}
System.out.println("finished creation of streams, now waiting to be killed");
Thread.sleep(Long.MAX_VALUE);
}
}
このクラスは基本的には何もしませんが、参照されていないInputStreamオブジェクトを作成します。これらのオブジェクトはすぐにガベージコレクションされるため、ヒープサイズには影響しません。この例では、jarファイルから既存のリソースをロードすることが重要です。サイズはここで問題になります。
よくわからない場合は、上記のクラスをコンパイルして起動してみてください。ただし、適切なヒープサイズ(2 MB)を選択してください。
javac MemLeak.Java
java -Xmx2m -classpath .:big.jar MemLeak
ここでOOMエラーは発生しません。参照が保持されないため、上記の例でITERATIONSをどの程度大きく選択しても、アプリケーションは実行を続けます。アプリケーションがwaitコマンドを実行しない限り、プロセスのメモリ消費量(上部(RES/RSS)またはプロセスエクスプローラに表示されます)が増加します。上記の設定では、メモリ内に約150 MBを割り当てます。
アプリケーションを安全に動作させるには、入力ストリームが作成された場所で閉じます。
MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();
繰り返し回数に関係なく、プロセスは35 MBを超えることはありません。
とてもシンプルで驚くべきことです。
多くの人が示唆しているように、JDBCの例のように、リソースリークはかなり起こりやすいものです。実際のメモリリークは少し困難です - 特にあなたがあなたのためにそれをするためにJVMの壊れた部分に頼っていないなら...
非常に大きなフットプリントを持つオブジェクトを作成してからそれらにアクセスできないというアイデアも、実際のメモリリークではありません。アクセスできないものがあればガベージコレクションされ、アクセスできるものがあればリークではありません...
が を使用して動作するための1つの方法 - それでもまだ機能するかどうかはわかりません - 3つの深さの循環チェーンを用意することです。オブジェクトAがオブジェクトBを参照し、オブジェクトBがオブジェクトCを参照し、オブジェクトCがオブジェクトAを参照しているのと同様に、GCはA <--> Bのように2つの深いチェーンを知っている - AとBが他のものからアクセスできない場合でも安全に収集できますが、3方向チェーンを処理できませんでした...
メモリがリークする状況はたくさんあります。私が遭遇したもの、それは公開されてはならず、他の場所で使用されるべきではない地図を公開します。
public class ServiceFactory {
private Map<String, Service> services;
private static ServiceFactory singleton;
private ServiceFactory() {
services = new HashMap<String, Service>();
}
public static synchronized ServiceFactory getDefault() {
if (singleton == null) {
singleton = new ServiceFactory();
}
return singleton;
}
public void addService(String name, Service serv) {
services.put(name, serv);
}
public void removeService(String name) {
services.remove(name);
}
public Service getService(String name, Service serv) {
return services.get(name);
}
// the problematic api, which expose the map.
//and user can do quite a lot of thing from this api.
//for example, create service reference and forget to dispose or set it null
//in all this is a dangerous api, and should not expose
public Map<String, Service> getAllServices() {
return services;
}
}
// resource class is a heavy class
class Service {
}
スレッドは終了するまで収集されません。それらは ルーツ のガベージコレクションとして機能します。それらは、単にそれらを忘れることやそれらへの参照をクリアすることによって回収されることがない数少ないオブジェクトのうちの1つです。
検討してください:ワーカースレッドを終了するための基本的なパターンは、スレッドから見た状態変数を設定することです。スレッドは変数を定期的にチェックし、それを終了のシグナルとして使用できます。変数がvolatile
と宣言されていない場合、変数への変更はスレッドによって認識されない可能性があるため、終了することはわかりません。あるいは、一部のスレッドが共有オブジェクトを更新したいが、ロックしようとしている間にデッドロックが発生したとします。
あなたがほんの一握りのスレッドしか持っていないのなら、あなたのプログラムが正しく動作しなくなるので、これらのバグはおそらく明白でしょう。必要に応じてより多くのスレッドを作成するスレッドプールがある場合は、使用されなくなったスレッドやスタックされたスレッドが気付かれずに無期限に蓄積され、メモリリークが発生します。スレッドはアプリケーション内で他のデータを使用する可能性が高いため、それらが直接参照するものはこれまで収集されなくなります。
おもちゃの例として:
static void leakMe(final Object object) {
new Thread() {
public void run() {
Object o = object;
for (;;) {
try {
sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {}
}
}
}.start();
}
System.gc()
を好きなだけ呼び出してください、しかしleakMe
に渡されたオブジェクトは決して死にません。
(*編集*)
有効な例として、スレッドがプールされている環境でThreadLocal変数を使用することが考えられます。
たとえば、サーブレットでThreadLocal変数を使用して他のWebコンポーネントと通信し、スレッドをコンテナで作成し、アイドル状態のスレッドをプールに保持します。 ThreadLocal変数は、正しくクリーンアップされていないと、おそらく同じWebコンポーネントがその値を上書きするまでそこに存在します。
もちろん、一度識別されれば、問題は容易に解決することができます。
巨大なメモリリークを引き起こす可能性があるもう1つの方法は、TreeMap
のMap.Entry<K,V>
への参照を保持することです。
これがTreeMap
sにのみ適用される理由を査定するのは困難ですが、実装を見ることでその理由は次のようになるでしょう。TreeMap.Entry
はその兄弟への参照を格納するので、TreeMap
を収集する準備ができたらしかし、他のクラスがそのMap.Entry
のいずれかへの参照を保持している場合、 全体 Mapはメモリに保持されます。
現実のシナリオ:
大きなTreeMap
データ構造を返すdbクエリがあるとします。要素の挿入順序が保持されるため、人々は通常TreeMap
sを使用します。
public static Map<String, Integer> pseudoQueryDatabase();
クエリが何度も呼び出され、クエリごとに(つまり、返されたMap
ごとに)Entry
をどこかに保存すると、メモリは常に増え続けます。
次のラッパークラスを考えます。
class EntryHolder {
Map.Entry<String, Integer> entry;
EntryHolder(Map.Entry<String, Integer> entry) {
this.entry = entry;
}
}
応用:
public class LeakTest {
private final List<EntryHolder> holdersCache = new ArrayList<>();
private static final int MAP_SIZE = 100_000;
public void run() {
// create 500 entries each holding a reference to an Entry of a TreeMap
IntStream.range(0, 500).forEach(value -> {
// create map
final Map<String, Integer> map = pseudoQueryDatabase();
final int index = new Random().nextInt(MAP_SIZE);
// get random entry from map
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue().equals(index)) {
holdersCache.add(new EntryHolder(entry));
break;
}
}
// to observe behavior in visualvm
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
public static Map<String, Integer> pseudoQueryDatabase() {
final Map<String, Integer> map = new TreeMap<>();
IntStream.range(0, MAP_SIZE).forEach(i -> map.put(String.valueOf(i), i));
return map;
}
public static void main(String[] args) throws Exception {
new LeakTest().run();
}
}
それぞれのpseudoQueryDatabase()
呼び出しの後、map
インスタンスはコレクションの準備ができているはずですが、少なくとも1つのEntry
がどこか別の場所に格納されているので、起こりません。
jvm
設定によっては、OutOfMemoryError
が原因でアプリケーションが初期段階でクラッシュすることがあります。
このvisualvm
グラフから、メモリがどのように増え続けるかがわかります。
ハッシュ化されたデータ構造(HashMap
)でも同じことは起こりません。
これはHashMap
を使ったときのグラフです。
解決策は? Map.Entry
を保存するのではなく、(おそらく既に行っているように)単にキー/値を保存するだけです。
もっと広範なベンチマーク here を書きました。
インタビュアーは循環参照ソリューションを探しているかもしれません。
public static void main(String[] args) {
while (true) {
Element first = new Element();
first.next = new Element();
first.next.next = first;
}
}
これは、参照カウントガベージコレクタの古典的な問題です。あなたはそれからJVMがこの制限を持たないもっと洗練されたアルゴリズムを使うことを丁寧に説明するでしょう。
-Wes Tarle
私が最近修正した例は、新しいGCとImageオブジェクトを作成することですが、dispose()メソッドの呼び出しを忘れています。
GC Javadocスニペット:
インスタンスが不要になった場合、アプリケーションコードは、各インスタンスによって管理されているオペレーティングシステムリソースを解放するために、GC.dispose()メソッドを明示的に呼び出す必要があります。これは、オペレーティングシステムが利用可能なデバイスコンテキストの数が限られているWindows 95およびWindows 98では特に重要です。
画像Javadocスニペット:
インスタンスが不要になった場合、アプリケーションコードはImage.dispose()メソッドを明示的に呼び出して、各インスタンスによって管理されているオペレーティングシステムリソースを解放する必要があります。
JVMで利用可能なツールを使用して、アプリケーションでメモリリークを監視する方法についてアドバイスをしたいと思います。メモリリークの発生方法は示していませんが、最小限のツールでそれを検出する方法を説明しています。
最初にJavaのメモリ消費量を監視する必要があります。
これを行う最も簡単な方法は、JVMに付属のjstatユーティリティを使用することです。
jstat -gcutil <process_id> <timeout>
各世代(Young、Eldery、Old)のメモリ消費量と、ガベージコレクションの時間(Young、Full)を報告します。
Full Garbage Collectionが頻繁に実行され、時間がかかりすぎることがわかったら、アプリケーションでメモリリークが発生していると考えることができます。
その後、jmapユーティリティを使ってメモリダンプを作成する必要があります。
jmap -dump:live,format=b,file=heap.bin <process_id>
それから、例えば、Memory Analyzer、Eclipse Memory Analyzer(MAT)でheap.binファイルを分析する必要があります。
MATはメモリを分析し、メモリリークについての疑わしい情報を提供します。
理論的にはできません。 Javaメモリモデルはそれを防ぎます。ただし、Javaを実装する必要があるため、使用できるいくつかの注意点があります。あなたが使うことができるものに依存します:
ネイティブを使用できる場合は、後で放棄しないでメモリを割り当てることができます。
それが手に入らない場合、Javaについての汚い小さな秘密があり、それは多くの人に知られていません。 GCで管理されていないダイレクトアクセスアレイを要求することができます。そのため、メモリリークを起こすのに簡単に使用できます。これはDirectByteBuffer(http://download.Oracle.com/javase/1.5.0/docs/api/Java/nio/ByteBuffer.html#allocateDirect(int))によって提供されます。
あなたがそれらのどれも使うことができないならば、あなたはまだGCをだますことによってメモリリークを作ることができます。 JVMは、世代別ガベージコレクションを使用して実装されています。これが意味するのは、ヒープが領域に分割されているということです:若い、大人そして年長者。作成時のオブジェクトは、若い領域から始まります。彼はますます使用されているので、彼は年配者まで大人に成長します。高齢者エリアに到達したオブジェクトは、おそらくゴミ収集されません。オブジェクトが漏洩したことを確認することはできません。停止してGCを清掃すると清掃することができますが、長期間にわたって彼は漏洩します。詳細については(http://Java.Sun.com/docs/hotspot/gc1.4.2/faq.html)を参照してください。
また、クラスオブジェクトはGCされる必要はありません。その方法を教えてください。
終了しないスレッド(runメソッドで無期限にスリープする)参照をなくしてもガベージコレクションにはなりません。あなたが望むようにスレッドオブジェクトが大きくなるようにフィールドを追加することができます。
現在のトップの答えはこれをめぐるより多くのトリックをリストしていますが、これらは冗長に見えます。
Finalizeメソッドから未処理の例外を投げます。
私がJavaで見たメモリリークのほとんどは、プロセスが同期しなくなることに関係しています。
プロセスAはTCPを介してBと通信し、プロセスBに何かを作成するように指示します。 BはリソースにIDを発行します。432423は、Aがオブジェクトに格納し、Bとの会話中に使用するものです。ある時点で、A内のオブジェクトはガベージコレクションによって回収されます。多分別のバグ)。
これで、AはBのRAMに作成されたオブジェクトのIDをもう持たなくなり、BはAがもうオブジェクトを参照していないことを知りません。実際には、オブジェクトはリークされています。
Javaでメモリリークを発生させる方法については多くの回答がありますが、インタビューで尋ねた点に注意してください。
「Javaでメモリリークを起こす方法は?」その目的は開発者の経験の程度を評価することです。
「Javaのメモリリークのトラブルシューティングの経験がありますか?」と聞かれた場合、あなたの答えは単純な「はい」でしょう。私はそれからあなたが私に1つか2つの例を挙げるならば「あなたは私があなたが私にあなたが私に1つまたは2つの例を与えるだろうというあなたが悩んでいる例を与える?.
しかし、インタビュアーが「Javaでメモリリークを発生させる方法」と尋ねたとき期待される答えはこれらの行に沿って続くべきです:
開発者がこの考え方に従わなかった場合は、「Javaでメモリリークが発生する可能性がある例を教えてください。」、「Javaでメモリリークを修正しなければならなかったのですか」と尋ねました。
私は not Javaでメモリをリークする方法の例を求めていることに注意してください。それはばかげているでしょう。メモリリークのあるコードを効率的に記述できる開発者に興味があるのは誰でしょうか。
最大ヒープサイズがXの場合Y1 .... Ynインスタンス数なので、合計メモリ=インスタンス数X 1インスタンスあたりのバイト数。 Y1 * X1 + ..... + Yn * Xnしたがって、M> Xの場合、ヒープスペースを超えます。以下はコード1の問題である可能性があります。 2.オブジェクトをプールする代わりに毎回インスタンスを作成します。 3.オンデマンドでオブジェクトを作成しません。 4.操作の完了後にオブジェクト参照をnullにする。再度、プログラムで要求されたときに再作成する。
いくつかの提案:
上記の効果は、アプリケーションを再デプロイすることで「改善」できます。
最近これにつまずいた:
詳細な議論については、 http://bugs.Sun.com/bugdatabase/view_bug.do?bug_id=5072161 および関連する問題を参照してください。
1つの可能性は、1つのメソッドのみを提供するArrayListのラッパーを作成することです。1つはArrayListに機能を追加するものです。 ArrayList自体を非公開にします。さて、これらのラッパーオブジェクトの1つを(クラスの静的オブジェクトとして)グローバルスコープで構築し、 final キーワードで修飾します(例:public static final ArrayListWrapper wrapperClass = new ArrayListWrapper()
)。そのため、参照を変更することはできません。つまり、wrapperClass = null
は機能せず、メモリの解放には使用できません。しかし、オブジェクトを追加する以外にwrapperClass
を使って何かをする方法もありません。したがって、wrapperClass
に追加したオブジェクトはリサイクルできません。
Javaのメモリリークは、一般的なC/C++のメモリリークではありません。
JVMの動作を理解するには、 メモリ管理について を読んでください。
基本的に、重要な部分は以下のとおりです。
マークアンドスイープモデル
JRockit JVMは、ヒープ全体のガベージコレクションを実行するために、マークアンドスイープガベージコレクションモデルを使用します。マークアンドスイープガベージコレクションは、マークフェーズとスイープフェーズの2つのフェーズで構成されています。
マークフェーズの間、 Javaスレッド、ネイティブハンドル、その他のルートソースから到達可能なすべてのオブジェクトは、これらのオブジェクトなどから到達可能なオブジェクトと同様に、alive としてマークされます。 このプロセスは、まだ使用されているすべてのオブジェクトを識別してマークします。残りはゴミと見なすことができます。
スイープフェーズでは、ライブオブジェクト間のギャップを見つけるためにヒープがトラバースされます。これらのギャップはフリーリストに記録され、新しいオブジェクトの割り当てに利用できるようになります。
JRockit JVMは、2つの改良されたバージョンのmark and sweepモデルを使用しています。 1つは主に同時マークとスイープ、もう1つはパラレルマークとスイープです。 2つの戦略を混在させることもできます。たとえば、主に同時マークと並列スイープを実行します。
だから、Javaでメモリリークを作成するには。そのための最も簡単な方法は、データベース接続を作成し、いくつかの作業を行い、単にClose()
を行わないことです。次に、スコープ内に留まりながら、新しいデータベース接続を生成します。これは、例えばループでは難しくありません。キューから取り出してデータベースにプッシュするワーカーがいる場合、Close()
接続を忘れるか、必要でないときにそれらを開くことによって、簡単にメモリリークを起こすことができます。
結局、接続をClose()
することを忘れることによってJVMに割り当てられたヒープを消費するでしょう。これにより、JVMのガベージコレクションがおかしくなります。最終的にJava.lang.OutOfMemoryError: Java heap space
エラーになります。エラーがメモリリークがあることを意味しないかもしれないように注意される必要があります。それは単にあなたが十分なメモリを持っていないことを意味するかもしれません。例えばCassandraやElasticSearchのようなデータベースは十分なヒープスペースを持っていないのでそのエラーを投げることができます。
これがすべてのGC言語に当てはまることは注目に値します。以下は、私がSREとして働いているのを見たいくつかの例です。
json.Unmarshal
で解析し、その結果を参照渡しして開いたままにします。結局、これは私がjsonをデコードするためにオープンにしておいた偶然のrefによってヒープ全体が消費される結果となりました。Java 1.6のString.substringメソッドはメモリリークを引き起こします。このブログ記事はそれを説明しています。
http://javarevisited.blogspot.com/2011/10/how-substring-in-Java-works.html
Javaでは、「メモリリーク」とは、単にメモリを使い過ぎていることを意味します。Cを使用するのではなく、メモリを使用していないにもかかわらず戻ります(解放)。インタビュアーがJavaメモリリークについて尋ねるとき、彼らはただ上がり続けているように見えるJVMメモリ使用について尋ねていて、彼らは定期的にJVMを再起動することが最善の解決策であると決心しました。 (インタビュアーが 非常に 技術的に精通していない限り)
そのため、この質問に答えて、あたかもJVMのメモリ使用量が時間の経過とともに増加するのかを尋ねたかのように答えます。正解は、タイムアウトが長すぎる、または古いエントリをフラッシュしないインメモリキャッシュ(Singleton)の実装が不十分なHttpSessionsに格納するデータが多すぎることです。もう1つの可能性のある答えは、多数のJSPまたは動的に生成されたクラスを持つことです。クラスは通常小さいPermGenというメモリ領域にロードされ、ほとんどのJVMはクラスのアンロードを実装しません。
Swingはダイアログでとても簡単です。 JDialogを作成し、それを見せ、ユーザーがそれを閉じると、リークします。 dispose()
を呼び出すか、setDefaultCloseOperation(DISPOSE_ON_CLOSE)
を設定する必要があります。
経過したListernersはメモリリークの良い例です。オブジェクトはListenerとして追加されます。オブジェクトが不要になると、そのオブジェクトへの参照はすべてヌルになります。ただし、リスナーリストからオブジェクトを削除するのを忘れると、オブジェクトは存続し続け、イベントに応答しさえするため、メモリとCPUの両方が無駄になります。 http://www.drdobbs.com/jvm/Java-qa/184404011 を参照してください。
圧縮ガベージコレクタを使用しない場合は、ヒープの断片化によるある種のメモリリークが発生する可能性があります。
独自のライフサイクルを持つクラスの内部で、静的でない内部クラスを使用する。
Javaでは、 非静的な内部クラスおよび匿名クラス - 暗黙の参照を保持します その外部クラスへの/。一方、静的内部クラスは しません 。
これはAndroidでメモリリークが発生する一般的な例ですが、これは明らかではありません。
public class SampleActivity extends Activity {
private final Handler mLeakyHandler = new Handler() { //non-static inner class, holds the reference to the SampleActivity outter class
@Override
public void handleMessage(Message msg) {
// ...
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Post a message and delay its execution for a long time.
mLeakyHandler.postDelayed(new Runnable() {//here, the anonymous inner class holds the reference to the SampleActivity class too
@Override
public void run() {
//....
}
}, SOME_TOME_TIME);
// Go back to the previous Activity.
finish();
}}
これにより、アクティビティコンテキストがガベージコレクションされるのを防ぐことができます。
JDK 1.7より前のメモリリークのリアルタイムの例
1000行のテキストのファイルを読み、Stringオブジェクトを保持するとします。
String fileText = 1000 characters from file
fileText = fileText.subString(900, fileText.length());
上記のコードで私は最初に1000文字を読み、そして最後の100文字だけを得るために部分文字列を作りました。参照を失ったため、fileTextは100文字のみを参照し、それ以外のすべての文字はガベージコレクションされるようになりました。あなたが部分文字列の参照を失うまでメモリ上で。
あなたは上記のようなメモリリークの例を作成することができます
メモリリークは、コンピュータプログラムが誤ってメモリの割り当てを管理し、 不要になったメモリが解放されないようになった場合に発生するタイプのリソースリークです。 => wikiの定義
これは一種の比較的コンテキストベースのトピックです。未使用の参照がクライアントによって使用されることはなく、まだ生き続ける限り、あなたの好みに基づいてトピックを作成することができます。
最初の例はnullingを持たないカスタムスタックです。 有効なJava項目6 の廃止された参照。
もちろん、あなたが望む限りもっとたくさんありますが、Javaの組み込みクラスを見れば、次のようになるでしょう。
リークを発生させるためにsuper sillyコードをチェックしましょう。
public class MemoryLeak {
private static final int HUGE_SIZE = 10_000;
public static void main(String... args) {
letsLeakNow();
}
private static void letsLeakNow() {
Map<Integer, Object> leakMap = new HashMap<>();
for (int i = 0; i < HUGE_SIZE; ++i) {
leakMap.put(i * 2, getListWithRandomNumber());
}
}
private static List<Integer> getListWithRandomNumber() {
List<Integer> originalHugeIntList = new ArrayList<>();
for (int i = 0; i < HUGE_SIZE; ++i) {
originalHugeIntList.add(new Random().nextInt());
}
return originalHugeIntList.subList(0, 1);
}
}
実際には、 HashMap を使って、その見かけのプロセスを利用してメモリリークを起こすことができるもう1つのトリックがあります。実際には2つの種類があります。
hashCode()
は常に同じですが、equals()
は異なります。hashCode()
とequals()
を常に真にしてください。どうして?
hashCode()
- > bucket => equals()
でペアを探す
最初に substring()
、次にsubList()
について言及しようとしていましたが、JDK 8でソースが提示されているので、この問題はすでに修正されているようです。
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}