Java 8 的 Metaspace

摘要:
目前,有三个Java虚拟机:HotSpot、oracle JRockit和IBM J9。然后,在Java 8之后的版本中,持久化生成被永久删除,并被Metaspace取代。Metaspace表示,为了避免持久化世代的内存溢出问题,Java 8和更高版本完全移除了持久化世代,并使用Metaspace来替代它。元空间是HotSpot中方法区域的实现。元空间和持久生成之间的最大区别是元空间不存在于虚拟机内存中,而是使用本地内存。因此,元空间的具体大小理论上取决于32-64位系统的可用内存大小。可以看出,它不是无限的,需要配置参数。
Java 8 的 Metaspace

https://www.cnblogs.com/xrq730/p/8688203.html

被废弃的持久代

想起之前面试的时候有面试官问起过我一个问题:Java 8为什么要废弃持久代即Metaspace的作用。由于当时使用的Java 7且研究重心不在JVM上,一下没有回答上来,今天突然想起这个问题,就详细总结一下这个问题。

目前有三大Java虚拟机:HotSpot,oracle JRockit,IBM J9。

JRockit是oracle发明的,用于其WebLogic服务器,IBM JVM是IBM发明的用于其Websphere服务器(所以在某行开发的时候,他们用的是IBM的JDK,因为他们使用的IBM的应用程序服务器Websphere,使用其他JDK可能存在兼容性问题)。

JRockit和J9不存在永久代这种说法。这里只讨论HotSpot虚拟机,这也是目前使用的最多的JVM。Sun JDK7 HotSpot虚拟机的内存模型如下图所示:

Java 8 的 Metaspace第1张

注意到里面有一块METHOD AREA,它是一块线程共享的对象,名为方法区,在HotSpot虚拟机中,这块METHOD AREA我们可以认为等同于持久代(PermGen),在Java 6及之前的版本,持久代存放了以下一些内容:

  • 虚拟机加载的类信息
  • 常量池
  • 静态变量
  • 即时编译后的代码

到了Java 7之后,常量池已经不在持久代之中进行分配了,而是移到了堆中,即常量池和对象共享堆内存。

接着到了Java 8之后的版本(至此篇文章,Java 10刚发布),持久代已经被永久移除,取而代之的是Metaspace。

方法区和永久代的关系

在Java虚拟机规范中,方法区在虚拟机启动的时候创建,虽然方法区是堆的逻辑组成部分,但是简单的虚拟机实现可以选择不在方法区实现垃圾回收与压缩。这个版本的虚拟机规范也不限定实现方法区的内存位置和编译代码的管理策略。所以不同的JVM厂商,针对自己的JVM可能有不同的方法区实现方式。

在HotSpot中,设计者将方法区纳入GC分代收集。HotSpot虚拟机堆内存被分为新生代和老年代,对堆内存进行分代管理,所以HotSpot虚拟机使用者更愿意将方法区称为老年代。

方法区和永久代的关系很像Java中接口和类的关系,类实现了接口,而永久代就是HotSpot虚拟机对虚拟机规范中方法区的一种实现方式。

我们知道在HotSpot虚拟机中存在三种垃圾回收现象,minor GC、major GC和full GC。对新生代进行垃圾回收叫做minor GC,对老年代进行垃圾回收叫做major GC,同时对新生代、老年代和永久代进行垃圾回收叫做full GC。许多major GC是由minor GC触发的,所以很难将这两种垃圾回收区分开。major GC和full GC通常是等价的,收集整个GC堆。但因为HotSpot VM发展了这么多年,外界对各种名词的解读已经完全混乱了,当有人说“major GC”的时候一定要问清楚他想要指的是上面的full GC还是major GC。

为什么要移除持久代

HotSpot团队选择移除持久代,有内因和外因两部分,从外因来说,我们看一下JEP 122的Motivation(动机)部分:

