关于GC

在介绍GC之前有必要先了解一下JVM的内存划分,这样在后面介绍GC和各种不同的GC collector的时候更容易理解。

下面这张图是“偷”的别人的,很经典的描述了jvm的体系结构,我们只需要关注最大的那一块——运行时数据区域。

运行时区顾名思义是jvm在运行时的内存结构,主要有以下5种。

1.方法区

方法区是各个线程共享的一块内存区域,当虚拟机装载一个class文件时,它会从二进制数据中解析类型的信息,这些信息便是存储在方法区,包括类的静态变量也会存储到该区域。虚拟机规范把该区域划分为堆的一部分,但是实际上它还有个别名Non-Heap,很明显是用来和堆做区分的。在讨论GC时我们习惯把这个区域叫做永久代,本质上它俩不是一个概念,对于HotSpot来说,永久代仅仅是实现方法区的一种方式。并且HotSpot后续的版本计划移除永久代,如果该区域内存不足时,会发生OOM。

2.堆

堆和方法区一样也是各个线程共享的一块内存区域,所有的对象实例包括数组都在这里分配内存。比如当我们new Object()的时候便是在这里分配的内存,java堆可以说是jvm管理的最大的一块内存区域,需要注意的一点是和方法区一样jvm规范并没有要求堆是连续的,jvm可以在运行时动态的扩展和收缩堆。为了更好的实现GC,现代的jvm针对堆又做了细化,将一整块堆分成不同的区域。下面这张图来自oracle官方网站,详细的画出了堆的详细情况。

图倒是蛮大的- -,还是横着的,凑合着看吧,整个堆分为三个区域,Young区、Tenured区(也就是Old区)、Perm区,习惯上我们称为年轻代,年老代,永久代(实际上GC就是按照这三个代进行分代收集的)。细心的朋友可能会注意到每个区域都有一块virtual,有必要说明一下virtual是做什么,我们知道堆可以在运行时扩充,比如在配置虚拟机参数的时候通常会指定-Xmx,-Xms最大堆和初始堆,这里的virtual就是预留的内存区域,其值为最大堆减去初始堆的值,实际上操作系统一开始就会划分-Xmx大小的内存给jvm,只不过jvm一开始可能不需要那么大的空间,因此jvm将一部分内存标记为virtual区,留着后面扩展用。三种内存区域中Young区稍微复杂些,这里又分为三个区域分别为一个Eden和两个Survivor区(一个to
survivor一个from survivor),名字很有意思,一个是伊甸园一个是幸存区,关于这几个区具体存放的是什么后面做进一步解释。

3.虚拟机栈

虚拟机栈为线程私有,因此处于这个区域的值不需要考虑并发的问题。jvm为每个java线程都分配一个栈,为该线程私有,栈内存随着线程的销毁而释放。jvm为每个方法都会生成一个栈帧用于保存局部变量表,操作数栈(这些概念在我之前的博客中也提到过)等信息。基本类型和对象的引用都可以在栈中存储。该区域有可能抛出StackOverflowerror和OOM异常。

4.本地方法栈

本地方法栈类似虚拟机栈,只不过虚拟机栈是为java方法服务,本地方法栈为本地的Native方法服务。

5.程序计数器

程序计数器是一块非常小的内存区域,它是当前线程执行的字节码的行号指示器,总是指向下一个要执行的指令,该区域是唯一不会发生OOM的内存区。

上面说过虚拟机栈,本地方法栈和程序计数器它们的内存分配在编译期基本就可以确定,并且内存随着线程的方法结束或者线程结束而销毁,因此这部分内存不需要考虑回收的问题。堆和方法区的内存分配相比来说就具有了不确定性,而且这部分的内存分配和回收都是动态的,因此jvm需要针对这两块内存做GC。

趁热打铁,刚刚讲完堆的分代,正好来看下为什么要分代以及各个代中存储的对象有何不同。

之所以要分代很明显的一个原因是方便做垃圾收集,因为垃圾收集只是针对那些没有被引用的孤对象进行的,而研究表明java中大多数的对象都是短命的,但也有一些对象存活的时间比较长。因此为了针对这些生命不一的对象做收集,将堆划分为不同的代来存放这些对象,也就是说Young区中的对象都是比较“年轻的”,同理可理解Old区。这就是为什么叫做Young和Old的原因。

