理解JAVA垃圾回收的好处是什么?满足求知欲是一方面,编写更好的JAVA应用是另外一方面。

如果一个人对垃圾回收过程感兴趣,那表明他在应用程序开发领域有相当程度的经验。如果一个人在思考如何选择正确的垃圾回收算法,那意味着他对应用程序的特性完全了解。当然,不能一概而论。不过,相信很少人会不认为理解垃圾回收是做一个好的JAVA开发的前提。

本文的目的是对垃圾回收进行简明介绍。

在学习垃圾回收之前,有一个概念需要知道。这个概念是:停止世界(stop-the-world)。任何一个垃圾回收算法都会引发停止世界。停止世界指的是JVM在运行垃圾回收的时候会暂停程序的运行。当停止世界发生的时候,除了垃圾回收意外的进程都会暂停。当垃圾回收完成的时候,这些暂停的任务会恢复运行。

垃圾回收的分代

在JAVA中并不用明确指明某段内存,并在程序代码中将其清空。有的人通过将对象设置为空,或者用System.gc()方法来清空内存。将对象设置为空没有多大影响。然而调用System.gc()方法将会明显地影响系统的性能。因此不应调用这个方法。(幸运的是,还没有NHN的开发者被发现调用这个方法。)

在JAVA中,开发者并不用在程序代码中指定清空某段内存。垃圾回收器会找到不再需要的对象,也就是垃圾,并清除他们。垃圾回收器有以下两个假设:(也许说是预设或者前提会更好。)

  • 大部分的对象都很快就会不可访问。
  • 老对象对新对象的引用是不常见的。

这些假设被称为弱分代假设。那么为了保障这个假设的优势,在HotSpot VM中一般都在物理上将对象分为两代:新生代,和老年代

新生代:大部分新创建的对象都位于这个地方。由于大部分对象都很快不可访问,在新生代中创建的很多对象都将消失。当对象从这个区域消失的时候,我们就说发生了一次次要垃圾回收

老年代:在新生代中没有变得不可访问并继续生存的对象将会被复制到这个区域。这个区域一般比新生代要更大。由于它更大,这里的垃圾回收相对于新生代就要不那么频繁。当对象从老年代中消失的时候,我们就说发生了一次主要垃圾回收(或者说完全垃圾回收)。

让我们在图上来看这个过程:

图1:垃圾回收区域和数据流

上图中的永生代被称为方法区域。它存储类或被扣留的字符串。这个区域并不存储老年代中存活的对象。这个区域有可能发生垃圾回收。这个区域发生的垃圾回收也是主要垃圾回收。

有的人也许会问:

如果老年代中的对象需要引用新生代中的对象,会发生什么情况?

为了处理这种情况,老年代中有一个大小为512字节的块,作为卡片表(card table)。当老年代中的对象需要引用新生代中的对象时,在卡片表中加入相应记录。当需要在新生代中进行垃圾回收时,就会扫描这个表判定是否可以回收对象。这样就避免扫描整个老年代中的对象。卡片表由写栅栏(write barrier)管理。写栅栏可以加快次要垃圾回收的速度。尽管它本身需要消耗一些资源,不过整体的垃圾回收时间减少了。

图2:卡片表结构

新生代的组件

理解新生代是理解垃圾回收的基础。新生代中的对象都是第一次创建。新生代分为3个区域。

  • 1个伊甸空间
  • 2个生存者空间

总共有3个空间,其中2个是生存者空间。每个空间的执行过程是:

  1. 大部分新创建的对象都在伊甸空间。
  2. 在伊甸空间的一次垃圾回收之后,存活的对象将被移动到两个生存者空间的其中一个。
  3. 在伊甸空间的再一次垃圾回收之后,存活的对象将被继续在生存者空间中堆积。
  4. 当其中一个生存者空间满载之后,存活的对象将被移动到另外一个生存者空间。满载的生存者空间将被标记为没有数据的状态。
  5. 经过上面几步若干次循环之后的存活的对象将会被移动到老年代中。

在上述过程中,总有一个生存者空间必须为空。如果两个生存者空间都有数据,或两个生存者空间都没有数据。那么就意味着系统出问题了

下图说明了在次要垃圾收集中,数据被堆积到老年代的过程:

图3:垃圾回收之前和垃圾回收之后

注意在HotSpot VM中,有两个快速内存分配的技术。一个叫做bump-the-pointer,另一个叫做TLABs(线程本地分配缓存)

Bump-the-pointer技术追踪分配到伊甸空间的最后一个对象。这个对象将被分配到伊甸空间的顶部。如果在这之后又有一个对象被创建,它只要检查对象的大小是否适合于伊甸空间。如果适合,对象将被放到伊甸空间中,并分配到顶部。因此,当新的对象被创建的时候,只有最后加入的对象需要被检查。这就使内存分配更加高效。不过,在多线程环境中就是另外一回事了。如果要用线程安全的方式在伊甸空间中保存多线程使用的对象,就会因为要用锁导致性能大幅下降。TLABs是解决HotSpot VM中这个问题的方案。这允许每个线程都单独拥有伊甸空间的一小部分。每个线程都只能访问他们自己的TLAB,这样Bump-the-pointer技术既可以不用使用锁了。