This is part of the JRockit and Hotspot convergence effort. JRockit customers do not need to configure the permanent generation (since JRockit does not have a permanent generation) and are accustomed to not configuring the permanent generation.

大致就是说移除持久代也是为了和JRockit进行融合而做的努力,JRockit用户并不需要配置持久代(因为JRockit就没有持久代)。

从内因来说,持久代大小受到-XX:PermSize和-XX:MaxPermSize两个参数的限制,而这两个参数又受到JVM设定的内存大小限制,这就导致在使用中可能会出现持久代内存溢出的问题,因此在Java 8及之后的版本中彻底移除了持久代而使用Metaspace来进行替代。

Metaspace ( 元空间 )

上面说了,为了避免出现持久代内存溢出的问题,Java 8及之后的版本彻底移除了持久代而使用Metaspace来进行替代。类的元信息被存储在元空间中。元空间没有使用堆内存,而是与堆不相连的本地内存区域。所以,理论上系统可以使用的内存有多大,元空间就有多大,所以不会出现永久代存在时的内存溢出问题。这项改造也是有必要的,永久代的调优是很困难的,虽然可以设置永久代的大小,但是很难确定一个合适的大小,因为其中的影响因素很多,比如类数量的多少、常量数量的多少等。永久代中的元数据的位置也会随着一次full GC发生移动,比较消耗虚拟机性能。同时,HotSpot虚拟机的每种类型的垃圾回收器都需要特殊处理永久代中的元数据。将元数据从永久代剥离出来,不仅实现了对元空间的无缝管理,还可以简化Full GC以及对以后的并发隔离类元数据等方面进行优化。

Metaspace是方法区在HotSpot中的实现,它与持久代最大的区别在于:Metaspace并不在虚拟机内存中而是使用本地内存。因此Metaspace具体大小理论上取决于32位/64位系统可用内存的大小,可见也不是无限制的,需要配置参数。

接着我们模拟一下Metaspace内存溢出的情况,前面说了持久代存放了以下信息:

  • 虚拟机加载的类信息
  • 常量池
  • 静态变量
  • 即时编译后的代码

所以最简单的模拟Metaspace内存溢出,我们只需要无限生成类信息即可,类占据的空间总是会超过Metaspace指定的空间大小的,下面用Cglib来模拟:

 1 public class MetaspaceOOMTest {
 2 
 3     /**
 4      * JVM参数:-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=128m -XX:+PrintFlagsInitial
 5      */
 6     public static void main(String[] args) {
 7         int i = 0;
 8         
 9         try {
10             for (;;) {
11                 i++;
12                 
13                 Enhancer enhancer = new Enhancer();
14                 enhancer.setSuperclass(OOMObject.class);
15                 enhancer.setUseCache(false);
16                 enhancer.setCallback(new MethodInterceptor() {
17                     public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
18                         return proxy.invokeSuper(obj, args);
19                     }
20                 });
21                 enhancer.create();
22             }
23         } catch (Exception e) {
24             System.out.println("第" + i + "次时发生异常");
25             e.printStackTrace();
26         }
27     }
28     
29     static class OOMObject {
30         
31     }
32     
33 }

虚拟机参数设置为"-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=128m",运行代码,结果为:

 1 第15562次时发生异常
 2 net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
 3     at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:345)
 4     at net.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492)
 5     at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:114)
 6     at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:291)
 7     at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)
 8     at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:305)
 9     at org.xrq.commom.test.jvm.MetaspaceOOMTest.main(MetaspaceOOMTest.java:34)
10 Caused by: java.lang.reflect.InvocationTargetException
11     at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
12     at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
13     at java.lang.reflect.Method.invoke(Unknown Source)
14     at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:413)
15     at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:336)
16     ... 6 more
17 Caused by: java.lang.OutOfMemoryError: Metaspace
18     at java.lang.ClassLoader.defineClass1(Native Method)
19     at java.lang.ClassLoader.defineClass(Unknown Source)
20     ... 11 more