实际上GC并不是java独有的,GC的历史要比java悠久。关于GC的基本原理比如引用计数法,可达性分析法等等就不作介绍了。GC主要有两种,一种是minor gc另一种是major gc也有称为young gc和old gc的。minor gc发生在Young区,并且时间通常非常短,major gc发生在Old区,时间较长,需要控制major gc的次数和GC时间。

jvm按照对象存活的时间,给对象一个类似我们人类“年龄的概念”,实际上每经过一次GC,存活下来的对象年龄便+1,大多数情况下对象优先分配到Eden区,这就是为什么这里叫Eden区的原因,当Eden区的没有足够内存分配的时候,便会触发一次Minor GC。此时存活下来的对象年龄+1,当达到一定年龄的时候,表明该对象存活比较稳定,会把该部分对象移到年老代(我们可以通过参数-XX:MaxTenuringThreshold 控制经过多少次Minor gc后便进入old区,默认为15),如果在Minor GC的时候发现to
survivor存放不下这些对象,则会直接存放到年老代。需要注意的一点是当要分配大对象(比如数组和长字符串)的时候,也是直接将他们分配到年老代的,因此我们尽量避免大对象尤其是短命大对象的使用,因为这很容易引起old区的内存不够分配,从而提前触发full gc。当年老代没有足够内存的时候便会触发Major GC,通常minor gc的时间非常短对于程序影响可以忽略,但是major gc的过程则要比minor gc长不少,因此要尽量避免major gc的发生(当发生gc的时候会暂停所有的应用线程执行,官方称为stop
the world,这里的时间指的就是stop the world的时间)。

来看一下目前几种主要的GC算法,之所以这些算法并存是因为针对不同的区域通常需要使用特定的算法。

1.标记-清除

很明显该算法分为两步,第一步是标记出需要回收的对象,第二步针对这些对象做清理动作。该算法的缺点主要有两个:一是效率问题,标记和清除的效率都不高,二是清除之后会产生大量不连续的内存碎片,这会导致运行过程中如果需要分配较大的对象,会由于找不到足够的连续内存而提前触发一次垃圾收集。

2.复制算法

复制算法将内存分为大小相等的两块,每次使用一块,当一块使用完时,将还存活的对象全部复制到另一块区域中,然后清理第一块的内存,该算法的好处不言而喻,不会产生内存碎片,但是只使用一块内存未免代价太大,并且在存活对象非常多的时候,效率肯定会低下。实际上在java中绝大多数的对象都是“短命的”,因此不需要按照1:1来划分内存,HotSpot默认将Eden区和两个survivor区按照8:2的比例进行划分,也就是Eden:survivor=8:1(当然我们可以通过-XX:SurvivorRatio设置Survivor的大小,该值如果为8,则表示Eden区占Young的十分之八,两个Survivor占Young区的十分之二),该算法非常适合在minor
gc时使用。

3.标记-整理

复制算法针对有大量存活的对象时效率低下,因此不适合对年老代的回收,但是标记清除又有碎片的问题,因此产生了标记整理算法。标记整理算法和标记清除算法类似,第一步都是标记,而第二步整理阶段是将存活的对象移向一端,然后直接清理边界以外的内存,这样不会产生碎片效率也不算太差。

针对上面几种算法,HotSpot主要提供了以下几种实现:

图中黄色背景是年轻代的收集器,浅灰色背景是年老代的收集器,蓝色背景表示垃圾收集器,两两直线相连表示两种收集器可以共用。下面分别介绍一下这六种收集器:

1."Serial"会引起stop the world,基于拷贝的单线程收集器。

2."ParNew"即是serial的多线程版本,不同于 "Parallel Scavenge" ,ParNew可以和CMS配合一起使用威力更大。

3. "Parallel Scavenge"会引起stop the world,基于拷贝的多线程收集器。

4."Serial Old"会引起stop the world,基于标记-清除-整理的单线程收集器。

5."CMS"是一种并发短暂停的收集器,其中的某些步会引起stw,后面详细讲解。

6."Parallel Old"一种并发的基于标记-整理的收集器,Parallel Scavenge的年老代版本。

以上六种最复杂的是CMS收集器,后面会详细讲解。

ParNew是多线程并行收集器,CMS是并发收集。这里不得不提一句,并行指的是多个垃圾收集线程一起进行回收工作,此时应用线程是停止,但是并发指的是收集线程和应用线程同时执行,也就是垃圾收集工作的时候并不影响应用(这里的不影响只是相对的,实际CMS的工作过程分为好多步,有些步骤也会发生stop the world)。

