JVM内存区域详解,一文弄懂JVM内存【内存分布、回收算法、垃圾回收器】

视频讲解地址

学习文档


一、内存区域

JVM内存区域详解,一文弄懂JVM内存【内存分布、回收算法、垃圾回收器】_第1张图片


区域 描述 线程私有 如何溢出
程序计数器 为了线程切换后能恢复到正确的执行位置,每个线程都要有一个独立的程序计数器。 唯一一个不会内存溢出的地方
虚拟机栈 1. 每个方法执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、方法出口等信息。
2. 每一个方法从调用到执行完毕都对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
3. 局部变量表存储了编译期可知的各种Java基本数据类型和对象引用。
1. 线程请求的栈深度大于虚拟机所允许的深度时抛出 StackOverFlowError异常。
2. 栈扩容时无法申请到足够内存的时候抛出 OutOfMemoryError。
本地方法栈 和虚拟机栈类似,本地方法栈是为本地(Native)方法服务的 同【虚拟机栈】
方法区 线程共享,用于存放被虚拟机加载后的类型信息、常量、静态变量、即时编译器编译后的代码缓存数据。
注:运行时常量池、元空间都属于方法区的一部分。
无法满足新的内存分配会抛出 OutOfMemoryError
堆(垃圾收集的主要区域) 1. 基本上所有的对象都是在堆上分配的。
2. Java堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。
无法满足新的内存分配会抛出 OutOfMemoryError
直接内存 NIO通过使用Native函数库直接分配对外内存。不受Java堆大小限制,但是受机器的物理内存限制。 无法满足新的内存分配会抛出 OutOfMemoryError

堆其实就是一大块内存区域,是用来存放对象的,对于一个应用来说最耗费内存的就是“对象”。因为在运行的过程中会创建无数个对象,所以内存回收(垃圾回收)的时候主要就是针对堆的垃圾进行回收。


常见的堆划分是:

  1. 把堆分为新生代和老年代
  2. 新生代分为一个Eden区和两个Survivor区,它们的内存占比是 8:1:1
  3. 注:但G1却不是这样的,它把堆分成数个大小相同的Regin块

二、回收时机


上面我们谈到内存空间,内存是有限的,想要健康持续的运行下去,就一定要回收“垃圾”。

那怎么判定一个对象是不是垃圾呢,就成了新的问题。

算法 描述 备注
引用计数算法 当某个对象被引用的时候引用计数器就加一,引用失效时就减一,当没有引用的时候就说明可以被回收了。 几乎没有使用它的,因为它无法解决循环依赖的问题。
可达性分析 某些对象被定义为根(GC Roots),从GC Roots向下搜索的路径成为“引用链”,如果某个对象到GC Roots间没有任何引用,那说明它不可达,它就可以被回收了。 目前都是用这种算法。

GC Roots并不是一个固定的对象,它是一组对象:

  1. 在虚拟机栈引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
  2. 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
  3. 在方法区中常量引用的对象,譬如字符串常量池里的引用。
  4. 在本地方法栈中JNI(即 Native方法)引用的对象。
  5. Java虚拟机内部的引用,如基本数据类型对应的Class对象。
  6. 所有被同步锁(Sync)持有的对象。
  7. 除了这些固定的GC Roots,根据不同的垃圾回收器还可以有其他“临时性”地加入。

一个对象是否可以被回收,是要看有没有被GC Roots触达,而不是仅仅是 触达

JVM内存区域详解,一文弄懂JVM内存【内存分布、回收算法、垃圾回收器】_第2张图片


不管是引用计数算法,还是可达性分析,都提到了引用。Java中的引用并不是简单的引用,它有四种不同的引用

引用类型 描述
强引用 被强引用的对象不会被回收。 new 的方式创建就是产生强引用。
软引用 被软应用的对象,只有在内存不足的时候才会被回收。
弱引用 被弱引用的对象,在下一次垃圾回收的时候就会被回收。
虚引用 虚引用又被称为幽灵引用,它和回收没太大关系,只是在回收的时候,会收到一个系统的通知。

三、回收算法


已经知道了哪些对象是可以回收的,那就需要按照某种回收算法,去回收它们。
名称 描述 优缺点
标记-清除 标记所有未被引用的对象,在GC的时候清空它们。 优点:简单直观
缺点:会产生大量的内存碎片。如果下次需要分配一个大对象,没有连续空间的时候会提前触发GC。
标记-整理 标记所有被引用的对象,将还存活的对象移动到一端,然后清除边界外的内存。 优点:减少了内存碎片,相对于标记-清除减少了碎片化问题。
缺点:移动对象需要成本。
标记-复制 将内存划分成两个相同大小的块,每次只使用其中一块。当一块的内存用完了,就将还存活的对象复制到另外一块,然后再把之前那块空间清空。

IBM公司有一项研究的结论是:新生代中98%的对象熬不过第一轮回收,所以不必按照 1:1 的比例来划分。
新生代分为三个区:一个Eden、两个Survivor,对象优先分配在Eden区,每次只使用Eden和一个Survivor,在垃圾回收的时候把还存活的对象移动到另外一个没有被使用的Survivor中。如果Survivor区空间不够,会把对象移动到老年代。


1. 两个Survivor,在有的地方被称为From和 To,或 S0、S1
2. 默认情况下Eden和两个Survivor的比例是 8:1:1
优点:减少了内存碎片,适用于对象生命周期短的场景。
缺点:空间浪费和复制成本。

