この記事の内容は、ヒープとは何ですか?メソッド領域とは何ですか? JVM メモリ モデルのヒープとメソッド領域の紹介は、参考にしていただければ幸いです。
ヒープはオブジェクトを保存するために使用されるメモリ領域です。したがって、ガベージ コレクター (GC) 管理の主なターゲットとなります。これには次の特徴があります。
ヒープは論理的に「新しい世代」と「古い世代」に分割されます。 JAVA のほとんどのオブジェクトは生成されて破棄され、少数のオブジェクトはメモリ内に長期間存在できるため、これら 2 つのオブジェクトを最も効果的にリサイクルするために、ヒープは新世代と古い世代に分割されます。世代とリサイクル戦略の実行は異なります。異なるガベージ コレクターは、これら 2 つの論理領域に対して異なるリサイクル メカニズムを備えています。これについては、後続の章で詳しく説明します。
ヒープが占有するメモリには物理的な連続性は必要なく、論理的な連続性のみが必要です。
ヒープは通常、拡張可能なメモリ サイズとして実装され、「-Xms」と「-Xmx」を使用してヒープの最小メモリと最大メモリを制御し、拡張アクションが実行されます。仮想マシンによって。ただし、この動作はパフォーマンスを消費するため、通常、ヒープの最大メモリと最小メモリは等しくなるように設定されます。
ヒープはすべてのスレッドによって共有されるメモリ領域であるため、各スレッドはヒープ上の同じオブジェクトを取得できます。
ヒープのライフサイクルは、仮想マシンの起動時に作成されます。
ヒープがオブジェクト メモリを割り当てられず、拡張できない場合、OutOfMemoryError 例外がスローされます。
一般に、GC はヒープがオブジェクトを割り当てられない場合に実行されます。GC 後もオブジェクトを割り当てられない場合は、メモリ枯渇エラーが報告されます。この状況は、参照を解放せずに新しいオブジェクトを継続的に生成することでシミュレートできます。
/** * java堆溢出demo * JVM参数:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError * Created by chenjunyi on 2018/4/25. */ public class HeapOOM { static class OOMObject { } public static void main(String[] args) { List<OOMObject> list = new ArrayList<>(); //不断创建新对象,使得Heap溢出 while (true) { list.add(new OOMObject()); } } }
上記のコードでは、オブジェクトは参照を解放せずに継続的に作成され、GC がヒープ メモリを再利用できなくなります。最終的に OutOfMemoryError 、エラー メッセージ:
java.lang.OutOfMemoryError: Java heap space
(2) クラス/メソッド/フィールドなどのメタデータ オブジェクト: バイトコードがロードされた後、JVM はコンテンツに基づいてこのクラスのクラス/メソッド/フィールドおよびその他のオブジェクトを生成します。これらはクラスを記述するために使用されます。通常は反射で使用されることが多いです。ヒープに格納される Java インスタンス オブジェクトとは異なり、これら 2 つのオブジェクトはメソッド領域に格納されます。
(3) static-final 定数と静的変数: これら 2 種類のクラス メンバーの場合、JVM はメソッド領域にデータのコピーを作成するため、静的に変更されたクラス メンバーのコピーは 1 つだけ存在します。同じクラス;
(4) JIT コンパイラのコンパイル結果: ホットスポット仮想マシンを例に挙げると、JIT ジャストインタイム コンパイラを使用して、実行時にホットスポット コードを最適化します。バイトコードをマシンコードにコンパイルします。通常、JVM は「解釈と実行」メソッドを使用してバイトコードを実行します。つまり、JVM がバイトコード命令を読み取ると、事前に決定されたルールに従ってスタック操作が実行され、スタック操作は基礎となるマシン操作にさらにマッピングされます。 ; JIT コンパイル後、実行されたマシン コードは基礎となるマシンを直接処理します。以下の図に示すように:
1 //使用StringBuilder在堆上创建字符串abc,再使用intern将其放入运行时常量池 2 String str = new StringBuilder("abc"); 3 str.intern(); 4 //直接使用字符串字面量xyz,其被放入运行时常量池 5 String str2 = "xyz";
(1)HotSpot虚拟机1.7-:在JDK1.6及之前版本,HotSpot使用“永久代(permanent generation)”的概念作为实现,即将GC分代收集扩展至方法区。这种实现比较偷懒,可以不必为方法区编写专门的内存管理,但带来的后果是容易碰到内存溢出的问题(因为永久代有-XX:MaxPermSize的上限)。在JDK1.7+之后,HotSpot逐渐改变方法区的实现方式,如1.7版本移除了方法区中的字符串常量池。
(2)HotSpot虚拟机1.8+:1.8版本中移除了方法区并使用metaspace(元数据空间)作为替代实现。metaspace占用系统内存,也就是说,只要不碰触到系统内存上限,方法区会有足够的内存空间。但这不意味着我们不对方法区进行限制,如果方法区无限膨胀,最终会导致系统崩溃。
我们思考一个问题,为什么使用“永久代”并将GC分代收集扩展至方法区这种实现方式不好,会导致OOM?首先要明白方法区的内存回收目标是什么,方法区存储了类的元数据信息和各种常量,它的内存回收目标理应当是对这些类型的卸载和常量的回收。但由于这些数据被类的实例引用,卸载条件变得复杂且严格,回收不当会导致堆中的类实例失去元数据信息和常量信息。因此,回收方法区内存不是一件简单高效的事情,往往GC在做无用功。另外随着应用规模的变大,各种框架的引入,尤其是使用了字节码生成技术的框架,会导致方法区内存占用越来越大,最终OOM。
在2.3一节中,我们了解到方法区的2种实现方式最终都会有一个最大值上限,因此若方法区(含运行时常量池)占用内存到达其最大值,且无法再申请到内存时,便会抛出OutOfMemoryError。
在下面的例子中,我们将使用cglib字节码生成框架不断生成新的类,最终使方法区内存占用满,抛出OutOfMemoryError:
/** * java方法区溢出OutOfMemoryError(JVM参数适用于JDK1.6之前,借助CGLIB) * JVM参数:-XX:PermSize=10M -XX:MaxPermSize=10M * Created by chenjunyi on 2018/4/26. */ public class JavaMethodAreaOOM { public static void main(String[] args) { while (true) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOMObject.class); enhancer.setUseCache(false); enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> methodProxy.invokeSuper(objects, args)); enhancer.create(); } } static class OOMObject { } }
报错信息为:
1 Caused by: java.lang.OutOfMemoryError: PermGen space 2 at java.lang.ClassLoader.defineClass1(Native Method) 3 ···
其实,在日常开发中,不仅仅使CGlib字节码生成框架会产生大量的class信息,动态语言、JSP、基于OSGI的应用都会在方法区额外产生大量的类信息。
以上がヒープとは何ですか?メソッド領域とは何ですか? JVM メモリ モデルのヒープとメソッド領域の概要の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。