既然ParNew和Parallel Scavenge都是针对新生代的并行收集,那么他们两个有什么不同呢?

像CMS和ParNew等收集器主要关注的是减少因收集而引起的应用停顿时间,而Parallel Scavenge主要关注的是应用的吞吐量,所谓的吞吐量就是CPU用于运行应用程序时间和CPU总消耗时间的比值,比如虚拟机总共运行100分钟,而垃圾收集用掉了1分钟,吞吐量即为99/100=99%。而对于Parallel Scavenge有个特殊的参数 -XX:+UseAdaptiveSizePolicy应用该参数JVM可以在运行时自动调整堆内存各个区的大小,不需要人为的配置。

针对虚拟机参数使用-XX配置不同的收集器,主要有以下几种:

UseSerialGC 是"Serial" + "Serial Old"

UseParNewGC 是 "ParNew" + "Serial Old"

UseConcMarkSweepGC 是"ParNew" + "CMS" + "Serial Old"。 年老代的回收绝大多数时间使用"CMS"。但是当发生concurrent mode failure错误的时候会切换到"Serial Old" 。

UseParallelGC是"Parallel Scavenge" + "Serial Old"

UseParallelOldGC是"Parallel Scavenge" + "Parallel Old"

上面这张图是CMS收集器的几个工作阶段分别是:初始标记,并发标记,重新标记,并发清除。其中的1,3两个步骤需要暂停所有的应用程序线程的。第一次暂停从root对象开始标记存活的对象,这个阶段称为初始标记;第二次暂停是在并发标记之后, 暂停所有应用程序线程,重新标记并发标记阶段遗漏的对象(在并发标记阶段结束后对象状态的更新导致)。第一次暂停会比较短,第二次暂停通常会比较长,并且 remark这个阶段可以并行标记。一个CMS会发生两次STW。因此在使用CMS的垃圾收集器的时候,通常我们使用jstat查看的fullgc(有一种说法是fullgc的次数就是STW的次数)次数和cms发生的次数为2:1的关系。关于CMS的参数有不少需要关注的点:

其他的一些关于GC的参数还有下面一些:

-XX:+PrintGC 输出GC日志

-XX:+PrintGCDetails 输出GC的详细日志

-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)

-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)

-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息

-Xloggc:../logs/gc.log 日志文件的输出路径

上面个的参数主要涉及GC日志的打印,jvm还有很多其他的参数不一一描述了,网上有很多详细的讲解。

讲了那么多GC,下面来分析一段GC日志

519.514: [GC 519.514: [ParNew: 5149852K->83183K(5662336K), 0.0831770 secs] 6955196K->1905793K(9856640K), 0.0833560 secs] [Times: user=0.57 sys=0.03, real=0.08 secs ]

