Java内存区域与垃圾回收

Java内存区域与垃圾回收

Scroll Down
cropped-1366-768-786077
cropped-1366-768-786077

文章是基于《深入理解Java虚拟机-JVM高级特性与最佳实践》(第二版)这本书所做的知识总结。

这本书在各大书单里面大多数都会被推荐,确实是一本很好的书,值得多看几遍。深入了解JVM,才能更好的对JVM进行调优。我会把重要章节的内容,结合自己的理解,做一个知识梳理。

运行时数据区域

Java虚拟机会把所管理的内存划分为以下几个运行时数据区域:

  • 程序计数器
  1. 每一个线程都有自己的程序计数器,作用是记录线程正在执行的虚机机字节码指令的地址,像分支、循环、跳转、异常处理、线程恢复等基础功能都要依赖这个计数器来完成。
  2. 此区域不会发生OutOfMemoryError错误。
  • 虚拟机栈
  1. Java虚拟机栈是线程私有,生命周期与线程相同。线程内的每个方法执行时都会创建一个栈帧,用于存储局部变量表操作数方法入口等信息。
    局部变量表记录的是基本数据类型(byte、short、int、long、float、double、boolean、char)以及对象的引用。
  2. 局部变量表所需要的内存空间在编译期间就已经完全确定,方法的运行不会改变该内存的大小。
  3. 该区域有可能发生这两种异常:
    1)StackOverflowError 异常:当栈深度大于虚拟机允许的深度时发生。
    2)OutOfMemoryError 异常:如果虚拟机栈允许扩展,当扩展时候无法申请到足够的存储时发生。
  4. -Xss设置栈大小
  • 本地方法栈
    该区域是本地方法允许时申请的,作用与虚拟机栈类似。同样可能会有StackOverflowError异常与 OutOfMemoryError 异常。
  1. 堆会被所有的线程共享,存放的是各种对象。
  2. 如果分配对象是,堆内存不足,且堆没法扩展时,会有 OutOfMemoryError 异常。
  3. -Xmx最大堆,-Xms最小堆。
  • 方法区
  1. 该区域也会被各个线程共享,用于存储已被虚拟机加载的类信息常量静态变量等数据。
  2. 也被成为"永久代"(Permanent Generation)。
  3. 方法区无法满足内存分配的需求时,会有 OutOfMemoryError 异常。
jvm内存区域
jvm内存区域

对象的内存布局

对象在内存中存储的布局可以分为3个区域:

  1. 对象头
  2. 实例数据
  3. 对齐填充

对象头包含两部分信息:

  1. 存储对象自身的运行时数据。
    比如哈希码GC分代年龄锁状态标志、线程持有的锁、偏向线程ID等。
  2. 类型指针。
    表示指向它类元数据的指针,通过这个确定这个对象是哪个类的实例。

垃圾回收

如何确定哪些对象需要回收?

有两种主流的算法来确定垃圾对象,分别为:

  1. 引用计算法
  2. 可达性分析算法

JVM 采用的是第二种。

引用计算法

这个算法是给对象添加一个引用计数器,每当有一个地方引用它时,计数器就加1;当引用失效时,计数器就减1;任何时刻计数器为0的对象就是不可能再被使用的。

好处:实现简单,效率高。
弊端:很难解决对象之间的循环引用问题。

可达性分析算法

这个算法的基本思路是通过一系列的成为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到 GC Roots 没有任何的引用链时,代表该对象不可用。

可以成为 GC Roots 的对象有以下这些:

  • 虚拟机栈(局部变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量应用的对象
  • 本地方法栈中Native方法引用的对象

引用

在JDK1.2之后,Java将引用分为强引用、软引用、弱引用、虚引用。

  • 强引用。
    类似new出来的对象,只要是强引用对象,垃圾收集器永远不会回收被强引用的对象。
  • 软引用。
    软引用用来描述一些还有用但并非必须的对象。JVM 内存不够时,才会去回收软引用对象。
  • 弱引用。
    弱引用用来描述非必需对象,不管内存够不够,只要发生垃圾收集,该引用对象就会被回收。
  • 虚引用。
    虚引用不会对对象的生命周期产生影响,给对象设置虚引用的唯一目的是为了能在该对象被垃圾回收的时候,收到一个系统的通知。

垃圾收集算法

有常见的以下三种:

  1. 标记清除算法
    此算法分标记和清除两个阶段。首先标记出需要回收的对象,标记完成后会统一进行回收。
    弊端:1)效率不高。2)会产生大量不连续的空间碎片
  2. 复制问题
    该算法把内存分为大小相等的两块,每次只使用其中的一块,回收的时候,会把使用那块的存活对象复制到另一块中,然后清空先前被使用的空间。
    优点:实现简单。
    弊端:浪费空间

