很多开发者都在其系统中见过“java.lang.OutOfMemoryError: PermGen space”这一问题。这往往是由类加载器相关的内存泄漏以及新类加载器的创建导致的,通常出现于代码热部署时。相对于正式产品,该问题在开发机上出现的频率更高,在产品中最常见的“问题”是默认值太低了。常用的解决方法是将其设置为256MB或更高。

PermGen space简单介绍

PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,说说为什么会内存益出:这一部分用于存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域,它和和存放Instance的Heap区域不同,所以如果你的APP会LOAD很多CLASS的话,就很可能出现PermGen space错误。这种错误常见在web服务器对JSP进行pre compile的时候。

JVM 种类有很多,比如 Oralce-Sun Hotspot, Oralce JRockit, IBM J9, Taobao JVM(淘宝好样的!)等等。当然武林盟主是Hotspot了,这个毫无争议。需要注意的是,PermGen space是Oracle-Sun Hotspot才有,JRockit以及J9是没有这个区域。

元空间(MetaSpace)一种新的内存空间诞生

JDK8 HotSpot JVM 将移除永久区,使用本地内存来存储类元数据信息并称之为:元空间(Metaspace);这与Oracle JRockit 和IBM JVM’s很相似,如下图所示

这意味着不会再有java.lang.OutOfMemoryError: PermGen问题,也不再需要你进行调优及监控内存空间的使用……但请等等,这么说还为时过早。在默认情况下,这些改变是透明的,接下来我们的展示将使你知道仍然要关注类元数据内存的占用。请一定要牢记,这个新特性也不能神奇地消除类和类加载器导致的内存泄漏。

java8中metaspace总结如下:

PermGen 空间的状况

这部分内存空间将全部移除。

JVM的参数:PermSize 和 MaxPermSize 会被忽略并给出警告(如果在启用时设置了这两个参数)。

Metaspace 内存分配模型

大部分类元数据都在本地内存中分配。

用于描述类元数据的“klasses”已经被移除。

Metaspace 容量

默认情况下,类元数据只受可用的本地内存限制(容量取决于是32位或是64位操作系统的可用虚拟内存大小)。

新参数(MaxMetaspaceSize)用于限制本地内存分配给类元数据的大小。如果没有指定这个参数,元空间会在运行时根据需要动态调整。

Metaspace 垃圾回收

对于僵死的类及类加载器的垃圾回收将在元数据使用达到“MaxMetaspaceSize”参数的设定值时进行。

适时地监控和调整元空间对于减小垃圾回收频率和减少延时是很有必要的。持续的元空间垃圾回收说明,可能存在类、类加载器导致的内存泄漏或是大小设置不合适。

Java 堆内存的影响

一些杂项数据已经移到Java堆空间中。升级到JDK8之后,会发现Java堆 空间有所增长。

Metaspace 监控

元空间的使用情况可以从HotSpot1.8的详细GC日志输出中得到。

Jstat 和 JVisualVM两个工具,在使用b75版本进行测试时,已经更新了,但是还是能看到老的PermGen空间的出现。

前面已经从理论上充分说明,下面让我们通过“泄漏”程序进行新内存空间的观察……

PermGen vs. Metaspace 运行时比较

为了更好地理解Metaspace内存空间的运行时行为,

将进行以下几种场景的测试:

  1. 使用JDK1.7运行Java程序,监控并耗尽默认设定的85MB大小的PermGen内存空间。

  2. 使用JDK1.8运行Java程序,监控新Metaspace内存空间的动态增长和垃圾回收过程。

  3. 使用JDK1.8运行Java程序,模拟耗尽通过“MaxMetaspaceSize”参数设定的128MB大小的Metaspace内存空间。

首先建立了一个模拟PermGen OOM的代码

1 public class ClassA {
2  public void method(String name) {
3   // do nothing
4  }
5 }

上面是一个简单的ClassA,把他编译成class字节码放到D:/classes下面,测试代码中用URLClassLoader来加载此类型上面类编译成class

