程序员的视角:java GC

GC(Garbage Collection 垃圾回收)的概念随着 java 的流行而被人们所熟知。 实际 GC 最早起源于20世纪60年代的 LISP 语言,是一种自动的内存管理机制。 GC 要解决的问题有 3 个:

1. 回收什么?(what)

2. 何时回收?(when)

3. 如何回收?(how)

回收什么?

清理的是垃圾,回收的是内存空间。

既然 GC 是 java 的自动内存管理机制,那么先看下 java 虚拟机将所管理的内存划分为不同的区域,如图1。

如图1所示,java 虚拟机管理的内存区域分为如下几个部分:

1. 堆(Heap)

2. 方法区(Method Area)

3. 虚拟机栈(VM Stack)

4. 本地方法栈(Native Method Stack)

5. 程序计数器(Program Counter Register)

其中堆和方法区属于所有线程共享,而其他区域属于线程隔离的区域。

下面我们以 java HotSpot 虚拟机为例分别说说每个区域的作用和构成:

堆(Heap)

堆用于存储对象实例,从内存回收的角度看,由于收集器基本都采用了分代收集算法,所以堆可以进一步细分为:

- Eden 区

- Survivor 0 区 (From)

- Survivor 1 区 (To)

- Old/Tenured 区

其中 Eden、S0、S1 组成了新生代(Young/New Generation),Old/Tenured 为老年代。

方法区(Method Area)

方法区存储虚拟机加载的类信息、常量、编译代码等数据。 HotSpot 虚拟机使用永久代(Permanent Generation)来实现方法区。

虚拟机栈(VM Stack)

虚拟机栈描述的是 java 方法执行的内存模型,每个方法在执行时创建一个栈帧(Stack Frame)。 栈帧中存储内容主要包含:

- 局部变量表

- 操作数栈

- 动态链接

- 方法返回地址

每个方法的执行过程就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

本地方法栈(Native Method Stack)

本地方法栈与虚拟机栈类似,只不过服务于虚拟机执行 Native 方法时。 HotSpot 虚拟机的实现把虚拟机栈和本地方法栈合二为一。

程序计数器(Program Counter Register)

可以看作是线程执行的字节码的行号指示器,在虚拟机的概念模型中便于实现分支、循环、跳转、异常处理和线程切换恢复等基础功能。 每个线程都有一个独立的程序计数器。

GC 管理的内存区域主要是堆(Heap),而堆中存放的是对象实例,因此 GC 回收的就是“死亡”(不可能再被使用)的对象占用的内存空间。

何时回收?

既然说到“死亡”的对象,那不得不说下对象的生命周期。

虚拟机通过 new 指令创建了对象,大多数对象创建时在 Eden 区分配内存空间,而一些大对象若 Eden 区不能满足其空间需求时会直接在 Old/Tenured 区分配。

对象的死亡判定,主流的 GC 实现都是通过可达性分析,形象点来说就是在基于引用建立的对象图中形成了孤岛的对象就是死亡的(可回收的)。

GC 分类

- Minor GC

- Major GC

- Full GC

Minor GC 是针对新生代的回收,当 Eden 区空间满了时将触发 Minor GC。

Major GC 是针对老年代的回收,当 Minor GC 发生时会拷贝对象到老年代,这个过程称为对象晋升(promotion)或老年化(tenuring)。

为了避免对象晋升时老年代空间不足,收集器总是尝试预测剩余的空间是否足够以避免对象晋升失败,当晋升失败时就会发生 Full GC。

Full GC 是针对整个堆的操作,是非常昂贵的操作。除了在对象晋升失败时发生 Full GC,当堆自动调整大小时(Heap-Resizing)也会发生,不过可以通过设置 -Xms和-Xmx为相同的值来避免 Heap-Resizing。

如何回收?

Minor GC 将新生代中存活的对象拷贝到 Survivor 区和 Tenured 区。

Major GC 针对老年代区域进行死亡对象标记、清除和内存整理。

Full GC 则包括了所有存活对象的晋升以及老年代的内存回收及整理。

前面泛泛而谈了3种垃圾收集方式的过程,而具体则是由垃圾收集器来实现。

截至 JDK 1.7 HotSpot 虚拟机提供的垃圾收集器如图2所示,一共有 7 种不同作用的收集器。

图中连线表明它们可以搭配使用。

Serial Collector

如其名,串行的单线程收集器,是目前虚拟机运行在 client 模式下的默认新生代收集器。

ParNew Collector

相当于 Serial 的多线程版本。

Parallel Scavenge Collector

与 ParNew 很像,但它的关注点在达到一个可控制的吞吐量(Throughput),这里吞吐量的定义是 CPU 用于运行用户代码的时间与 CPU总消耗时间的比值。

因此 Parallel Scavenge 收集器也经常称为吞吐优先收集器,它还有个特点是自适应调节策略。 虚拟机会根据当前系统的运行情况收集监控信息,动态调整 Eden与Survivor区比例、晋升老年代对象年龄等参数,以提供最合适的停顿时间或最大的吞吐量。

Serial Old Collector

相当于 Serial 收集器的老年代版本。

Parallel Old Collector

