JVM运行时数据区篇(方法区基本概述)
Posted ProChick
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM运行时数据区篇(方法区基本概述)相关的知识,希望对你有一定的参考价值。
1.堆、栈、方法区之间的关系
从线程共享与否的角度来看
从三者交互关系的角度来看
2.什么是方法区?
-
JVM规范中表明:尽管所有的方法区在逻辑上属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩。
-
对于 HotSpot VM 而言,方法区还有一个别名叫做Non-heap(
非堆
),目的就是要和堆分开。所以方法区可以看作是一块独立于Java堆的一块内存空间 -
方法区与堆空间一样,是各个线程共享的内存区域
-
方法区在JVM启动时就会被创建, 关闭JVM就会释放这个区域的内存,并且它的实际的物理内存空间中和Java堆区域一样都可以是不连续的
-
方法区的大小和堆空间一样,也可以选择固定大小或者可拓展大小
-
方法区的大小决定了系统可以保存多少个类信息,如果系统定义了太多的类,就会导致方法区溢出,虚拟机同样会抛出内存溢出错误
java.lang.OutOfMemoryError:PermGen space
或者java.lang,OutOfMemoryError:Metaspace
程序示例
// 哪怕是最简单的程序也会加载非常多的类信息 public class TestMethod { public static void main(String[] args) { System.out.println("start..."); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("end..."); } }
3.方法区的演进
-
在JDK7以及之前的版本中,习惯上把方法区称为永久代
-
实质上,方法区和永久代并不等价,等价只针对于Java的 HotSpot VM。因为在JVM规范中,对如何实现方法区没有统一要求,也就是说可以进行不同的实现,例如:JRockit VM 或者 J9 VM 中根本不存在永久代的概念,也就谈不上等价关系。
-
从JDK8开始,则使用元数据空间取代了永久代
-
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代最大的区别在于:元空间不再虚拟机设置的内存中,而是使用本地内存,这样就很大程度上减少了OOM的问题
4.方法区内存大小设置
方法区的大小不必是固定的,JVM还可以根据应用的需要动态调整
-
JDK7以及之前
- 通过参数
-XX:PermSize
来设置永久代初始分配空间。默认值是20.75M - 通过参数
-XX:MaxPermSize
来设置永久代最大可分配空间。32位机器默认是64M,64位机器默认是82M
- 通过参数
-
JDK8以及之后
-
通过参数
-XX:MetaspaceSize
来设置元空间初始分配空间。默认值大约是21M(Windows平台
) -
通过参数
-XX:MaxMetaspaceSize
来设置元空间最大可分配空间。默认值是-1,即没有限制
-
元空间的初始大小叫做最高水位线,一旦触及这个水位线,Full GC 将会被触发并卸载没用的类,然后这个高水位线将会重置。新的高水位线的值取决于GC后释放了多少元空间。如果释放的空间不足,那么在不超过MaxMetaspaceSize时,适当提高该值。如果释放空间过多,则适当降低该值
-
如果初始化的高水位线设置过低,则上述高水位线调整情况会发生很多次。为了避免频繁地触发Full GC 进行垃圾回收,建议将 元空间的初始值设置为一个相对较高的值
-
5.方法区溢出过程示例
/**
* 首先设置元空间大小
* jdk7中:-XX:PermSize=10m -XX:MaxPermSize=10m
* jdk8中:-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
*/
public class OOMTest extends ClassLoader {
public static void main(String[] args) {
int j = 0;
try {
OOMTest test = new OOMTest();
for (int i = 0; i < 10000; i++) {
// 创建ClassWriter对象,用于生成类的二进制字节码
ClassWriter classWriter = new ClassWriter(0);
// 指明版本号,修饰符,类名,包名,父类,接口
classWriter.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "Class" + i, null,
"java/lang/Object", null);
// 返回字节数组
byte[] code = classWriter.toByteArray();
// 进行类的加载
test.defineClass("Class" + i, code, 0, code.length);
j++;
}
} finally {
System.out.println(j);
}
}
}
在JDK7中测试
在JDK8中测试
解决溢出的思路
- 要解决 OOM 异常或 heap space 的异常,一般的手段是首先通过内存映像分析工具(比如 Eclipse Memory Analyzer) 对dump出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,也就是要先分清楚到底是出现了内存泄漏(Memory Leak)还是内存溢出(Memory 0verflow)
- 如果是内存泄漏,可进一步通过工具查看泄漏对象到 GC Roots 的引用链。于是就能找到泄漏对象是通过怎样的路径与GCRoots相关联并导致垃圾收集器无法自动回收它们的。掌握了泄漏对象的类型信息,以及GC Roots 引用链的信息,就可以比较准确地定位出泄漏代码的位置
- 如果是内存溢出,换句话说就是内存中的对象确实都还必须存活着,那就应当检查虚拟机的堆参数设置,然后与机器物理内存对比看是否还可以调大。或者从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,进而尝试减少程序运行期的内存消耗
以上是关于JVM运行时数据区篇(方法区基本概述)的主要内容,如果未能解决你的问题,请参考以下文章