HotSpot虚拟机的新生代可以细分为:eden、from Survivor、to Survivor,比例分别为8:1:1。所以如果新生代采用的是该算法的话,最多只会浪费10%空间。

  1. 标记整理算法。
    该算法把标记出来的存活对象移动到一端,然后清空端边界以外的内存。

实际上,现代虚拟机是根据新生代和老年代的不同特点来使用最合适的算法。像新生代,垃圾收集时,只会有少量的对象存活,所以用复制算法比较好,只需要复制少量的对象,成本比较低。老年代的话,因为对象存活率高,会使用标记清除或者标记整理算法

垃圾收集器

Serial收集器

Serial收集器是一个单线程收集器,垃圾收集的时候,会暂停掉其他工作线程(stop the world)。目前是虚拟机运行在client模式下的新生代收集器。优点是简单高效,没有线程切换的开销。缺点是会stop the world

ParNew收集器

ParNew收集器是Serial收集器的多线程版本。只有该收集器能与CMS收集器一起工作

ParNew
ParNew

Parallel Scavenge收集器

Parallel Scavenge是一个新生代收集器,它的关注点与其他收集器不同,CMS收集器的关注点是尽可能地减少垃圾收集时用户线程的停顿时间,Parallel Scavenge收集器则是为了达到一个可控制的吞吐量。

吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),停顿时间越短越适合与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量可以高效率地利用CPU时间,尽快完成程序的运算任务,适合后台运算不需要太多交互的任务。

Parallel
Parallel

Serial Old收集器

Serial Old是Serial的老年代版本,同样是单线程收集器。该收集器用的是标记整理算法,可以用作CMS的后备方案

Parallel Old收集器

Parallel Old 是Parallel Scavenge 收集器的老年代版本,使用的标记整理算法,和Parallel Scavenge一样,同样是关注吞吐量。实际生产上可以用Parallel Scavenge和Parallel Old配合使用。

CMS收集器

CMS收集器是一种以获取最短回收的停顿时间为目标的收集器。因为低停顿,所以用户体验更好,CMS收集器是基于标记清除算法。

收集过程可以分为以下4个步骤:

  1. 初始标记
  2. 并发标记
  3. 重新标记
  4. 并发清除

初始标记指标GC Roots对象,并发标记是标记当前需要回收的对象,重新标记是修正并发标记期间因为用户程序运产生变动的对象,然后清除掉。并发标记和并发清除可以与用户线程并发运行。

CMS
CMS

好处:低停顿,用户体验好,支持并发收集
弊端:

  1. 对CPU资源敏感。需要占用一定的线程来收集。
  2. 无法处理“浮动垃圾”。浮动垃圾指的是并发清除阶段,用户线程运行导致的垃圾,要留到下一次GC才会被清除。要是CMS运行期间,预留的内存无法满足用户程序的需要,就会导致“Concurrent Mode Failure”,这时虚拟机会启用后备方案:Serial Old收集器。预留的内存可以通过XX:CMSInitiatingOccupancyFraction参数来设置
  3. 用的标记清除算法,所以会有空间碎片的产生,如果碎片过多,即使老年代空间还足够,在分配大对象时候会出现分配不了的情况,此时会触发Full GC。这里可以通过设置-XX:CMSFullGCsBeforeCompaction参数,默认0,代表每次进入Full GC都进行碎片整理,不过会影响停顿时间。

G1收集器

G1收集器有如下几个特点:

  1. 支持用户线程并发执行
  2. 分代收集
  3. 空间整合:整体来看是“标记-整理”算法,从局部(两个Region)来看是基于“复制”算法,这意味者不会产生内存空间碎片,不会因为分配不了大对象导致的提前被迫GC。
  4. 可预测的停顿:这是G1相比CMS的另一优势。在M毫秒的时间片内,垃圾收集不得超过N毫秒。

G1收集器把堆分成了一个个 Region,每一个 Region 可能是新生代也可能是老年代,G1之所以能实现“可预测的停顿”,是因为它可以判断回收各个Region所带来的价值大小,找出性价比最高的 Region 来进行回收。

G1收集的步骤如下所示:

  1. 初始标记
  2. 并发标记
  3. 最终标记
  4. 筛选回收
1661f3b79ca85a8c
1661f3b79ca85a8c