JVM内存结构

1.7和1.8之间JVM内存结构以及它们的差异

JVM 内存分配

根据 JVM 规范,JVM 内存分为虚拟机栈、堆、方法区、程序计数器、本地方法 stack5个部分。

https://www.programmersought.com/article/74164482693/

名称 特征 作用 配置参数 异常
程序计数器 占用内存小,线程私有,生命周期与线程相同 字节码行号指示器
虚拟机栈 线程私有,生命周期与线程相同,使用连续的内存空间 Java方法执行的内存模型,存储局部变量表、操作栈、动态链接、方法出口等信息 -Xss StackOverflowError/OutOfMemoryError
线程共享,生命周期与虚拟机相同,可以不使用连续的内存地址 保存对象实例,所有对象实例(包括数组)都要在堆上分配 -Xms -Xsx -Xmn OutOfMemoryError
方法区 线程共享,生命周期与虚拟机相同,可以不使用连续的内存地址 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据 -XX:PermSize:16M -XX:MaxPermSize64M / -XX:MetaspaceSize=16M -XX:MaxMetaspaceSize=64M OutOfMemoryError
本地方法栈 线程私有 为虚拟机使用到的 Native 方法服务 StackOverflowError/OutOfMemoryError

1.7和1.8之间JVM内存结构以及它们的差异

事实上,移除永久代的工作是从 JDK 1.7开始的。在 JDK 1.7中,存储在永久生成中的部分数据已经转移到 Java 堆或本机堆。

然而,JDK 1.7中的永久代仍然存在,并且没有被完全删除。

例如,将符号引用转移到本机堆; 将类的文本变量(内嵌字符串)和静态变量转移到 Java 堆。

JDK 1.8 同 JDK 1.7 比,最大的差别就是:元数据区取代了永久代。元空间的本质和永久代类似,都是对 JVM 规范中方法区的实现。

不过元空间与永久代之间最大的区别在于:元数据区并不在虚拟机中,而是使用本地内存。

方法区是java虚拟机规范中定义的一种概念上的区域,不同的厂商可以对虚拟机进行不同的实现。我们通常使用的Java SE都是由Sun JDK和OpenJDK所提供,这也是应用最广泛的版本。而该版本使用的VM就是HotSpot VM。通常情况下,我们所讲的java虚拟机指的就是HotSpot的版本

  • PermGen(永久代)

    Java7及以前版本的Hotspot中方法区位于永久代中。同时,永久代和堆是相互隔离的,但它们使用的物理内存是连续的,永久代的垃圾收集是和老年代捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集。

    Java7中永久代中存储的部分数据已经开始转移到Java Heap或Native Memory中了。比如,符号引用(Symbols)转移到了Native Memory;字符串常量池(interned strings)转移到了Java Heap;类的静态变量(class statics)转移到了Java Heap。

    绝大部分Java程序员应该都见过java.lang.OutOfMemoryError: PremGen space异常。这里的PermGen space其实指的就是方法区。不过方法区和PermGen space又有着本质的区别。前者是JVM的规范,而后者则是JVM规范的一种实现,并且只有HotSpot才有PermGen space,而对于其他类型的虚拟机,如JRockit(Oracle)、J9(IBM)并没有PermGen space。由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。并且JDK 1.8中永久代的参数PermSize和MaxPermSize已经失效。

  • Metaspace(元空间)

    对于Java8,HotSpot取消了永久代,那么是不是就没有方法区了呢?当然不是,方法区只是一个规范,只不过它的实现变了。

    在Java8中,元空间(Metaspace)登上舞台,方法区存在于元空间(Metaspace)。同时,元空间不再与堆连续,而且是存在于本地内存(Native memory)。

    JDK1.7后对JVM架构进行了改造,将类元数据放到本地内存中,另外,将字符串常量池和静态变量放到Java堆里。HotSpot VM将会为类的元数据明确分配和释放本地内存。在这种架构下,类元信息就突破了原来 -XX:MaxPermSize的限制,现在可以使用更多的本地内存。这样就从一定程度上解决了原来在运行时生成大量类造成经常Full GC问题,如运行时使用反射、代理等。所以升级以后Java堆空间可能会增加。

    元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间的最大区别在于:元空间并不在虚拟机中,而是使用本地内存。本地内存(Native memory),也称为C-Heap,是供JVM自身进程使用的。当Java Heap空间不足时会触发GC,但Native memory空间不够却不会触发GC。默认情况下元空间是可以无限使用本地内存的,但为了不让它如此膨胀,JVM同样提供了参数来限制它使用的使用。

    -XX:MetaspaceSize,class metadata的初始空间配额,以bytes为单位,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当的降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize(如果设置了的话),适当的提高该值。

    -XX:MaxMetaspaceSize,可以为class metadata分配的最大空间。默认是没有限制的。

    -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为class metadata分配空间导致的垃圾收集。

    -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为class metadata释放空间导致的垃圾收集。

  • 对于方法区,Java8之后的变化

    移除了永久代(PermGen),替换为元空间(Metaspace)

    永久代中的class metadata(类元信息)转移到了native memory(本地内存,而不是虚拟机)

    永久代中的interned Strings(字符串常量池) 和 class static variables(类静态变量)转移到了Java heap

    永久代参数(PermSize MaxPermSize)-> 元空间参数(MetaspaceSize MaxMetaspaceSize

  • Java8为什么要将永久代替换成Metaspace?

    字符串存在永久代中,容易出现性能问题和内存溢出。

    类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困 难,太小容易出现永久代溢出,太大则容易导致老年代溢出。

    永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

请我吃🍗