这是对新生代中垃圾回收的快速概览。你没有必要去记住这两个技术。不知道他们也不用去坐牢。但是请记住,对象第一次在伊甸空间中创建以后,长期存活的对象将会经由生存着空间移动到老年代。

老年代垃圾回收

老年代一般会在数据满载的时候执行一次垃圾回收。执行过程会随着垃圾回收的类型而变化。那么了解垃圾回收的各种类型将对理解执行过程有所帮助。

根据JDK7,有5种垃圾回收类型:

  1. 序列垃圾回收
  2. 并行垃圾回收
  3. 并行老垃圾回收(并行压缩垃圾回收)
  4. 同步标记清空垃圾回收(CMS)
  5. 垃圾优先(G1)垃圾回收

在这之中,序列垃圾回收不应当在服务器上使用。这种垃圾回收类型仅适用于单核工作站。使用这种序列垃圾回收将会急剧降低应用的性能。

现在来介绍各种垃圾回收类型:

序列垃圾回收(-XX:+UseSerialGC)

在上面一节已经介绍了新生代中的垃圾回收。老年代垃圾回收中使用的算法叫做“标记-清空-压缩”。

  1. 算法第一步是标记老年代中的存活对象。
  2. 然后,遍历堆,只保留存活对象,清空其它。
  3. 最后一步,它从头开始填充堆,保证对象连续片列。并将堆分成两个部分:一部分有对象,一部分没有。

序列垃圾回收适合于内存小,CPU核少的机器。

并行垃圾回收 (-XX:+UseParallelGC)

图4:序列垃圾回收和并行垃圾回收的差异

通过图4,可以很容易看出序列垃圾回收和并行垃圾回收的差异。当序列垃圾回收只使用一个线程处理一次垃圾回收。并行垃圾回收使用多个线程处理一次垃圾回收。并行垃圾回收适用于大内存多核CPU的机器。因此它也被叫做吞吐垃圾回收。

并行老垃圾回收(-XX:+UseParallelOldGC)

JDK5开始支持并行老垃圾回收。和并行垃圾回收相比,唯一的不同是,这是老年代垃圾回收的算法。它有三步:标记-概括-压缩。概括阶段仅标记垃圾回收曾经处理过的区域中的存活对象。这是和并行垃圾回收的差异。它将由更为复杂的步骤。

同步标记清空(CMS)垃圾回收 (-XX:+UseConcMarkSweepGC)

图5:序列垃圾回收和CMS垃圾回收

如图5所示,CMS垃圾回收比前述各种垃圾回收类型都要复杂。它的初始标记阶段很简单。最靠近classloader的对象中的存活对象将会被搜索。这样,暂停时间就会非常短站。在同步标记阶段,存活对象引用的对象将会被追踪和检查。这一步的不同点在于它和其它线程同时执行。在重标记阶段,在同步标记阶段新增加的对象或不再被引用的对象将会被检查。最后,在同步清空阶段,开始垃圾回收过程。垃圾回收和其它线程同步执行。由于这个垃圾回收类型用这种方式执行,它的暂停时间就非常短。CMG垃圾回收也被叫做低延迟回收压机回收。它适合反应时间要求非常高的环境

这种垃圾回收类型在低停止世界时间上非常有优势。它也同样有以下弱势:

  • 它比其它的垃圾回收类型消耗更多的内存和CPU时间。
  • 压缩阶段并不被默认提供。

使用这个类型需要经过非常小心的评估。如果因为过多的内存碎片需要执行压缩任务,停止世界时间将会比其它垃圾回收类型更长。需要检查压缩任务的执行时间和执行频率。

垃圾优先(G1)垃圾回收

Finally, let's learn about the garbage first (G1) GC.

最后,介绍垃圾优先(G1)垃圾回收。

图6:G1垃圾回收的布局

如果需要理解G1垃圾回收,首先要忘了新生代和老年代。如图6所示,一个对象分配一个网格,然后执行垃圾回收。然后,在一个区域满载以后,对象将被分配到另外一个区域,然后执行垃圾回收。数据从新生代的三个空间移动到老年代的过程不存在于G1垃圾回收类型。这个类型曾用于替代CMS垃圾回收。在很长一段时间内,这导致了很多问题和抱怨。

G1垃圾回收的最大的优势是性能。它比前述各个垃圾回收类型都要快。但是在JDK6中,这不是正式特性。JDK7种正式提供这个特性。JDK6中因为应用了G1而导致JVM崩溃的案例也有所听闻。请等到它更加稳定。

在这个文章中,仅仅是对JAVA垃圾回收的概览。请期待下篇文章,介绍如何检测JAVA垃圾回收状态,垃圾回收调优。

