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. 内存:我系统使用内存来存储状态,在管理的时候它们常常需要复制和移动。

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

程序员的视角:java GC的更多相关文章

  1. 优秀Java程序员必须了解的GC工作原理(转)

    一个优秀的Java程序员必须了解GC(Garbage Collection 垃圾收集)的工作原理.如何优化GC的性能.如何与GC进行有限的交互,因为有一些应用程序对性能要求较高,例如嵌入式系统.实时系 ...

  2. 优秀Java程序员必须了解的GC工作原理

    一个优秀的Java程序员必须了解GC的工作原理.如何优化GC的性能.如何与GC进行有限的交互,因为有一些应用程序对性能要求较高,例如嵌入式系统.实时系统等,只有全面提升内存的管理效率 ,才能提高整个应 ...

  3. C++程序员如何转Java

     C++程序员如何转Java 忙里偷闲,到了这个时间终于得空写一篇早想写的文章.其实本文的标题有些不太准确,C++程序员写Java代码不是说就非得转行写Java,抛弃C++,而只是多了一个选择而已.两 ...

  4. 一位资深程序员大牛给予Java初学者的学习路线建议

    java学习这一部分其实也算是今天的重点,这一部分用来回答很多群里的朋友所问过的问题,那就是我你是如何学习Java的,能不能给点建议?今天我是打算来点干货,因此咱们就不说一些学习方法和技巧了,直接来谈 ...

  5. Spring MVC 程序首页的设置 - 一号门-程序员的工作,程序员的生活(java,python,delphi实战)

    body { font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI ...

  6. 转载:一位资深程序员大牛给予Java初学者的学习路线建议

    一位资深程序员大牛给予Java初学者的学习路线建议   java学习这一部分其实也算是今天的重点,这一部分用来回答很多群里的朋友所问过的问题,那就是我你是如何学习Java的,能不能给点建议?今天我是打 ...

  7. 震惊!90%的程序员不知道的Java知识!

    震惊!90%的程序员不知道的Java知识! 初学Java的时候都会接触的代码 public static void main(String[] args){ ... } 当时就像背公式一样把这行代码给 ...

  8. 【转载】国外程序员整理的Java资源大全

    以下转载自: 推荐!国外程序员整理的Java资源大全中文版    https://github.com/akullpp/awesome-java英文版 Java 几乎是许多程序员们的入门语言,并且也是 ...

  9. 今天看到的一篇文章:一位资深程序员大牛给予Java初学者的学习路线建议

    一位资深程序员大牛给予Java初学者的学习路线建议 持续学习!

  10. 什么是函数,干嘛啊,怎么干。一个py程序员的视角.md

    目录 前言 本质 math definition py definition class 是类,是对象的蓝本 回到函数 一个结论 self 是什么? 以上就是py世界里函数的定义 什么是函数,干嘛啊, ...

随机推荐

  1. Nginx之(四)工作原理

    众所周知,nginx性能高,而nginx的高性能与其架构是分不开的 4.1 进程模型 Nginx在启动后,会有一个master进程和多个worker进程.master进程主要用来管理worker进程, ...

  2. OpenCV 2.x/3.x 随机初始化矩阵

    简介 在测试算法的时候,或者某些算法需要使用随机数,本文介绍如何使用OpenCV的随机数相关功能. 主要内容: 1. cv::RNG类 -- random number generator 2. cv ...

  3. mybatis映射器配置细则

    前面三篇博客我们已经多次涉及到映射器的使用了,增删查基本上都用过一遍了,但是之前我们只是介绍了基本用法,实际上mybatis中映射器可以配置的地方还是非常多,今天我们就先来看看映射器还有哪些需要配置的 ...

  4. 理解 Linux 的硬链接与软链接

    Linux 的文件与目录 现代操作系统为解决信息能独立于进程之外被长期存储引入了文件,文件作为进程创建信息的逻辑单元可被多个进程并发使用.在 UNIX 系统中,操作系统为磁盘上的文本与图像.鼠标与键盘 ...

  5. Android View框架总结(二)View焦点

    请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52263256 前言:View框架写到第六篇,发现前面第二篇竟然没有, ...

  6. 【java多线程系列】java内存模型与指令重排序

    在多线程编程中,需要处理两个最核心的问题,线程之间如何通信及线程之间如何同步,线程之间通信指的是线程之间通过何种机制交换信息,同步指的是如何控制不同线程之间操作发生的相对顺序.很多读者可能会说这还不简 ...

  7. (一一九)通过CALayer实现阴影、圆角、边框和3D变换

    在每个View上都有一个CALayer作为父图层,View的内容作为子层显示,通过layer的contents属性决定了要显示的内容,通过修改过layer的一些属性可以实现一些华丽的效果. [阴影和圆 ...

  8. 【Unity Shaders】法线纹理(Normal Mapping)的实现细节

    写在前面 写这篇的目的是为了总结我长期以来的混乱.虽然题目是"法线纹理的实现细节",但其实我想讲的是如何在shader中编程正确使用法线进行光照计算.这里面最让人头大的就是各种矩阵 ...

  9. iOS中让Settings Bundle中的变化立即在App中反应出来的两种方法

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 为了能够在Settings Bundle中的变化在进入App后 ...

  10. Ubuntu15.10 安装OpenCV3.1

    wget https://sourceforge.net/projects/opencvlibrary/files/opencv-unix/3.1.0/opencv-3.1.0.zip/downloa ...