当前位置: 首页 > 科技观察

Java8新特性探索——告别OOM:Permgen

时间:2023-03-16 12:30:54 科技观察

PermGen空间简介PermGen空间的全称是PermanentGenerationspace,指的是内存的最大存储区域。先说说为什么内存有好处:这部分是用来存储Class和Meta信息的,Class在加载的时候是放到PermGen空间区域的,不同于存放Instance的Heap区域,所以如果你的APP加载很多CLASS、PermGen空间错误都可能发生。Web服务器预编译JSP时,这种错误很常见。JVM的种类很多,比如Oralce-SunHotspot、OralceJRockit、IBMJ9、淘宝JVM(淘宝好!)等等。当然,武林之首是热点,这一点没有争议。需要注意的是PermGen空间只有Oracle-SunHotspot才有,JRockit和J9没有这个区域。元空间(MetaSpace)一个新的内存空间诞生JDK8HotSpotJVM将去掉***区,使用本地内存存储类元数据信息并称之为:元空间(Metaspace);这类似于OracleJRockit和IBMJVM非常相似,如下图所示,这意味着不再有java.lang.OutOfMemoryError:PermGen问题,您无需调整和监视内存使用...但是等等,它是那天早上说太晚了。默认情况下,这些更改是透明的,我们的下一个演示会让您知道您仍然需要注意类元数据的内存使用情况。重要的是要记住,这个新特性也不会神奇地消除由类和类加载器引起的内存泄漏。java8中元空间总结如下:PermGen空间现状这部分内存空间将被彻底清除。JVM参数:PermSize和MaxPermSize将被忽略并给出警告(如果启用时设置了这两个参数)。元空间内存分配模型大多数类元数据都分配在本地内存中。用于描述类元数据的“类”已被删除。元空间容量默认情况下,类元数据仅受可用本地内存的限制(容量取决于32位或64位操作系统的可用虚拟内存大小)。用于限制为类元数据分配的本机内存大小的新参数(MaxMetaspaceSize)。如果不指定该参数,元空间将在运行时根据需要动态调整。MetaspaceGarbageCollection当元数据使用量达到“MaxMetaspaceSize”参数设置的值时,将对死类和类加载器进行垃圾收集。及时监控和调整元空间对于减少垃圾收集频率和延迟是必要的。连续的元空间垃圾回收表明可能存在由类、类加载器或不正确的大小设置引起的内存泄漏。对Java堆内存的影响一些杂项数据已移至Java堆空间。升级到JDK8后,你会发现Java堆空间变大了。元空间监控元空间使用情况可以从HotSpot1.8详细的GC日志输出中获取。Jstat和JVisualVM这两个工具在使用b75版本测试的时候已经更新了,但是还是可以看到老PermGen空间的样子。上面已经在理论上进行了充分的解释,让我们通过“泄漏”程序来观察新的内存空间……#p#PermGenvs.Metaspaceruntimecomparison为了更好的理解Metaspace内存空间的运行时行为,下面将进行几个场景的测试:使用JDK1.7运行Java程序,监控耗尽默认PermGen内存空间85MB。使用JDK1.8运行Java程序,监控新的Metaspace内存空间的动态增长和垃圾回收过程。使用JDK1.8运行Java程序,模拟“MaxMetaspaceSize”参数设置的128MBMetaspace内存空间耗尽。先创建一个模拟PermGenOOM的代码1publicclassClassA{2publicvoidmethod(Stringname){3//donothing4}5}上面是一个简单的ClassA,编译成类字节码放到D:/classes下测试代码使用URLClassLoader加载这个类型,把上面的class编译成class01/**02*SimulatePermGenOOM03*@authorbenhail04*/05publicclassOOMTest{06publicstaticvoidmain(String[]args){07try{08//准备url09URLurl=新File("D:/classes").toURI().toURL();10网址[]网址={网址};11//获取关于类型加载的JMX接口12ClassLoadingMXBeanloadingBean=ManagementFactory.getClassLoadingMXBean();13//用于缓存类加载器14ListclassLoaders=newArrayList();15while(true){16//加载类型并缓存类加载器实例17ClassLoaderclassLoader=newURLClassLoader(urls);18classLoaders.add(类加载器);19classLoader.loadClass("ClassA");20//显示数量信息(总共加载的类型数,当前有效的类型数,已经卸载的类类型编号)21System.out.println("total:"+loadingBean.getTotalLoadedClassCount());22System.out.println("active:"+loadingBean.getLoadedClassCount());23System.out.println("卸载:"+loadingBean.getUnloadedClassCount());24}25}catch(Exceptione){26e.printStackTrace();27}28}29}虚拟机参数设置如下:-verbose-verbose:gc设置-verbose参数获取类型加载卸载信息Set-verbose:gc获取垃圾回收信息JDK1.7@64-bit–PermGenExhaustionTestJava1.7的PermGen默认空间是85MB(或者可以通过-XX:MaxPermSize=XXXm指定)从上面JVisualVM的截图可以看出:当加载超过60000个类时,PermGen就被耗尽了我们也可以通过程序和GC输出来观察耗尽过程。程序输出(提取部分)01......02[LoadedClassAfromfile:/D:/classes/]03total:6488704active:6488705unloaded:006[GC245041K->213978K(536768K)??,0.0597188secs]07[FullGC213978K->211425K(644992K),0.6456638secs]08[GC211425K->211425K(656448K),0.0086696secs]09[FullGC211425K->211411K(731008K),0.6924754secs]10[GC211411K->211411K(726528K),0.0088992secs]11...................12java.lang.OutOfMemoryError:PermGenspaceJDK1.8@64-bit–Metaspacesizedynamicadjustment测试Java的Metaspacespace:unlimited(default)从上面的截图可以看出,JVMMetaspace已经动态扩展,本地内存的使用从20MB增加到646MB.为了满足程序中类数据不断增加的内存占用,我们还可以观察JVM垃圾回收事件——试图销毁死类或类加载器对象。但是,由于我们程序的漏洞,JVM不得不动态扩展Metaspace内存空间。该程序加载了超过100,000个没有OOM事件的类。JDK1.8@64-bit–MetaspacelimitedtestJava的Metaspacespace:128MB(-XX:MaxMetaspaceSize=128m)从上面JVisualVM的截图可以看出:当类加载超过20000个时,Metaspace被耗尽;和JDK1.7runtime很相似。我们也可以通过程序和GC输出来观察耗尽过程。另一个有趣的现象是保留的本机内存占用量是设置的最大大小的两倍。这可能表明,如果可能,微调元空间容量大小调整策略以避免浪费本地内存。在Java程序的输出中可以看到以下异常。1[LoadedClassAfromfile:/D:/classes/]2total:213933active:213934unloaded:05[GC(MetadataGCThreshold)64306K->57010K(111616K),0.0145502secs]6[FullGC(MetadataGCThreshold)57010K->5681(122368K),0.1068084secs]7java.lang.OutOfMemoryError:Metaspace在设置了MaxMetaspaceSize的情况下,这个空间的内存还是会被耗尽,然后会触发“java.lang.OutOfMemoryError:Metadataspace”的错误。因为类加载器泄漏仍然存在,而且一般Java不希望无限期地消耗本机内存,因此设置一个类似于MaxPermSize的限制似乎是合理的。总结以前,不管需要与否,JVM都会吃掉那块空间……如果设置得太小,JVM就会死掉;如果设置太大,JVM会浪费这些内存。理论上来说,现在不需要关注这个,因为JVM会在运行时自动调整到“合适的大小”;为了提高FullGC的性能,在FullGC期间,不需要在Metadata和Metadata指针之间进行扫描,不要小看这几纳秒;隐患是如果程序有内存泄漏,比如OOMTest,如果metaspace空间不断扩大,机器内存就会不足,所以还是需要进行必要的调试和监控。