[牛感悟系列]JAVA(1)理解JAVA垃圾回收的更多相关文章

  1. Java GC系列(4):垃圾回收监视和分析

    本文由 ImportNew - lomoxy 翻译自 javapapers. 目录 垃圾回收介绍 垃圾回收是如何工作的? 垃圾回收的类别 垃圾回收监视和分析 在这个Java GC系列教程中,让我们学习 ...

  2. 《深入理解 Java 虚拟机》学习 -- 垃圾回收算法

    <深入理解 Java 虚拟机>学习 -- 垃圾回收算法 1. 说明 程序计数器,虚拟机栈,本地方法栈三个区域随线程而生,随线程而灭,这几个区域的内存分配和回收都具备确定性 Java 堆和方 ...

  3. Java内存管理 -JVM 垃圾回收

    版权声明:本文为博主原创文章,未经博主允许不得转载 一.概述 相比起C和C++的自己回收内存,JAVA要方便得多,因为JVM会为我们自动分配内存以及回收内存. 在之前的JVM 之内存管理 中,我们介绍 ...

  4. Java基础教程:垃圾回收

    Java基础教程:垃圾回收 垃圾回收 垃圾回收(Garbage Collection,GC),顾名思义是释放垃圾占用的空间,防止内存泄漏.有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使 ...

  5. Java其实不支持垃圾回收

    Java其实不支持垃圾回收.如果真的支持的话,大多数Java程序在运行的一开始就应该把程序本身删除,因为这些程序本身就是垃圾.   // TODO: This is a 分割线. Please no ...

  6. JVM基础系列第10讲:垃圾回收的几种类型

    我们经常会听到许多垃圾回收的术语,例如:Minor GC.Major GC.Young GC.Old GC.Full GC.Stop-The-World 等.但这些 GC 术语到底指的是什么,它们之间 ...

  7. Java GC专家系列1:理解Java垃圾回收

    了解Java的垃圾回收(GC)原理能给我们带来什么好处?对于软件工程师来说,满足技术好奇心可算是一个,但重要的是理解GC能帮忙我们更好的编写Java应用程序. 上面是我个人的主观的看法,但我相信熟练掌 ...

  8. 深入理解Java虚拟机之JVM垃圾回收随笔

    1.对象已经死亡? 1.1引用计数法:给对象中添加一个引用计数器,每当有一个地方引用他时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器都为0的对象就是不可能再被使用 的.但是它很难解决 ...

  9. 深入理解java虚拟机之——JVM垃圾回收策略总结

    如何判断一个对象是否存活 引用计数算法:给对象中添加一个引用计数器,每当有引用它时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器为0的对象就是不可能再被使用.  Java虚拟机里面没有 ...

随机推荐

  1. Mac OS X操作系统常见快捷键集锦

    Mac OS X操作系统常见快捷键集锦 启动时的快捷键 启动时按住 X 键 : 强制从 Mac OS X 启动(适用于那些在同一宗卷上安装了 Mac OS X 和 Mac OS 9 双系统的 Mac ...

  2. 搭建属于自己的wiki

    1.开源wiki mediawiki http://www.mediawiki.org/wiki/MediaWiki 2. 开发文档 http://www.mediawiki.org/wiki/Man ...

  3. oracle数据库元数据SQL查询

    oracle数据库经典SQL查询 .查看表空间的名称及大小 select t.tablespace_name, round(sum(bytes/(1024*1024)),0) ts_size from ...

  4. BeanDefinitionRegistry extends AliasRegistry

    // 用该Registry注册一个新定义的bean,但是新的bean必须支持父的定义和子的定义void registerBeanDefinition(String beanName, BeanDefi ...

  5. 乐在其中设计模式(C#) - 代理模式(Proxy Pattern)【转】

    介绍 为其他对象提供一个代理以控制对这个对象的访问. 示例 有一个Message实体类,某对象对它的操作有Insert()和Get()方法,用一个代理来控制对这个对象的访问. MessageModel ...

  6. 【Nginx 1】Nginx 的下载和安装

    今天正式开始学习Nginx.Nginx是一个著名的轻量级Http服务器,目前已经有很多知名网站使用Nginx作为服务器.因为Nginx是开源的软件,因此对于开发人员和学习者来说都是一个大宝藏. 首先, ...

  7. SQL Server 在线进程分析处理

    SQL Server 在线进程分析处理 前言 数据库在线进程处理在很多时候需要人为干预已达到预期管理目标,下面整理一下常用的在线进程管理方法,便于后续工作使用. 一.查看目标数据库在线进程,并杀死指定 ...

  8. [转]W3C 验证 there is no attribute target for this element

    http://validator.w3.org/ 本文转自:http://hi.baidu.com/linkbestlove/item/d7fff865aefa5f0ba1cf0f04 我们要在新窗口 ...

  9. Linux基本命令之逻辑测试二

    1.首先介绍一个与test一样的测试方式[ expression ](千万注意expression的前后都有空格,没有空格的话会报错) 这个测试方式经常作为if的条件. /home/www这个文件名存 ...

  10. 使用迭代器遍历List的时候修改List报ConcurrentModificationException异常的解决办法

    为了避免这种异常,我们可以使用CopyOnWriteArrayList来代替ArrayList,CopyOnWriteArrayList支持并发访问,所以同时进行迭代和修改是没有问题的.