之前已经讲过了不少有关 JVM 的内容,今天准备将之前没有细讲的部分进行补充,比如:永久代和元空间。

永久代

Java 的内存中有一块称之为方法区的部分,在 JDK8 之前, Hotspot 虚拟机中的实现方式为永久代(Permanent Generation),别的JVM都没有这个东西。

在过去(当自定义类加载器使用不普遍的时候),类几乎是“静态的”并且很少被卸载和回收,因此类也可以被看成“永久的”。另外由于类作为 JVM 实现的一部分,它们不由程序来创建,因为它们也被认为是“非堆”的内存。

永久代是一段连续的内存空间,我们在 JVM 启动之前可以通过设置-XX:MaxPermSize的值来控制永久代的大小,32 位机器默认的永久代的大小为 64M,64 位的机器则为 85M。

永久代的垃圾回收和老年代的垃圾回收是绑定的,一旦其中一个区域被占满,这两个区都要进行垃圾回收。但是有一个明显的问题,由于我们可以通过‑XX:MaxPermSize设置永久代的大小,一旦类的元数据超过了设定的大小,程序就会耗尽内存,并出现内存溢出错误 (java.lang.OutOfMemoryError: PermGen space)。

为什么类的元数据占用内存会那么大?因为在 JDK7 之前的 HotSpot 虚拟机中,纳入字符串常量池的字符串被存储在永久代中,因此导致了一系列的性能问题和内存溢出错误。

为了解决这些性能问题,也为了能够让 Hotspot 能和其他的虚拟机一样管理,元空间就产生了。

元空间

元空间是 Hotspot 在 JDK8 中新加的内容,其本质和永久代类似,都是对 JVM 规范中方法区的实现。不过元空间与永久代之间最大的区别在于:

元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:

-XX:MetaspaceSize 

初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
  
-XX:MaxMetaspaceSize
最大空间,默认是没有限制的。

除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:

-XX:MinMetaspaceFreeRatio

在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集

-XX:MaxMetaspaceFreeRatio

在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集

移除永久代的影响

由于类的元数据分配在本地内存中,元空间的最大可分配空间就是系统可用内存空间。因此,我们就不会遇到永久代存在时的内存溢出错误,也不会出现泄漏的数据移到交换区这样的事情。最终用户可以为元空间设置一个可用空间最大值,如果不进行设置,JVM 会自动根据类的元数据大小动态增加元空间的容量。

注意:永久代的移除并不代表自定义的类加载器泄露问题就解决了。因此,你还必须监控你的内存消耗情况,因为一旦发生泄漏,会占用你的大量本地内存,并且还可能导致交换区交换更加糟糕。

元空间内存管理

元空间的内存管理由元空间虚拟机来完成。

先前,对于类的元数据我们需要不同的垃圾回收器进行处理,现在只需要执行元空间虚拟机的 C++ 代码即可完成。在元空间中,类和其元数据的生命周期和其对应的类加载器是相同的。话句话说,只要类加载器存活,其加载的类的元数据也是存活的,因而不会被回收掉。

准确的来说,每一个类加载器的存储区域都称作一个元空间,所有的元空间合在一起就是我们一直说的元空间。当一个类加载器被垃圾回收器标记为不再存活,其对应的元空间会被回收。在元空间的回收过程中没有重定位和压缩等操作。但是元空间内的元数据会进行扫描来确定 Java 引用。

那具体是如何管理的呢?

元空间虚拟机负责元空间的分配,其采用的形式为组块分配。组块的大小因类加载器的类型而异。在元空间虚拟机中存在一个全局的空闲组块列表。

  1. 当一个类加载器需要组块时,它就会从这个全局的组块列表中获取并维持一个自己的组块列表。
  2. 当一个类加载器不再存活时,那么其持有的组块将会被释放,并返回给全局组块列表。
  3. 类加载器持有的组块又会被分成多个块,每一个块存储一个单元的元信息。组块中的块是线性分配(指针碰撞分配形式)。组块分配自内存映射区域。这些全局的虚拟内存映射区域以链表形式连接,一旦某个虚拟内存映射区域清空,这部分内存就会返回给操作系统。

运行时常量池

运行时常量池在 JDK6 及之前版本的 JVM 中是方法区的一部分,而在 HotSpot 虚拟机中方法区的实现是永久代(Permanent Generation)。所以运行时常量池也是在永久代的。

但是 JDK7 及之后版本的 JVM 已经将字符串常量池从方法区中移了出来,在堆(Heap)中开辟了一块区域存放运行时常量池。

String.intern()是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。

存在的问题

前面已经提到,元空间虚拟机采用了组块分配的形式,同时区块的大小由类加载器类型决定。类信息并不是固定大小,因此有可能分配的空闲区块和类需要的区块大小不同,这种情况下可能导致碎片存在。元空间虚拟机目前并不支持压缩操作,所以碎片化是目前最大的问题。

总结

曾经的永久代,因为容易产生 OOM 而被优化成了元空间,但即便这样,依然存在着问题,不知道 JDK 之后还会怎样优化呢?

有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。

https://death00.github.io/

