不少开发者都在其系统中见过“java.lang.OutOfMemoryError: PermGen space”这一问题。这每每是由类加载器相关的内存泄漏以及新类加载器的建立致使的,一般出现于代码热部署时。相对于正式产品,该问题在开发机上出现的频率更高,在产品中最多见的“问题”是默认值过低了。经常使用的解决方法是将其设置为256MB或更高。java
PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,说说为何会内存益出:这一部分用于存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域,它和和存放Instance的Heap区域不一样,因此若是你的APP会LOAD不少CLASS的话,就极可能出现PermGen space错误。这种错误常见在web服务器对JSP进行pre compile的时候。web
JVM 种类有不少,好比 Oralce-Sun Hotspot, Oralce JRockit, IBM J9, Taobao JVM(淘宝好样的!)等等。固然武林盟主是Hotspot了,这个毫无争议。须要注意的是,PermGen space是Oracle-Sun Hotspot才有,JRockit以及J9是没有这个区域。缓存
JDK8 HotSpot JVM 将移除永久区,使用本地内存来存储类元数据信息并称之为:元空间(Metaspace);这与Oracle JRockit 和IBM JVM’s很类似,以下图所示服务器
这意味着不会再有java.lang.OutOfMemoryError: PermGen问题,也再也不须要你进行调优及监控内存空间的使用……但请等等,这么说还为时过早。在默认状况下,这些改变是透明的,接下来咱们的展现将使你知道仍然要关注类元数据内存的占用。请必定要牢记,这个新特性也不能神奇地消除类和类加载器致使的内存泄漏。工具
java8中metaspace总结以下:性能
这部份内存空间将所有移除。测试
JVM的参数:PermSize 和 MaxPermSize 会被忽略并给出警告(若是在启用时设置了这两个参数)。url
大部分类元数据都在本地内存中分配。spa
用于描述类元数据的“klasses”已经被移除。操作系统
默认状况下,类元数据只受可用的本地内存限制(容量取决因而32位或是64位操做系统的可用虚拟内存大小)。
新参数(MaxMetaspaceSize)用于限制本地内存分配给类元数据的大小。若是没有指定这个参数,元空间会在运行时根据须要动态调整。
对于僵死的类及类加载器的垃圾回收将在元数据使用达到“MaxMetaspaceSize”参数的设定值时进行。
适时地监控和调整元空间对于减少垃圾回收频率和减小延时是颇有必要的。持续的元空间垃圾回收说明,可能存在类、类加载器致使的内存泄漏或是大小设置不合适。
一些杂项数据已经移到Java堆空间中。升级到JDK8以后,会发现Java堆 空间有所增加。
元空间的使用状况能够从HotSpot1.8的详细GC日志输出中获得。
Jstat 和 JVisualVM两个工具,在使用b75版本进行测试时,已经更新了,可是仍是能看到老的PermGen空间的出现。
前面已经从理论上充分说明,下面让咱们经过“泄漏”程序进行新内存空间的观察……
为了更好地理解Metaspace内存空间的运行时行为,
将进行如下几种场景的测试:
使用JDK1.7运行Java程序,监控并耗尽默认设定的85MB大小的PermGen内存空间。
使用JDK1.8运行Java程序,监控新Metaspace内存空间的动态增加和垃圾回收过程。
使用JDK1.8运行Java程序,模拟耗尽经过“MaxMetaspaceSize”参数设定的128MB大小的Metaspace内存空间。
首先创建了一个模拟PermGen OOM的代码
public class ClassA { public void method(String name) { // do nothing } }
上面是一个简单的ClassA,把他编译成class字节码放到D:/classes下面,测试代码中用URLClassLoader来加载此类型上面类编译成class
/** * 模拟PermGen OOM * @author benhail */ public class OOMTest { public static void main(String[] args) { try { //准备url URL url = new File("D:/classes").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("ClassA"); //显示数量信息(共加载过的类型数目,当前还有效的类型数目,已经被卸载的类型数目) System.out.println("total: " + loadingBean.getTotalLoadedClassCount()); System.out.println("active: " + loadingBean.getLoadedClassCount()); System.out.println("unloaded: " + loadingBean.getUnloadedClassCount()); } } catch (Exception e) { e.printStackTrace(); } } }
虚拟机器参数设置以下:-verbose -verbose:gc
设置-verbose参数是为了获取类型加载和卸载的信息
设置-verbose:gc是为了获取垃圾收集的相关信息
Java1.7的PermGen默认空间为85 MB(或者能够经过-XX:MaxPermSize=XXXm指定)
能够从上面的JVisualVM的截图看出:当加载超过6万个类以后,PermGen被耗尽。咱们也能经过程序和GC的输出观察耗尽的过程。
程序输出(摘取了部分)
...... [Loaded ClassA from file:/D:/classes/] total: 64887 active: 64887 unloaded: 0 [GC 245041K->213978K(536768K), 0.0597188 secs] [Full GC 213978K->211425K(644992K), 0.6456638 secs] [GC 211425K->211425K(656448K), 0.0086696 secs] [Full GC 211425K->211411K(731008K), 0.6924754 secs] [GC 211411K->211411K(726528K), 0.0088992 secs] ............... java.lang.OutOfMemoryError: PermGen space
Java的Metaspace空间:不受限制 (默认)
从上面的截图能够看到,JVM Metaspace进行了动态扩展,本地内存的使用由20MB增加到646MB,以知足程序中不断增加的类数据内存占用需求。咱们也能观察到JVM的垃圾回收事件—试图销毁僵死的类或类加载器对象。可是,因为咱们程序的泄漏,JVM别无选择只能动态扩展Metaspace内存空间。程序加载超过10万个类,而没有出现OOM事件。
Java的Metaspace空间:128MB(-XX:MaxMetaspaceSize=128m)
能够从上面的JVisualVM的截图看出:当加载超过2万个类以后,Metaspace被耗尽;与JDK1.7运行时很是类似。咱们也能经过程序和GC的输出观察耗尽的过程。另外一个有趣的现象是,保留的原生内存占用量是设定的最大大小两倍之多。这可能代表,若是可能的话,可微调元空间容量大小策略,来避免本地内存的浪费。
从Java程序的输出中看到以下异常。
[Loaded ClassA from file:/D:/classes/] total: 21393 active: 21393 unloaded: 0 [GC (Metadata GC Threshold) 64306K->57010K(111616K), 0.0145502 secs] [Full GC (Metadata GC Threshold) 57010K->56810K(122368K), 0.1068084 secs] java.lang.OutOfMemoryError: Metaspace
在设置了MaxMetaspaceSize的状况下,该空间的内存仍然会耗尽,进而引起“java.lang.OutOfMemoryError: Metadata space”错误。由于类加载器的泄漏仍然存在,而一般Java又不但愿无限制地消耗本机内存,所以设置一个相似于MaxPermSize的限制看起来也是合理的。
以前不论是不是须要,JVM都会吃掉那块空间……若是设置得过小,JVM会死掉;若是设置得太大,这块内存就被JVM浪费了。理论上说,如今你彻底能够不关注这个,由于JVM会在运行时自动调校为“合适的大小”;
提升Full GC的性能,在Full GC期间,Metadata到Metadata pointers之间不须要扫描了,别小看这几纳秒时间;
隐患就是若是程序存在内存泄露,像OOMTest那样,不停的扩展metaspace的空间,会致使机器的内存不足,因此仍是要有必要的调试和监控。