可见即使使用了Metaspace,也是有OOM的风险的,但是由于Metaspace使用本机内存,因此只要不要代码里面犯太低级的错误,OOM的概率基本是不存在的。

Metaspace相关JVM参数

最后我们来看一下Metaspace相关的几个JVM参数:

参数名作  用
MetaspaceSize初始化的Metaspace大小,控制Metaspace发生GC的阈值。GC后,动态增加或者降低MetaspaceSize,默认情况下,这个值大小根据不同的平台在12M到20M之间浮动
MaxMetaspaceSize限制Metaspace增长上限,防止因为某些情况导致Metaspace无限使用本地内存,影响到其他程序,默认为4096M
MinMetaspaceFreeRatio当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数,那么虚拟机增长Metaspace的大小,默认为40,即70%
MaxMetaspaceFreeRatio当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放部分Metaspace空间,默认为70,即70%
MaxMetaspaceExpanisonMetaspace增长时的最大幅度,默认值为5M
MinMetaspaceExpanisonMetaspace增长时的最小幅度,默认为330KB
 

============ End

免责声明:文章转载自《Java 8 的 Metaspace》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇KVO的底层实现原理?如何取消系统默认的KVO并手动触发?Ubuntu sudo NOPASSWD设置下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

在ubuntu下利用minicom实现串口通信

    windos有串口调试助手,linux下也有这样的工具——minicom。不过,minicom和linux下的许多工具都一样,也是命令行模式,没有图形化界面供我们享受。作为一款串口调试工具,虽然难看但总比没有的好。     为什么linux下的工具都这么的简朴,没什么华丽的界面,好像回到了解放前。一种可能的原因是linux这种非商业性质的软件,都是...

OS:VM虚拟机连不上网络

VM虚拟机连接不上网络 一、问题描述:      1、我的虚拟机网络连接方式“桥接模式”。虚拟机的网络显示“正常”,但是无法连接网络。如何解决?   2、解决办法:在“虚拟网络编辑器”中,选择“桥接模式”,点击“还原默认设置”。在“虚拟网络编辑器”中,选择“你的网络连接模式”,点击“还原默认设置”。 二、解决方案:   1、找到“虚拟网络编辑器”(编辑 -...

openstack热迁移和冷迁移

迁移类型: *非在线迁移 (有时也称之为‘迁移’)。也就是在迁移到另外的计算节点时的这段时间虚拟机实例是处于宕机状态的。在此情况下,实例需要重启才能工作。 *在线迁移 (或 '真正的在线迁移')。实例几乎没有宕机时间。用于当实例需要在迁移时保持运行。在线迁移有下面几种类型: * 基于共享存储的在线迁移。所有的Hypervisor都可以访问共享存储。 *...

主流服务器虚拟化技术简单使用——KVM(二)

通过Linux工具管理KVM 主流服务器虚拟化技术简单使用——KVM(一)部署了一台KVM主机,提到KVM可以通过命令行工具(virt-install、virsh)和GUI工具(virt-manager)管理虚拟机。实际上virt-install、virsh、virt-manager只是管理工具,如果部署多台KVM,并不需要每一台都安装这些管理工具,因为它...

IDEA连接虚拟机

idea配置连接 转自:https://blog.csdn.net/chengbai121444/article/details/84334959 1、打开链接配置(Tools-->Deployment-->Configuration) 2、自定义虚拟机名称和连接方式(连接方式选择SFTP) 3、配置虚拟机具体信息 4、展示虚拟机页面...

学以致用二十六-----虚拟机磁盘空间清理

最近在虚拟机上调试脚本,安装包解压又删除,解压又删除。结果发现,本地(物理主机)空间消耗很快,最后居然到C盘空间不足了 只有迁移了一个虚拟机到硬盘,再来清理下空间。 在虚拟机里,发现window系列的虚拟机是可以清理磁盘空间的 vm---manage---clean up disk 而在清理linux的虚拟机时,发现没有这个选项 这就比价坑了。那么li...