JVM基础:永久代(PernGem)和元空间(Metaspace)

前言

在之前的文章中提到过方法区存放的是虚拟机已经加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。同时运行时常量池也是方法区的一部分。但是在不同的JDK版本中对方法区的实现方式存在一定的差异。

正题

下面通过对 JDK 1.6、JDK 1.7、JDK 1.8 的运行时常量池的对比来看看各自的实现方式。

JDK 1.6

在 JDK 1.6 及之前的版本中(针对 HotSpot 虚拟机),由于常量池分配在永久代内。

public class RuntimeConstantPoolOOM {

	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		int i = 0;
		while(true)
		{
			System.out.println(i);
			list.add(String.valueOf(i++).intern());
		}
	}
}

JDK版本号:

java version "1.6.0_20"
Java(TM) SE Runtime Environment (build 1.6.0_20-b02)
Java HotSpot(TM) 64-Bit Server VM (build 16.3-b01, mixed mode)

运行结果:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
    at java.lang.String.intern(Native Method)
    at com.access.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:14)

从运行结果中可以看出,运行时常量池溢出,在 OutOfMemoryError 后面跟随的提示信息是“PermGen spacer“,说明运行时常量池属于方法区(HotSpot 虚拟机中的永久代)的一部分。

通过使用 Java VisualVM 工具观察 PermGen 内存运行轨迹:

从上图可以看出:因为程序通过使用列表保存在PernGem区域中字符串的引用,导致 PermGen 区域的内存不断飙升直至抛出 OutOfMemoryError 异常。

如果不把 String 对象 add 到 list 列表的情况:

从上图可以看出:只要 PermGen 的使用量接近或达到最大大小时就会触发 PermGen 区域的垃圾回收。

JDK 1.7

JDK 版本号:

java version "1.7.0_79"
Java(TM) SE Runtime Environment (build 1.7.0_79-b15)
Java HotSpot(TM) 64-Bit Server VM (build 24.79-b02, mixed mode)

通过使用 Java VisualVM 工具观察 PernGen 内存运行轨迹图:

从 PernGen 运行轨迹看出:PernGen 很平稳,说明常量池没有分配到 PernGen 内存区域中。

通过使用 Java VisualVM 工具观察堆内存运行轨迹图:

从堆内存运行轨迹可以看出:堆不不断增加,最后导致抛出 OutOfMemoryError 异常。从上图可以知道常量池是在堆上分配存储空间。

运行结果:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.ArrayList.grow(Unknown Source)
    at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)
    at java.util.ArrayList.ensureCapacityInternal(Unknown Source)
    at java.util.ArrayList.add(Unknown Source)
    at com.access.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:14)

从运行结果可以看出,JDK1.7 把常量池从永久代移动到Java堆中,所以在 Exception 中提示 heap 溢出。

