JVM学习笔记P3—JVM内存结构

234 阅读5分钟

  本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

前言

  我们先从JDK 7版本的JVM内存结构先说,然后再对比一下JDK 7JDK 8JVM的不同,以及为何修改。

JDK 7中的 JVM 内存结构

image.png

  上图是JDK 7JVM的内存结构布局。JVM内存结构主要有三大块:堆内存、方法区和栈。堆Heap方法区为所有线程共享,而程序计数器线程私有。下面一一介绍这几个区域的作用。

堆(Heap)

  堆HeapJVM所管理的内存中最大的一块。堆是被所有线程共享的一块内存区域,在JVM启动时创建。此内存区域用于存放对象实例,几乎所有的对象实例都在这里分配内存

  堆是垃圾收集器管理的主要区域,如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以堆中还可以细分为:新生代和老年代;再细致一点的话,年轻代又分为,EdenFrom SurvivorTo Survivor

  通常实际应用时常通过参数-Xmx-Xms来控制堆的大小。如果堆空间被耗尽,且此时堆也无法再扩展时,将会抛出OutOfMemoryError异常。

方法区

  方法区与堆一样,是各个线程共享的内存区域,方法区一般存储类信息常量、和类的静态变量等数据。

  在JDK 8之前通常把方法区称为永久代(Permanent Generation),但两者本质上并不等价。仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已。

  方法区一般很少出现垃圾收集行为,但并非数据进入了方法区就如永久代的名字一样“永久”存在了。这个区域的内存回收目标主要是针对常量池的回收类的卸载。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

程序计数器

  程序计数器可以看做是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。程序计数器属于每个线程私有的区域。程序计数器是JVM内存区域中唯一一个不会抛出OutOfMemoryError错误的区域

JVM栈

  与程序计数器一样,JVM栈也是线程私有的,它的生命周期与线程相同。JVM栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会创建一个栈帧Stack Frame),用于存储局部变量表操作栈动态链接方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

  在Java虚拟机规范中,对这个区域规定了两种异常状况:

  • 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常

  • 如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常

本地方法栈

  本地方法栈与JVM栈所发挥的作用是非常相似的,其区别不过是JVM栈为Java方法提供服务,而本地方法栈则是为使用到的Native方法提供服务。与JVM栈一样,本地方法栈区域也会抛出StackOverflowErrorOutOfMemoryError异常。

JDK 8中的 JVM 内存结构

image.png

  上图是JDK 8中的JVM模型示意图,可以看到,JDK 8中的JVM移除了方法区,然后单独划分出一个本地内存区域,里面包含了元数据区和直接内存。

JDK 8中的MetaSpace

  在JDK 8之前,永久代中用于存放类的信息方法的元数据以及常量池。每当一个类初次被加载的时候,它的元数据信息都会放到永久代中。但是永久代是有大小限制的,因此当JVM加载的类过多时,很有可能导致永久代内存溢出,即常见的java.lang.OutOfMemoryError: PermGen,因而在JDK 8提出了MetaSpace,取代了之前的永久代。

  那么,Metaspace是哪一块区域?官方的解释是:

In JDK 8, classes metadata is now stored in the native heap and this space is called Metaspace.

   即把类的元数据信息放到本地堆内存(native heap)中,而这一块区域就叫 Metaspace

为什么提出 MetaSpace

  使用本地内存有什么好处呢?最直接的表现就是OOM问题将不复存在,类元数据信息的存储理论上只受本地内存大小的限制,也就是说本地内存剩余多少,理论上Metaspace就可以有多大,解决了空间不足的问题。不过,让Metaspace变得无限大显然是不现实的,通常还有考虑系统运行时所需的内存。通常使用-XX:MaxMetaspaceSize参数来指定Metaspace区域的大小。JVM默认的MaxMetaspaceSize的大小为21MB

  同时在垃圾回收方面,如果Metaspace的空间占用达到了设定值,那么就会触发GC来收集死亡对象和类的加载器。根据JDK 8的新特性,G1CMS都会很好地收集Metaspace区域,因而为了减少垃圾回收的频率及时间。对Metaspace进行适当的监控和调优是非常有必要的。如果在Metaspace区发生了频繁的Full GC,那么可能表示存在内存泄露Metaspace的空间太小。

总结

image.png