01 /**
02  * 模拟PermGen OOM
03  * @author benhail
04  */
05 public class OOMTest {
06     public static void main(String[] args) {
07         try {
08             //准备url
09             URL url = new File("D:/classes").toURI().toURL();
10             URL[] urls = {url};
11             //获取有关类型加载的JMX接口
12             ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean();
13             //用于缓存类加载器
14             List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
15             while (true) {
16                 //加载类型并缓存类加载器实例
17                 ClassLoader classLoader = new URLClassLoader(urls);
18                 classLoaders.add(classLoader);
19                 classLoader.loadClass("ClassA");
20                 //显示数量信息(共加载过的类型数目,当前还有效的类型数目,已经被卸载的类型数目)
21                 System.out.println("total: " + loadingBean.getTotalLoadedClassCount());
22                 System.out.println("active: " + loadingBean.getLoadedClassCount());
23                 System.out.println("unloaded: " + loadingBean.getUnloadedClassCount());
24             }
25         catch (Exception e) {
26             e.printStackTrace();
27         }
28     }
29 }

虚拟机器参数设置如下:-verbose -verbose:gc

设置-verbose参数是为了获取类型加载和卸载的信息

设置-verbose:gc是为了获取垃圾收集的相关信息

JDK 1.7 @64-bit – PermGen 耗尽测试

Java1.7的PermGen默认空间为85 MB(或者可以通过-XX:MaxPermSize=XXXm指定)

可以从上面的JVisualVM的截图看出:当加载超过6万个类之后,PermGen被耗尽。我们也能通过程序和GC的输出观察耗尽的过程。

程序输出(摘取了部分)

01 ......
02 [Loaded ClassA from file:/D:/classes/]
03 total: 64887
04 active: 64887
05 unloaded: 0
06 [GC 245041K->213978K(536768K), 0.0597188 secs]
07 [Full GC 213978K->211425K(644992K), 0.6456638 secs]
08 [GC 211425K->211425K(656448K), 0.0086696 secs]
09 [Full GC 211425K->211411K(731008K), 0.6924754 secs]
10 [GC 211411K->211411K(726528K), 0.0088992 secs]
11 ...............
12 java.lang.OutOfMemoryError: PermGen space

JDK 1.8 @64-bit – Metaspace大小动态调整测试

Java的Metaspace空间:不受限制 (默认)

从上面的截图可以看到,JVM Metaspace进行了动态扩展,本地内存的使用由20MB增长到646MB,以满足程序中不断增长的类数据内存占用需求。我们也能观察到JVM的垃圾回收事件—试图销毁僵死的类或类加载器对象。但是,由于我们程序的泄漏,JVM别无选择只能动态扩展Metaspace内存空间。程序加载超过10万个类,而没有出现OOM事件。

JDK 1.8 @64-bit – Metaspace 受限测试

Java的Metaspace空间:128MB(-XX:MaxMetaspaceSize=128m)

可以从上面的JVisualVM的截图看出:当加载超过2万个类之后,Metaspace被耗尽;与JDK1.7运行时非常相似。我们也能通过程序和GC的输出观察耗尽的过程。另一个有趣的现象是,保留的原生内存占用量是设定的最大大小两倍之多。这可能表明,如果可能的话,可微调元空间容量大小策略,来避免本地内存的浪费。

从Java程序的输出中看到如下异常。

1 [Loaded ClassA from file:/D:/classes/]
2 total: 21393
3 active: 21393
4 unloaded: 0
5 [GC (Metadata GC Threshold) 64306K->57010K(111616K), 0.0145502 secs]
6 [Full GC (Metadata GC Threshold) 57010K->56810K(122368K), 0.1068084 secs]
7 java.lang.OutOfMemoryError: Metaspace

在设置了MaxMetaspaceSize的情况下,该空间的内存仍然会耗尽,进而引发“java.lang.OutOfMemoryError: Metadata space”错误。因为类加载器的泄漏仍然存在,而通常Java又不希望无限制地消耗本机内存,因此设置一个类似于MaxPermSize的限制看起来也是合理的。

总结

  1. 之前不管是不是需要,JVM都会吃掉那块空间……如果设置得太小,JVM会死掉;如果设置得太大,这块内存就被JVM浪费了。理论上说,现在你完全可以不关注这个,因为JVM会在运行时自动调校为“合适的大小”;

  2. 提高Full GC的性能,在Full GC期间,Metadata到Metadata pointers之间不需要扫描了,别小看这几纳秒时间;

  3. 隐患就是如果程序存在内存泄露,像OOMTest那样,不停的扩展metaspace的空间,会导致机器的内存不足,所以还是要有必要的调试和监控。

Java 8新特性探究(九)跟OOM:Permgen说再见吧的更多相关文章

  1. [转帖]Java 8新特性探究(九)跟OOM:Permgen说再见吧

    Java 8新特性探究(九)跟OOM:Permgen说再见吧 https://my.oschina.net/benhaile/blog/214159 need study 很多开发者都在其系统中见过“ ...

  2. Java 8新特性探究(八)精简的JRE详解

    http://www.importnew.com/14926.html     首页 所有文章 资讯 Web 架构 基础技术 书籍 教程 Java小组 工具资源 - 导航条 - 首页 所有文章 资讯 ...

  3. [转帖]Java 8新特性探究(八)精简的JRE详解

    Java 8新特性探究(八)精简的JRE详解 https://my.oschina.net/benhaile/blog/211804 精简版的api   撸了今年阿里.网易和美团的面试,我有一个重要发 ...

  4. [转帖]Java 8新特性探究 前言

    Java 8新特性探究 前言 https://my.oschina.net/benhaile/blog/174136 讲下java的历史 感觉挺好的. 评论 17 jdk8java8javase新特性 ...

  5. Java 8新特性探究(五)Base64详解

    BASE64 编码是一种常用的字符编码,在很多地方都会用到.但base64不是安全领域下的加密解密算法.能起到安全作用的效果很差,而且很容易破解,他核心作用应该是传输数据的正确性,有些网关或系统只能使 ...

  6. Java 8新特性探究(二)深入解析默认方法

    什么是默认方法,为什么要有默认方法 简单说,就是接口可以有实现方法,而且不需要实现类去实现其方法.只需在方法名前面加个default关键字即可. 为什么要有这个特性?首先,之前的接口是个双刃剑,好处是 ...

  7. Java 8新特性探究(三)泛型的目标类型推断

    简单理解泛型 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.通俗点将就是"类型的变量".这种类型变量可以用在类.接口和方法 ...

  8. Java 8新特性探究(二)类型注解和重复注解

    本文将介绍java 8的第二个特性:类型注解. 注解大家都知道,从java5开始加入这一特性,发展到现在已然是遍地开花,在很多框架中得到了广泛的使用,用来简化程序中的配置.那充满争议的类型注解究竟是什 ...

  9. Java 8新特性探究(一) JEP126特性lambda表达式和默认方法

    Lambda语法 函数式接口 函数式接口(functional interface 也叫功能性接口,其实是同一个东西).简单来说,函数式接口是只包含一个方法的接口.比如Java标准库中的java.la ...

随机推荐

  1. Windows文件监视器 1.0 绿色版

    软件名称:软件名称: Windows文件监视器 软件语言: 简体中文 授权方式: 免费软件 运行环境: Win7 / Vista / Win2003 / WinXP / Win2008 软件大小: 1 ...

  2. app测试--稳定性测试

    稳定性测试的概念有2种, 一, 稳定性测试,对应于异常性测试,即发生异常情况时,系统如何反应的测试.包含: 1 交互性测试,被打扰的情况,如来电,短信,低电量等.这些其实在上章的功能测试中有提到. 2 ...

  3. (转)用JMX监测JVM的运行参数

    翻译自http://java.sun.com/javase/6/docs/technotes/guides/management/agent.html  用JMX管理你的JVMJMX是Java管理扩展 ...

  4. Strusts2--课程笔记5

      数据验证: 输入验证分为客户端验证与服务器端验证.客户端验证主要通过JavaScript脚本进行,而服务器端验证主要是通过Java代码进行验证. 分为以下四种情况: (1)手工编写代码,对所有Ac ...

  5. 关于GVIM的配置。

    最近开始考虑从常用的 Dev-Cpp 和 Visual Studio 开发环境中转到gvim+gcc+gdb. 我一直觉得IDE很好方便,不是很明白别人这样的配置.但还是开始有意识的去尝试更换环境. ...

  6. ASCII码对应表chr(9)、chr(10)、chr(13)、chr(32)、chr(34)、chr(39)

    chr(9) tab空格       chr(10) 换行      chr(13) 回车        Chr(13)&chr(10) 回车换行       chr(32) 空格符      ...

  7. Chapter 2 Open Book——19

    He bent over and began scraping together a pile of the white mush. 他弯下腰,开始团起一个白色的雪球. "I'll see ...

  8. Chapter 1 First Sight——33

    At that moment, the bell rang loudly, making me jump, and Edward Cullen was out of his seat. 在这个时候,铃 ...

  9. DLL调试方法

    1.已经做好的dll不能设置:你可以用AfxMessageBox把信息打印出来.2.哪个地方调用的函数 把DLL重新编译一次 在把DLL放到工程里 从新添加一下 然后在你工程调用DLL内容的地方设置断 ...

  10. ios控件 UIImageView

    UIImageView的作用是显示图片和多张动态的图片   - (id)initWithImage:(UIImage *)image;//初始化图片视图 - (id)initWithImage:(UI ...