JDK 1.8

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 4265607168 (4068.0MB)
   NewSize                  = 89128960 (85.0MB)
   MaxNewSize               = 1421869056 (1356.0MB)
   OldSize                  = 62914560 (60.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

随着 JDK 8 的到来,JVM不在有 PermGen。但类的元数据信息(metadata)还在,只不过不在是存储在连续的堆空间上,而是移动到叫做“Metaspace”的本地内存(Native memory)中。

元空间的本质和永久代类似,都是对JVM规范中方法区的实现,不过元空间和永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。理论上取决于 32 位/ 64 位系统可虚拟的内存大小。可见也不是无限的,需要配置参数。

现在大多数的类元数据分配在本地化内存中。默认情况下,类元数据分配受到可用的本机内存容量的限制(容量依然取决于你使用的是 32 位 JVM 还是 64 位操作系统的虚拟内存的可用性。在我的机子上的可用内存是 1082130432 B)。

类的元数据信息转移到Metaspace的原因:

  • 字符串存放在永久代中,容易出现性能问题和内存溢出。
  • PermGen 这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻。

常用配置参数:

1:MetaspaceSize

初始化 Metaspace 大小,控制元空间发生 GC 的阈值。GC 后,动态增加或降低 MetaspaceSize。在默认情况下,这个值大小根据不同的品台在 12M 到 20M 浮动,使用 Java -XX:+PrintFlagsInitial 命令查看本机的初始化参数

2:MaxMetaspaceSize

限制 Metaspace 增长上限,防止因为某些情况导致 Metaspace 无限使用本地内存,影响到其他程序。

3:MinMetaspaceFreeRatio

当进行过 Metaspace GC 之后,会计算当前 Metaspace 的空闲空间比,如果空闲比小于这个参数(即实际非空闲空间占比过大,内存不够用),那么虚拟机将增加 Metaspace 的大小。默认值为 40,也就是 40%。设置该参数可以控制 Metaspace 的增长速度,太小的值会导致 Metaspace 增长缓慢,Metaspace 的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致 Metaspace 增长过快,浪费内存。

4:MaxMetaspaceFreeRatio

当进行 Metaspace GC 之后,会计算当前 Metaspace 的空闲空间比,如果空闲比大于这个参数,那么虚拟机就会释放Metaspace的部分参数。默认值为 70,也就是 70%。

5:MaxMetaspaceExpansion

Metaspace 增长时的最大幅度。

6:MinMetaspaceExpansion

Metaspace 增长时最小幅度。

在 eclipse 中选中类设置参数:-Xms20m -Xmx20 -XX:PermSize=8m -XX:MaxPermSize=8M

运行时会提示:

ClassA类

package com.memory;

public class ClassA {

}

把 ClassA 编译成 class 文件,运行下面程序。

package com.memory;

import java.io.File;
import java.lang.management.ClassLoadingMXBean;
import java.lang.management.ManagementFactory;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;

public class OOMTest {

	public static void main(String[] args) {  
        try{  
            //准备url  
            URL url = new File("D:/eclipse/javaEE/WebWorkSpace/ClassProject/src").toURI().toURL();  
            URL[] urls = {url};  
            //获取有关类型加载的JMX接口  
            ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean();  
            //用于缓存类加载器  
            List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();  
            while(true) {  
                //加载类型并缓存类加载器实例  
                ClassLoader classLoader = new URLClassLoader(urls);  
                classLoaders.add(classLoader);  
                classLoader.loadClass("com.memory.ClassA");  
                //显示数量信息(共加载过的类型数目,当前还有效的类型数目,已经被卸载的类型数目)  
                System.out.println("total: "+ loadingBean.getTotalLoadedClassCount());  
                System.out.println("active: "+ loadingBean.getLoadedClassCount());  
                System.out.println("unloaded: "+ loadingBean.getUnloadedClassCount());  
            }  
        } catch(Exception e) {  
            e.printStackTrace();  
        }  
    }  
}

运行时参数设置:-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=512m -XX:PermSize=8m -XX:MaxPermSize=8M

Metaspace 空间的运行轨迹:

可以从上面的 JVisualVM 的截图看出:当加载类的总数到达 87386 之后,就会出现 Metaspace 区域溢出。也可以通过观察垃圾回收活动轨迹了解元空间内存耗尽。Java 程序运行结果:

total: 87416
active: 87416
unloaded: 0
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(Unknown Source)
	at java.security.SecureClassLoader.defineClass(Unknown Source)
	at java.net.URLClassLoader.defineClass(Unknown Source)
	at java.net.URLClassLoader.access$100(Unknown Source)
	at java.net.URLClassLoader$1.run(Unknown Source)
	at java.net.URLClassLoader$1.run(Unknown Source)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(Unknown Source)
	at java.lang.ClassLoader.loadClass(Unknown Source)
	at java.lang.ClassLoader.loadClass(Unknown Source)
	at com.memory.OOMTest.main(OOMTest.java:26)
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=8m; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=8M; support was removed in 8.0
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值