相信软件测试工程师都知道在java 8之前JVM第三代都是持久代PermGen,在java 8和之后的版本都是Metaspace元空间。
持久代PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,那么为什么会出现内存溢出呢?这是因为存放Class的信息在被加载时会放入到持久代PermGen space区域,当如果出现很多Class的话,那么就会可能出现PermGen space错误。
JVM类型也很多种,比如 Oralce-Sun Hotspot、ralce JRockit、IBM J9、Taobao JVM等等。当然用的最多的还是Hotspot。需要注意的是,PermGen space是Oracle-Sun Hotspot才有,JRockit以及J9是没有这个区域。一般现在讨论的多的是Hotspot的JVM,所以通常会说持久代。
持久代中包含了虚拟机中所有通过反射获取到的数据,如类和方法对象,不同的Java虚拟机之间可能会进行类的共享操作,因此持久代又分为只读区和读写区。关于JVM运行时会使用到多少持久代的空间取决于应该程序用到了多少类。除此之外,Java SE库中的类和方法也都存储在这里。当JVM对类的操作完成后,发现不再需要使用这个类时,就会将这个类释放出来,释放的空间需要使用Full GC进行回收。
那么持久代是如何来设置呢?在JVM中可以通过MaxPermSize参数来设置,默认值为64M,Java堆中分配的区域尽量是连续的,如果非连续的堆空间,那要定位出持久代到新对象的引用是非常复杂的也是很耗时的。在堆中有一种记忆集叫卡表,可以记录某个内存代在普通对象指针的修改情况。当持久代都使用了后,系统就会抛出OutOfMemoryError的异常信息,当然解决的办法就是清理了不用的类或者增加MaxPermSize的值。
那么在现在的JVM中为何将原来的持久代取消了呢?因为原来的持久代有以下一些缺点:
1) 以前的版本中PermGen会存储一些字符串,PermGen内存的大小是通过-xx:PermSize这个参数来设置的,但是由于字符串池的大小经常是变化的,导致设置-xx:PermSize这个参数变的困难,这样很容易出现OOM提示的错误 ,java.lang.OutOfMemoryError: PermGen space。
2) 以前将方法主要存储在PermGen,现在将方法都移动Metaspace,Metaspace不在JVM中,而是在本地的内存。
3) 减少经常使用Full GC的频率。
根据上面的各种原因,永久代最终被移除,永久代移除后,原来永久代中的方法区移至Metaspace元空间中,字符串常量移至Java Heap堆中。
Metaspace元空间由两大部分组成:Klass Metaspace和NoKlass Metaspace。
1) Klass Metaspace
Klass Metaspace是用来存放klass的,就是class文件在JVM中运行时的数据结构,这部分内存空间默认放在Compressed Class Pointer Space中,是一个连续的内存区域块,紧接着Heap堆,在JVM中可以-XX:CompressedClassSpaceSize来控制这块内存大小,默认值为1G。
Compressed Class Pointer Space不是必须存在的,如果设置了-XX:-UseCompressedClassPointers或者设置的-Xmx值大于32G,那么这块内存就不会存在,这种情况下klass就会存在NoKlass Metaspace中。
2) NoKlass Metaspace
NoKlass Metaspace专门来存klass相关的其它内容,如method、constantPool等,它可以是多个不连续的内存组成。这块内存是必须的,不能不存在,并且是在本地内存中进行分配。
Klass Metaspace和NoKlass Metaspace两个部分的内存空间是所有类加载器都可以共享的,当然这些加载器都需要分配内存,为了更好的管理这些类加载器,每个类加载器都有一个SpaceManger空间管理来管理这些类加载器如何分配内存大小。分配的内存都是来自于实内存,如果Klass Metaspace用完了,那么就会提醒OutOfMemoryError异常,但一般的情况下是不会出现这种情况的,NoKlass Metaspace是由一小块一小块内存累加起来的。
元空间和持久代在使用内存上是很类似的,都是对JVM规范中方法区的实现,但是他们分配内存是不同的,持久代内存是在虚拟机中,但是元空间是本地内存,所以正常情况下元空间的大小不受限制,如果说受限制那只是受本地内存限制,并且元空间一般是不可能会出现OutOfMemoryError异常的。设置元空间大小一般可以通过以下几个参数来实现:
1) -XX:MetaspaceSize
-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么就会提高该元空间的值,但不管怎么提高或增加元空间的值,都不能超过MaxMetaspaceSize所设置的值。
2) -XX:MaxMetaspaceSize
-XX:MaxMetaspaceSize表示元空间可以达到的最大值,默认是没有限制的,取决于机器的内存,限制类的元数据使用的内存大小,以免出现虚拟内存切换以及本地内存分配失败。如果怀疑有类加载器出现泄露,应当设置这个参数; 元空间的初始大小是21M,这是GC的初始的高水位线,超过这个大小会进行Full GC来进行类的回收。 如果启动后GC过于频繁,请将该值设置得大一些,可以设置成和持久代一样的大小,这个GC可以不用那么频繁的执行。
3) -XX:MinMetaspaceFreeRatio
-XX:MinMetaspaceFreeRatio表示GC之后,最小的Metaspace剩余空间容量的百分比,目的是控制减少为分配空间所导致的垃圾收集。MinMetaspaceFreeRatio和下面的MaxMetaspaceFreeRatio,主要是影响触发metaspaceGC的阈值。默认值为40,表示每次GC完之后,如果metaspace内存的空闲比例小于MinMetaspaceFreeRatio%,那么将尝试做扩容,增大触发metaspaceGC的阈值。不过这个增量至少是MinMetaspaceExpansion才会做,不然不会增加这个阈值。这个参数主要是为了避免触发metaspaceGC的阈值和GC之后committed的内存的量比较接近,于是将这个阈值进行扩大。
注:这里不用GC之后used的量来算,主要是担心可能出现committed的量超过了触发metaspaceGC的阈值,这种情况一旦发生会很危险,会不断做GC。
4) -XX:MaxMetaspaceFreeRatio
-XX:MaxMetaspaceFreeRatio表示GC之后,最大的Metaspace剩余空间容量的百分比,目的是控制减少为释放空间所导致的垃圾收集。默认值为70,这个参数和上面的参数基本是相反的,是为了避免触发metaspaceGC的阈值过大,而想对这个值进行缩小。这个参数在GC之后committed的内存比较小的时候并且离触发metaspaceGC的阈值比较远的时候才进行调整。
5) -verbose
-verbose通过这个参数可以获取类型加载和卸载的信息。
那么元空间这些内存是怎么来管理和分配或者说回收的呢?元空间的内存管理是由元空间虚拟机来管理,通常说的一个元空间是指一个类加载器的存储区域,当然所有元空间合在一起就称之为元空间,以前对于类的元数据需要不同的垃圾回收器来进行处理,但现在只需要执行虚拟机的C++代码即可以完成,并且类和其元数据的生命周期与类加载器是相同的,如果类加载器还是存活的话,那么类的元数据也是存活的,这个时候是不会被回收。当一个类加载器被垃圾回收器标记为不再存活时,其对应的元空间就会被回收。
元空间虚拟机负责元空间的分配,其采用的形式为组块分配,组块的大小因类加载器的类型而异,在元空间虚拟机中存在一个全局的空闲组块列表,当一个类加载器需要一个组块时,它就会从这个全局的组块列表中获取,并不断的维持一个属于自己的组块列表,当类加载器不再存活时,这个组块也就会被释放,并返回给全局组块列表,类加载器拥有的组块会被分成很多个块,每个块存储一个单元的元信息,组块中的每个块是线性分配的,组块分配自内存映射区域。这些全局的虚拟内存映射区域以链表形式连接,一旦某个虚拟内存映射区域清空,这部分内存就会返回给操作系统。
如果需求监控Metaspace元空间的信息,可以使用JDK自带的一些工具来展示Metaspace的详细信息:
针对Metaspace,JDK自带的一些工具做了修改来展示Metaspace的信息:
jmap -clstats :打印类加载器的统计信息(取代了在JDK8之前打印类加载器信息的permstat)。
jstat -gc :Metaspace的信息会被打印出来。
jcmd GC.class_stats:这是一个新的诊断命令,可以使用户连接到存活的JVM,转储Java类元数据的详细统计。
今天关于“PermGen与Metaspace有什么区别”的内容就学习完啦,大家喜欢的话记得每天来这里和小编一起学习涨薪技能哦。(笔芯)
川石学院零基础入门到精通课程免费学习即扫下方二维码,名师在线辅导!