前面的519.514表示了自虚拟机启动到该GC发生的秒数,[GC表示本次是普通的GC当然还有[Full GC,[ParNew表示使用的是ParNew收集器对年轻代做收集, 5149852K->83183K(5662336K)分别表示GC前该区域已使用的内存大小,GC后该区域使用的内存大小,该区域的总大小。 0.0831770 secs表示GC所占用的时间单位为秒,后面更详细的时间user=0.57 sys=0.03, real=0.08 secs与Linux的time命令所输出的时间含义一致。

时间: 2024-08-25 06:17:58

关于GC的相关文章

Java程序性能分析工具Java VisualVM(Visual GC)—程序员必备利器

VisualVM 是一款免费的\集成了多个JDK 命令行工具的可视化工具,它能为您提供强大的分析能力,对 Java 应用程序做性能分析和调优.这些功能包括生成和分析海量数据.跟踪内存泄漏.监控垃圾回收器.执行内存和 CPU 分析,同时它还支持在 MBeans 上进行浏览和操作. 在内存分析上,Java VisualVM的最大好处是可通过安装Visual GC插件来分析GC(Gabage Collection)趋势.内存消耗详细状况. 一  Visual GC(监控垃圾回收器) Java Visu

Java性能优化之JVM GC(垃圾回收机制)

Java的性能优化,整理出一篇文章,供以后温故知新. JVM GC(垃圾回收机制) 在学习Java GC 之前,我们需要记住一个单词:stop-the-world .它会在任何一种GC算法中发生.stop-the-world 意味着JVM因为需要执行GC而停止了应用程序的执行.当stop-the-world 发生时,除GC所需的线程外,所有的线程都进入等待状态,直到GC任务完成.GC优化很多时候就是减少stop-the-world 的发生. JVM GC回收哪个区域内的垃圾? 需要注意的是,JV

JVM 什么时候会full gc

除直接调用System.gc外,触发Full GC执行的情况有如下四种.1. 旧生代空间不足旧生代空间只有在新生代对象转入及创建为大对象.大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误:java.lang.OutOfMemoryError: Java heap space 为避免以上两种状况引起的FullGC,调优时应尽量做到让对象在Minor GC阶段被回收.让对象在新生代多存活一段时间及不要创建过大的对象及数组.2. Permanet Generation空间

【转载】GC基本算法及C++GC机制

原文: GC基本算法及C++GC机制 阅读目录 前言 基本概念 有向可达图与根集 三种基本的垃圾收集算法及其改进算法 1.引用计数算法 2. Mark & Sweep 算法 3. 节点复制算法 分代回收 C++垃圾回收机制 参考书籍 正文 回到顶部 前言 垃圾收集器是一种动态存储分配器,它自动释放程序不再需要的已分配的块,这些块也称为垃圾.在程序员看来,垃圾就是不再被引用的对象.自动回收垃圾的过程则称为垃圾收集(garbage collection).在一个支持垃圾收集的语言中,程序显式地申请内

C++随笔:.NET CoreCLR之GC探索(2)

首先谢谢 @dudu 和 @张善友 这2位大神能订阅我,本来在写这个系列以前,我一直对写一些核心而且底层的知识持怀疑态度,我为什么持怀疑态度呢?因为一般写高层语言的人99%都不会碰底层,其实说句实话,我以前也不看这些东西,只是因为自己觉得对C++感兴趣,索性乱写点东西,如果有写得不好的地方,还请上面2位大神指出. 其实我现在虽然写的是C++,但是我打算在后面把C++和.NET的一些基础类库融合起来,我发现写CLR的文章特别少,不知道什么原因.反正,废话不多,开始今天的写作吧,今天依然是把重点集中

Java 内存区域和GC机制

目录 Java垃圾回收概况 Java内存区域 Java对象的访问方式 Java内存分配机制 Java GC机制 垃圾收集器 Java垃圾回收概况 Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代 码,对内存泄露和溢出的问题,也不需要像C程序员那样战战兢兢.这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制.概括地说,该机制对 JVM(Java Virtual M

jvm系列:Java GC 分析

Java GC就是JVM记录仪,书画了JVM各个分区的表演. 什么是 Java GC Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代码,对内存泄露和溢出的问题,也不需要像C程序员那样战战兢兢.这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制.概括地说,该机制对JVM(Java Virtual Machine)中的内存进行标记,并确定哪些内存需要回收,根据一定

JVM运行报错:GC overhead limit exceeded

今天在折腾OOM和java的4种引用类型的时候,在运行过程中JVM报了一个错误: java.lang.OutOfMemoryError: GC overhead limit exceeded 这个错误平时遇到的概率很少很少,今天无意中遇到了,这里做个记录.oracle/sun官网的解释是: The concurrent collector will throw an OutOfMemoryError if too much time is being spent in garbage colle

java——GC回收机制

1. GC是什么? GC(Gabage Collection):java的垃圾回收机制的根本目的就是跟踪正在使用的对象,然后回收那些不再使用(引用)的对象.java的GC回收机制有两个好处: (1) 避免垃圾过多导致的内存耗尽 (2) 避免不恰当的内存释放导致的内存非法引用 GC主要是对JVM中的堆对象进行识别,如果对象正在被引用,就是活对象.否则就是死的,是可以被回收再分配的. 2. java的内存是如何存储的哪? 堆:用来存储java中创建的对象,new新建的对象都存在java的堆中 栈:用

HBase的几种调优(GC策略,flush,compact,split)

HBase的几种调优(GC策略,flush,compact,split) 一:GC的调优 1.jvm的内存 新生代:存活时间较短,一般存储刚生成的一些对象 老年代:存活时间较长,主要存储在应用程序中生命周期较长的对象 永久代:一般存储meta和class的信息 2.GC策略 Parrallel New Collector,垃圾回收策略 并行标记回收器(Concurrent Mark-Sweep Collector) 3.Parrallel New Collector 速度快,但是数据量一大,容易