四、回收器


回收算法是理论,回收器是实践,不同回收器都是基于理论进行真正的实践,在讨论回收器之前需要先了解下面几个点

  1. STW:Stop The World 的缩写,意味着在GC的时候,其它线程无法工作。
  2. 并行、并发:多个GC线程一起工作就是并行,GC线程和用户线程一起工作就是并发。
  3. 吞吐量:运行用户代码时间 / 运行用户代码时间+垃圾回收时间,通过算法可以得出想要提高吞吐量,就必须减少垃圾回收耗时。

下面是各个回收器的作用域,连线表示它们可以组合使用,红色线表示JDK9已经不推荐了。

JVM内存区域详解,一文弄懂JVM内存【内存分布、回收算法、垃圾回收器】_第3张图片


名称 描述 效率 STW 回收算法 作用域 目标 使用场景 回收步骤
Serial 它是单线程工作的,且在进行垃圾回收的时候,必须暂停所有的工作线程。 串行 标记-复制 新生代 快速的回收 1. 在客户端模式下的默认新生代收集器。
2. 对于内存资源不多的情况下,它是所有收集器里额外内存消耗最小的。
JVM内存区域详解,一文弄懂JVM内存【内存分布、回收算法、垃圾回收器】_第4张图片
Serial Old 同上 串行 标记-整理 老年代 快速的回收 同上 同上
ParNew 它支持多线程并行回收垃圾,其它与Serial收集器没什么大的差别。 并行 标记-复制 新生代 快速的回收 是除了Serial之外唯一可以和CMS收集器配合工作的。 JVM内存区域详解,一文弄懂JVM内存【内存分布、回收算法、垃圾回收器】_第5张图片
Parallel Scavenge 它和ParNew有很多相似的地方,不同的是它关注的是 达到一个可控制的吞吐量。 并行 标记-复制 新生代 提高吞吐量 大规模的后台服务、批处理任务等,对吞吐量要求高的场景。 同上
Parallel Old 同上 并行 标记-整理 老年代 提高吞吐量 同上 同上
CMS CMS可以并发的去执行,并且可以部分STW的回收器。 并发 部分STW 标记-清除 老年代 快速的回收 对延迟敏感的应用,需要较短垃圾回收停顿时间。 JVM内存区域详解,一文弄懂JVM内存【内存分布、回收算法、垃圾回收器】_第6张图片
G1 1. G1将堆分成多个大小相同的Regin(大小在 1-32MB,默认是 2048个),每一个Regin都可以充当新生代或老年代中的某个区。
2. Regin中还有一个特殊的区域 Humongous Regin(大对象直接分在老年代,防止了反复拷贝移动) ,G1规定大小超过普通Regin一半的对象是大对象,大对象就存在Humongous Regin,它会独占一个、或多个连续Region。
3. 使用 Mixed GC 回收(下面讲)
并发 部分STW 标记-整理 通吃 满足高吞吐量的同时,尽可能地减少垃圾回收耗时 大内存应用、需要可预测停顿时间的应用,JDK9开始成为默认的垃圾回收器。 G1对于CMS并不是完全的碾压,G1的实现更加复杂,所以它所额外使用的内存和程序运行时的负载都比CMS高。

CMS和G1的对比

  1. CMS会产生内存碎片,且无法回收浮动垃圾(因为清理的时候是并发的,这时候工作线程可能产生新的垃圾)
  2. 关注点:CMS是快速的回收,G1是在限定的时间内(这个时间可以自定义),最大限度的回收(会把垃圾排序然后回收收益最大的部分)
  3. 作用范围:CMS是作用于老年代,G1是都可以
  4. 内存分布:G1对堆内存进行了大小相同的Regin划分,在Regin的基础上再进行新生代和老年代划分,CMS是传统的新生代、老年代分布
  5. 性能:CMS对CPU资源非常敏感可能会影响正常请求(因为回收的时候很多阶段是并发的),G1对内存要求较高(想想它对堆做了那么复杂的划分和逻辑,这些都是需要额外内存和cup支持的),所以更适合大堆
  6. G1的目的是代替CMS,JDK9之后默认的垃圾收集器就是G1,但CMS并不是一无是处,在内存小的时候还是更合适的

MinorGC、MajorGC、Full GC、Mixed GC

  1. MinorGC:是对新生代回收时候的GC,有时候也叫 youngGC
  2. MajorGC:是针对老年代的垃圾回收操作。出现 Major GC 通常会出现至少一次 Minor GC。有时候也叫 OldGC
  3. Full GC:Full GC 会清理整个堆和方法区,包括年轻代、老年代和方法区。FullGC对于方法区的回收主要是满足下面三个条件
    • Java堆中不存在该类的任何实例对象;
    • 加载该类的类加载器已经被回收;
    • 该类对应的java.lang.Class对象不在任何地方被引用,且无法在任何地方通过反射访问该类的方法。
  1. Mixed GC:它是G1才有的。不再是每次回收新生代、老年代了,而是把内存排序后,回收利益最大的,这也是G1这个名字的由来。

五、其它


注:G1并不是终点,后面还有ZGC它关注更低的延迟,但现在大家都还没用到,暂时先不去学习了


参考:

  1. 本文严重参考了《深入理解Java虚拟机》这本书
  2. https://tech.meituan.com/2016/09/23/g1.html

你可能感兴趣的:(#,Java,中阶,jvm,算法,垃圾回收器,G1,JVM详解)