JVM 知识点补充——永久代和元空间的更多相关文章

  1. 面试官,Java8 JVM内存结构变了,永久代到元空间

    在文章<JVM之内存结构详解>中我们描述了Java7以前的JVM内存结构,但在Java8和以后版本中JVM的内存结构慢慢发生了变化.作为面试官如果你还不知道,那么面试过程中是不是有些露怯? ...

  2. 对于JVM中方法区,永久代,元空间以及字符串常量池的迁移和string.intern方法

    在Java虚拟机(以下简称JVM)中,类包含其对应的元数据,比如类的层级信息,方法数据和方法信息(如字节码,栈和变量大小),运行时常量池,已确定的符号引用和虚方法表. 在过去(当自定义类加载器使用不普 ...

  3. JAVA8 JVM内存结构变了,永久代到元空间

    在文章<JVM之内存结构详解>中我们描述了Java7以前的JVM内存结构,但在Java8和以后版本中JVM的内存结构慢慢发生了变化.作为面试官如果你还不知道,那么面试过程中是不是有些露怯? ...

  4. JVM内存结构从永久代到元空间

    在文章<JVM之内存结构详解>中我们描述了Java7以前的JVM内存结构,但在Java8和以后版本中JVM的内存结构慢慢发生了变化.作为面试官如果你还不知道,那么面试过程中是不是有些露怯? ...

  5. JVM体系结构之七:持久代、元空间(Metaspace) 常量池==了解String类的intern()方法、常量池介绍、常量池从Perm-->Heap

    一.intern()定义及使用 相信绝大多数的人不会去用String类的intern方法,打开String类的源码发现这是一个本地方法,定义如下: public native String inter ...

  6. JVM —— 移除永久代

    近期准备生产环境 JDK 升级到 1.8,本地先升级了下,发现 -XX:PermSize 和 -XX:MaxPermSize 已经失效,取而代之的是一个新的区域 -- Metaspace(元数据区). ...

  7. jvm——metaspace代替永久代

    https://mp.weixin.qq.com/s?__biz=MzIzNjI1ODc2OA==&mid=2650886860&idx=1&sn=f8bc6ab03d7a07 ...

  8. 转:Java8内存模型—永久代(PermGen)和元空间(Metaspace)

    一.JVM 内存模型 根据 JVM 规范,JVM 内存共分为虚拟机栈.堆.方法区.程序计数器.本地方法栈五个部分. 1.虚拟机栈:每个线程有一个私有的栈,随着线程的创建而创建.栈里面存着的是一种叫“栈 ...

  9. Java8内存模型—永久代(PermGen)和元空间(Metaspace)(转)

    Java8内存模型—永久代(PermGen)和元空间(Metaspace) 查看原文点击传送门:http://www.cnblogs.com/paddix/p/5309550.html 提示:本文做了 ...

随机推荐

  1. 浅谈HDFS(二)之NameNode与SecondaryNameNode

    NN与2NN工作机制 思考:NameNode中的元数据是存储在哪里的? 假设存储在NameNode节点的硬盘中,因为经常需要随机访问和响应客户请求,必然效率太低,所以是存储在内存中的 但是,如果存储在 ...

  2. [phyton]文件的简单读写练习

    f.open() 用于打开一个文件. f=open("record.txt","w",encoding="utf-8")#打开文件,设置文件 ...

  3. 环境搭建-CentOS集群搭建

    环境搭建-CentOS集群搭建 写在前面 最近有许多小伙伴问我,大数据的hadoop分布式集群该如何去搭建.所以,想着,就写一篇博客,帮助到更多刚入门大数据的人.本博客会一步一步带你实现一个Hadoo ...

  4. MySQL命令窗口出现中文乱码的解决方法

    查询表语句的时候,出现了中文乱码,但是用Navicat for MySQL查看的时候却是正常的,字符集都是设置的utf-8,如下图所示:     其实上大学学习java的时候也遇到了中文乱码但是却没有 ...

  5. 针对于ECMA5Script 、ECMAScript6、TypeScript的认识

    什么是ECMAScript.什么又是ECMA? Ecma国际(Ecma International)是一家国际性会员制度的信息和电信标准组织.1994年之前,名为欧洲计算机制造商协会(European ...

  6. mac 安装redis及phpredis扩展

    下载phpredis扩展安装包.git clone https://github.com/nicolasff/phpredis.git: 解压后,进入该目录: 依次执行以下操作完成安装: /Appli ...

  7. spring boot通过Jedis来操作redis

    idea中新建spring boot项目,引入jedis依赖 <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> ...

  8. 通过实例理解Java网络IO模型

    网络IO模型及分类 网络IO模型是一个经常被提到的问题,不同的书或者博客说法可能都不一样,所以没必要死抠字眼,关键在于理解. Socket连接 不管是什么模型,所使用的socket连接都是一样的. 以 ...

  9. Mariadb 基于Mycat实现读写分离

    环境:Mariadb主:192.168.200.129 Mariadb从:192.168.200.114 Mycat    :192.168.200.112 (1)      安装jdk,先查看本机是 ...

  10. Adobe PS常用快捷键

    ps使用快捷键 新建图层    Ctrl+Shift+N 取消选择区  Ctrl + D 新建标题    Ctrl + N 图片放大 Alt+鼠标滑动 图片位置拖动    空格 + 鼠标拖动 移动图层 ...