『Java仮想マシンを深く理解する』学習ノート2-Javaメモリオーバーフローの例
簡単なインスタンスプログラムでjava仮想マシンの各部分のメモリオーバーフローを実証します.
(1).javaスタックオーバーフロー:
Javaヒープはインスタンスオブジェクトを格納するために使用され、オブジェクトを作成し続け、GC Rootsからオブジェクトへの参照が可能であることを保証し、ゴミ収集器がインスタンスオブジェクトを回収しないようにすると、オブジェクト数がヒープの最大容量に達したときにOutOfMemoryError異常が発生します.
ヒープオーバーフローの迅速な発生を容易にするには、次のjava仮想マシンパラメータを使用します:-Xms 10 m(最小ヒープメモリは10 MB)、-Xmx 10 m(最大ヒープメモリは10 MB、最小ヒープメモリは最大ヒープメモリと同じようにヒープの動的拡張を避けるため)、-XX:+HeapDumpOnOutOfMemoryErrorはjava仮想マシンにメモリオーバーフローが発生したときに現在のヒープメモリスナップショットを生成して異常解析を行うことができます.
例コードは次のとおりです.
しばらく実行するとOutOfMemoryError異常が発生し、スタックメモリ異常dumpファイルが発生します.
(2).java仮想マシンスタックとローカルメソッドスタックのオーバーフロー:
SunのHotSpot仮想マシンはjava仮想マシンスタックとローカルメソッドスタックを区別しないため、HotSpot仮想マシンでは-Xossパラメータ(ローカルメソッドスタックサイズを設定)は存在するが、実際には無効であり、スタック容量は-Xssパラメータのみで設定できる.
Java仮想マシンスタックでは、StackOverflowErrorとOutOfMemoryErrorの2つの例外が発生するため、この2つのケースをそれぞれ2つの例で説明します.
a.java仮想マシンスタックの深さオーバーフロー:
単一スレッド環境では、スタックフレームが大きすぎても、仮想マシンスタック容量が小さすぎても、メモリが再割り当てできない場合、仮想マシンは常にStackOverflowError異常を放出します.-Xss 128 kを使用してjava仮想スタックのサイズを128 kbに設定します.例コードは次のとおりです.
しばらく運転した後、StackOverflowError異常が発生しました.Java仮想スタックオーバーフローは、一般に、メソッドの再帰呼び出しが多すぎてjava仮想スタックのメモリが足りない場合に発生します.
b.java仮想マシンスタックメモリオーバーフロー:
マルチスレッド環境では、作成可能なスレッド最大メモリ=物理メモリ-最大スタックメモリ-最大メソッド領域メモリは、java仮想マシンスタックメモリが一定の場合、単一スレッドが占有するメモリが大きいほど、作成可能なスレッド数が小さくなるため、マルチスレッド条件下でjava仮想マシンスタックメモリオーバーフローの異常が発生しやすい.
-Xss 2 mパラメータを使用してjava仮想マシンスタックのメモリサイズを2 MBに設定します.例コードは次のとおりです.
しばらく実行すると、java仮想マシンスタックはメモリが小さすぎてスレッドを作成できないため、OutOfMemoryErrorを生成します.
(3).実行時定数プールオーバーフロー:
実行時定数プールはメソッド領域の一部であり、-X:PermSize=10 mと-X:MaxPermSize=10 mを使用して永続最大メモリと最小メモリを10 MBサイズに設定できます.また、永続最大メモリと最小メモリサイズが同じであるため拡張できません.
Stringのintern()メソッドは、定数プールにこのStringオブジェクトに等しい文字列が存在する場合、定数プールの文字列オブジェクトを直接返します.そうでない場合、このStringオブジェクトに含まれる文字列を実行時定数プールに追加し、このStringオブジェクトの参照を返します.従って、Stringのintern()メソッドは、実行時定数プールオーバーフローを示すのに特に適しており、例コードは以下の通りである.
一定期間実行すると、永続メモリが不足し、実行時定数プールが定数を追加できないためOutOfMemoryErrorが発生します.
(4).メソッド領域オーバーフロー:
ランタイムプールは、HotSpot仮想マシンの永続的なメモリ領域に属するメソッド領域の一部です.メソッド領域はClassに関する情報を格納するために用いられ,Javaの反射と動的エージェントはClassを動的に生成することができ,またサードパーティのCGLIBはバイトコードを直接操作したり,Classを動的に生成したりすることができ,実験はCGLIBによって実証され,同様に−XX:PermSize=10 mと−XX:MaxPermSize=10 mを用いて永久世代最大メモリと最小メモリを10 MBサイズに設定する.また、永続世代の最大メモリと最小メモリのサイズが同じであるため、拡張できません.例コードは次のとおりです.
しばらく実行した後、永続世代メモリが不足し、メソッド領域はCGLIB作成処理のClass情報を再格納できず、メソッド領域OutOfMemoryErrorを生成する.
(5).本機直接メモリオーバーフロー:
Java仮想マシンは、パラメータ-XX:MaxDirectMemorySizeを使用して、ネイティブの直接メモリの使用可能なサイズを設定できます.指定しない場合、デフォルトはjavaスタックのメモリサイズと同じです.JDKでは、反射によってUnsafeクラス(UnsafeのgetUnsafe()メソッドは、クラスローダBootstrapを起動してこそインスタンスを返すことができる)を取得して、ネイティブ直接メモリを直接操作することができます.-X:MaxDirectMemorySize=10 Mを使用することで、最大使用可能なネイティブの直接メモリサイズを10 MBに制限します.例コードは以下の通りです.
一定時間運転した後、10 MBの自機直接メモリに光が割り当てられ、直接メモリ割り当てができない場合、OutOfMemoryErrorが発生する.
(1).javaスタックオーバーフロー:
Javaヒープはインスタンスオブジェクトを格納するために使用され、オブジェクトを作成し続け、GC Rootsからオブジェクトへの参照が可能であることを保証し、ゴミ収集器がインスタンスオブジェクトを回収しないようにすると、オブジェクト数がヒープの最大容量に達したときにOutOfMemoryError異常が発生します.
ヒープオーバーフローの迅速な発生を容易にするには、次のjava仮想マシンパラメータを使用します:-Xms 10 m(最小ヒープメモリは10 MB)、-Xmx 10 m(最大ヒープメモリは10 MB、最小ヒープメモリは最大ヒープメモリと同じようにヒープの動的拡張を避けるため)、-XX:+HeapDumpOnOutOfMemoryErrorはjava仮想マシンにメモリオーバーフローが発生したときに現在のヒープメモリスナップショットを生成して異常解析を行うことができます.
例コードは次のとおりです.
public class HeapOOM{
static class OOMObject{
}
public static void main(String[] args){
List<OOMObject> list = new ArrayList<OOMObject>();
while(true){
list.add(new OOMObject());
}
}
}
しばらく実行するとOutOfMemoryError異常が発生し、スタックメモリ異常dumpファイルが発生します.
(2).java仮想マシンスタックとローカルメソッドスタックのオーバーフロー:
SunのHotSpot仮想マシンはjava仮想マシンスタックとローカルメソッドスタックを区別しないため、HotSpot仮想マシンでは-Xossパラメータ(ローカルメソッドスタックサイズを設定)は存在するが、実際には無効であり、スタック容量は-Xssパラメータのみで設定できる.
Java仮想マシンスタックでは、StackOverflowErrorとOutOfMemoryErrorの2つの例外が発生するため、この2つのケースをそれぞれ2つの例で説明します.
a.java仮想マシンスタックの深さオーバーフロー:
単一スレッド環境では、スタックフレームが大きすぎても、仮想マシンスタック容量が小さすぎても、メモリが再割り当てできない場合、仮想マシンは常にStackOverflowError異常を放出します.-Xss 128 kを使用してjava仮想スタックのサイズを128 kbに設定します.例コードは次のとおりです.
public class JavaVMStackOF{
private int stackLength = 1;
public void stackLeak(){
statckLength++;
stackLeak();
}
public static void main(String[] args){
JavaVMStackOF oom = new JavaVMStackOF();
oom.stackLeak();
}
}
しばらく運転した後、StackOverflowError異常が発生しました.Java仮想スタックオーバーフローは、一般に、メソッドの再帰呼び出しが多すぎてjava仮想スタックのメモリが足りない場合に発生します.
b.java仮想マシンスタックメモリオーバーフロー:
マルチスレッド環境では、作成可能なスレッド最大メモリ=物理メモリ-最大スタックメモリ-最大メソッド領域メモリは、java仮想マシンスタックメモリが一定の場合、単一スレッドが占有するメモリが大きいほど、作成可能なスレッド数が小さくなるため、マルチスレッド条件下でjava仮想マシンスタックメモリオーバーフローの異常が発生しやすい.
-Xss 2 mパラメータを使用してjava仮想マシンスタックのメモリサイズを2 MBに設定します.例コードは次のとおりです.
public class JavaVMStackOOM{
private void dontStop(){
while(true){
}
}
public void stackLeakByThread(){
while(true){
Thread t = new Thread(new Runnable(){
public void run(){
dontStop();
}
});
t.start();
}
}
public static void main(String[] args){
JavaVMStackOOM oom = new JavaVMStackOOM();
oom. stackLeakByThread();.
}
}
しばらく実行すると、java仮想マシンスタックはメモリが小さすぎてスレッドを作成できないため、OutOfMemoryErrorを生成します.
(3).実行時定数プールオーバーフロー:
実行時定数プールはメソッド領域の一部であり、-X:PermSize=10 mと-X:MaxPermSize=10 mを使用して永続最大メモリと最小メモリを10 MBサイズに設定できます.また、永続最大メモリと最小メモリサイズが同じであるため拡張できません.
Stringのintern()メソッドは、定数プールにこのStringオブジェクトに等しい文字列が存在する場合、定数プールの文字列オブジェクトを直接返します.そうでない場合、このStringオブジェクトに含まれる文字列を実行時定数プールに追加し、このStringオブジェクトの参照を返します.従って、Stringのintern()メソッドは、実行時定数プールオーバーフローを示すのに特に適しており、例コードは以下の通りである.
public class RuntimeConstantPoolOOM{
public static void main(String[] args){
List<String> list = new ArrayList<String>();
int i = 0;
while(true){
list.add(String.valueOf(i++).intern());
}
}
}
一定期間実行すると、永続メモリが不足し、実行時定数プールが定数を追加できないためOutOfMemoryErrorが発生します.
(4).メソッド領域オーバーフロー:
ランタイムプールは、HotSpot仮想マシンの永続的なメモリ領域に属するメソッド領域の一部です.メソッド領域はClassに関する情報を格納するために用いられ,Javaの反射と動的エージェントはClassを動的に生成することができ,またサードパーティのCGLIBはバイトコードを直接操作したり,Classを動的に生成したりすることができ,実験はCGLIBによって実証され,同様に−XX:PermSize=10 mと−XX:MaxPermSize=10 mを用いて永久世代最大メモリと最小メモリを10 MBサイズに設定する.また、永続世代の最大メモリと最小メモリのサイズが同じであるため、拡張できません.例コードは次のとおりです.
public class JavaMethodAreaOOM{
public static void main(String[] args){
while(true){
Enhancer enhancer = new Enhancer();
enhancer.setSuperClass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor(){
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy)throws Throwable{
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
}
}
class OOMObject{
}
}
しばらく実行した後、永続世代メモリが不足し、メソッド領域はCGLIB作成処理のClass情報を再格納できず、メソッド領域OutOfMemoryErrorを生成する.
(5).本機直接メモリオーバーフロー:
Java仮想マシンは、パラメータ-XX:MaxDirectMemorySizeを使用して、ネイティブの直接メモリの使用可能なサイズを設定できます.指定しない場合、デフォルトはjavaスタックのメモリサイズと同じです.JDKでは、反射によってUnsafeクラス(UnsafeのgetUnsafe()メソッドは、クラスローダBootstrapを起動してこそインスタンスを返すことができる)を取得して、ネイティブ直接メモリを直接操作することができます.-X:MaxDirectMemorySize=10 Mを使用することで、最大使用可能なネイティブの直接メモリサイズを10 MBに制限します.例コードは以下の通りです.
public class DirectMemoryOOM{
private static final int _1MB = 1024* 1024 * 1024;
publc static void main(String[] args) throws Exception{
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while(true){
//unsafe
unsafe.allocateMemory(_1MB);
}
}
}
一定時間運転した後、10 MBの自機直接メモリに光が割り当てられ、直接メモリ割り当てができない場合、OutOfMemoryErrorが発生する.