相当于 Parallel Scavenge 收集器的老年代版本。

Concurrent Mark Sweep (CMS) Collector

前述的收集器在执行时都会停止所有的用户线程执行(Stop-The-World)

CMS 收集器的关注点则是尽可能地缩短垃圾收集时用户线程的停顿时间,让垃圾收集和用户线程并行执行,从而减少应用停顿时间,提升用户体验。

当然在获得低停顿的好处时是付出了吞吐量的代价,通常与 Parallel 系收集器相比吞吐率下降 10%-40%。

CMS 收集器的处理整个过程有如下步骤:

1. 初始标记:找到 GC Roots。

2. 并发标记:标记所有从 GC Roots 可达的对象。

3. 并发预清理:检查对象引用更新和在并发标记阶段晋升到老年代的对象并进行标记。

4. 重新标记:标记预清理阶段更新的对象引用。

5. 并发清理:回收死亡对象的内存。

6. 并发重置:重置数据结构为下次运行作准备。

其执行示意如图3所示

其中步骤1(初始标记)和步骤4( 重新标记)仍然需要 Stop The World,只是相对来说时间较短。

低停顿是 CMS 收集器是的优点,但它也并不完美,它有 3 个明显缺点:

1. 由于和用户线程并发执行,所以存在 CPU 争抢的问题。

2. 无法回收浮动垃圾。

3. CMS 仅进行了标记、清除而未进行整理,容易产生大量内存空间碎片。

CMS 默认启动的回收线程是 (CPU数量 + 3) / 4,也就是 CPU 在 4 个以上时并发回收线程使用的 CPU 资源不少于 25%。 在并发清理时新产生的垃圾称为浮动垃圾(Floating Garbage),本次无法收集,当浮动垃圾过多导致预留的内存无法满足程序需要时触发, 就可能出现 Concurrent Mode Failure 导致启用 Serial Old 收集器作为后备进行 Full GC。

Garbage First (G1) Collector

一种新的收集器,在 jdk7u4 开始正式支持,它有如下特点:

1. 多分区的堆组织方式

G1 也是分代收集器,但其组织堆的方式与其他收集器完全不同。它根据不同的用途将堆分为大量(~2000)固定大小的区域(region)。 相同用途的堆也并不连续,G1 依然保留了新生代和老年代的概念,但新生代和老年代不再是物理上隔离的了,它们都是一部分 region 的集合,如图4所示。

如果一个对象大小超过了普通区域大小的50%,那么它会被分配到一个大区域(humongous)里面。

2. 优先的收集方式

G1 的收集方式追求低停顿,并且建立可预测的停顿时间模型(在 M 毫秒的时间片段内,GC 的时间不得超过 N 毫秒,N < M)。 G1 通过有计划的避免在整个堆中进行全区域扫描进行垃圾收集,它通过跟踪各个 region 中垃圾的价值大小(回收获得的空间及回收所花费的时间的经验值), 在后台维护一个优先级列表,每次根据允许的收集时间,优先回收价值最大的 region,这也正式 Garbage-First 名称的由来。 而对 region 的收集采用的是 Stop-The-World 的方式,增量的将存活的对象复制到一个空 region 里面,这种方式不会产生内存碎片问题。

最后我们引用《Java Garbage Collection Distilled》 一文中的关于 GC 的折衷权衡点来总结下。

俗话说:“从来没有不劳而获,当我们得到某些事物的时候,通常不得不放弃另外一些事物”。

当谈论垃圾收集的时候,我们主要考虑三个收集器的指标:

1. 吞吐量:花费在 GC 上的时间占整个应用程序工作的比例。

2. 延迟:因为垃圾回收,而引起的响应暂停的时间。

3. 内存:我系统使用内存来存储状态,在管理的时候它们常常需要复制和移动。

上述三个指标,吞吐量越大越好,延迟越低越好,内存复制和移动产生的碎片越少越好。 但可惜这三个目标很难同时满足,很多时候我们都是根据应用类型在其中做出权衡取舍。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,137评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,824评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,465评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,131评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,140评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,895评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,535评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,435评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,952评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,081评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,210评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,896评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,552评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,089评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,198评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,531评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,209评论 2 357

推荐阅读更多精彩内容

  • 1.什么是垃圾回收? 垃圾回收(Garbage Collection)是Java虚拟机(JVM)垃圾回收器提供...
    简欲明心阅读 89,526评论 17 311
  • 原文阅读 前言 这段时间懈怠了,罪过! 最近看到有同事也开始用上了微信公众号写博客了,挺好的~给他们点赞,这博客我...
    码农戏码阅读 5,979评论 2 31
  • 这篇文章是我之前翻阅了不少的书籍以及从网络上收集的一些资料的整理,因此不免有一些不准确的地方,同时不同JDK版本的...
    高广超阅读 15,621评论 3 83
  • 1、java虚拟机发展史 1.1 Sun Classic jdk1.0-jdk1.4只能用解释器方式解...
    茨菇雪菜阅读 410评论 0 0
  • http://www.cnblogs.com/angeldevil/p/3801189.html值得一看 Clas...
    snail_knight阅读 1,425评论 1 0