[TOC]java
本篇文章是本人阅读《深刻理解JVM》和《java虚拟机规范》时的笔记。
记录的都是一些概念性的东西。
JVM是HotSpot,jdk1.7。
大神绕路,不喜勿喷。程序员
先来蜻蜓点水般地浏览一些著名的GC算法。
这里也仅仅是说一下大体过程,具体细节的介绍对于我一个Java程序员来讲表示无能为力,由于底层实现要牵扯到具体的实现语言了,并且不一样的JVM实现商确定有不一样的实现细节。算法
这种算法的大概过程是:多线程
标记出全部须要回收的对象并发
统一回收全部被标记的对象布局
这种算法很直观,但他的缺点以下:性能
标记和清除的两个阶段,效率并非很好,由于回收的粒度太细了spa
清除后的内存区域通常都是千疮百孔,可用内存区域通常都不连续线程
上面说的标记/清除算法不太好的主要缘由就是其回收粒度
太过细微了。
签于此,复制算法的主要作法是:日志
将内存分为大小相等的两块,暂且称之为内存块
每次当某一内存块(A)占满以后,将该内存块(A)的有用数据复制到另一块内存块(B)
将A内存块的整个块直接清除
这种算法相比于标记/清除算法的最大特色是:
每次回收都是之内存块为单位(粒度较大)
只有两个内存块,收集完后内存不连续的状况也就不用考虑了
可是,将整个内存分为两块,实际的可用内存也就减半了(这就有点没法接受了)
存在大量的对象复制操做
上面说的复制算法的最大缺点就是对象的复制操做。尤为是在有效的对象不少的状况下。
这里的标记/整理算法的大体过程是:
标记应该回收的区域
将有用的对象/数据集中移动到一块区域,暂且称之为"有效区",有效区的位置每每是在两端的某一端
将"有效区"以外的区域集体清理
既然上面说集中算法都各有优劣,那么根据他们各自的优势,在不一样的状况下使用最优的算法会不会更好呢?
分代收集的大体思路就是这样的:
将JVM堆内存分为新生代和年老代
年老代中的对象存活率通常都很高,采用'标记/清理'或'标记/复制'算法
新生代中通常对象的'死亡率'都很高,采用复制算法
上面说了一大堆GC的理论。可是忽略了一点:
怎么肯定哪些对象或内存区域是能够被回收的呢???
在java中对于对象是否还“活着”,采用的不是像Python或者其余语言中的"引用计数"的方法。
java中采用的是"可达性分析"。
至于可达性分析的细节不必去深究,可是由"判断对象是否还存活?"引出的另外一个问题却不得不考虑,看下文。
不管采用什么方法去区分哪些对象还活着,不得不作的一个让步就是:这个断定过程当中必须暂时让其余全部的线程都暂时停顿,这个现象对于JVM中的各个对象来讲就至关于整个世界中止了。也就是所谓的Stop The World
。
这个停顿固然是有必要的,好比你开始分析对象的存活状态时一个对象是无用的,当你分析完成后那个对象却让其余线程操做了变成有效对象了。
因此,在整个判断过程当中,要可以确保一致性。也就免不了Stop The World
。
固然,应用的规模越大,Stop The World
带来的影响越大。
因此,频繁的GC也不见得是好事。
上面说的都是GC的大体理论知识,如今看看GC的实现:垃圾收集器。
Serial收集器是众多垃圾收集器中的元老。是一个单线程的收集器。在它进行垃圾收集时,必须暂停其余全部的工做线程,直到它收集结束(Stop The World)。
虽然它的出现很是早,可是它依然是虚拟机运行在Client模式下的默认新生代收集器
,也有其独特的优势:
简单而高效(与其余收集器的单线程比)
对于限定单个CPU的环境来讲,Serial收集器因为没有线程交互的开销
这个ParNew的介绍是来自《深刻理解JVM》的做者说的,与本人没任何关系 ^_^ .. ^_^
ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集以外,其他行为包括Serial收集器可用的全部控制参数(例如:-XX:SurvivorRatio、 -XX:PretenureSizeThreshold、 -XX:HandlePromotionFailure等)、 收集算法、 Stop The World、 对象分配规则、 回收策略等都与Serial收集器彻底同样,在实现上,这两种收集器也共用了至关多的代码。
是许多运行在Server模式下的虚拟机中首选的新生代收集器,其中有一个与性能无关但很重要的缘由是,除了Serial收集器外,目前只有它能与CMS收集器配合工做
ParNew收集器在单CPU的环境中绝对不会有比Serial收集器更好的效果
甚至因为存在线程交互的开销,该收集器在经过超线程技术实现的两个CPU的环境中都不能百分之百地保证能够超越Serial收集器。
固然,随着可使用的CPU的数量的增长,它对于GC时系统资源的有效利用仍是颇有好处的。 它默认开启的收集线程数与CPU的数量相同,在CPU很是多的环境下,可使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数
他的特色以下:
是一个新生代收集器
使用复制算法的收集器
并行的多线程收集器
Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
也常常称为“吞吐量优先”收集器
-XX:MaxGCPauseMillis ==> 控制最大垃圾收集停顿时间,大于零的毫秒数
-XX:GCTimeRatio ==> 直接设置吞吐量大小,吞吐量的倒数,既然是个比率,也就是个0到100的整数
-XX:+UseAdaptiveSizePolicy
是一个开关参数,当这个参数打开以后,就不须要手工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、 晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了.
虚拟机会根据当前系统的运行状况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)
Serial收集器的老年代版本
一个单线程收集器
使用“标记-整理”算法
是Parallel Scavenge收集器的老年代版本
使用多线程和“标记-整理”算法
CMS:Concurrent Mark Sweep
是一种以获取最短回收停顿时间为目标的收集器
此处的停顿指的就是上文提到的"Stop The World"
其名称中的MS指的就是Mark Sweep,它采用的算法就是标记/清除
他有另一个名字就是:Concurrent Low Pause Collector(并发、低停顿)
他的缺点以下:
对CPU资源很是敏感
它虽然不会致使用户线程停顿,可是会由于占用了一部分线程而致使应用程序变慢,总吞吐量会下降
没法处理浮动垃圾
标记/清除------->内存不连续
是一款面向服务端应用的垃圾收集器
并行与并发
G1能充分利用多CPU、 多核环境下的硬件优点,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿的时间,部分其余收集器本来须要停顿Java线程执行的GC动做,G1收集器仍然能够经过并发的方式让Java程序继续执行
分代收集
空间整合
不会产生内存空间碎片,收集后能提供规整的可用内存
分配大对象时不会由于没法找到连续内存空间而提早触发下次GC
可预测的停顿:低停顿
《深刻理解JVM》一书是这么说的:
在G1以前的其余收集器进行收集的范围都是整个新生代或者老年代,而G1再也不是这样。 使用G1收集器时,Java堆的内存布局就与其余收集器有很大差异,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代再也不是物理隔离的了,它们都是一部分Region(不须要连续)的集合。
GC日志的格式乍看起来乱七八糟,乌漆嘛黑的。固然他确定是有格式的。就拿《深刻理解JVM》中的这段代码来讲吧:
public class ReferenceCountingGC { public Object instance = null; private static final int _1MB = 1024 * 1024; /** * 这个成员属性的惟一意义就是占点内存,以便能在GC日志中看清楚是否被回收过 */ byte[] bigSize = new byte[2 * _1MB]; public static void main(String[] args) { ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; // 假设在这行发生GC,objA和objB是否能被回收? System.gc(); } }
虚拟机参数:
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps
在个人机器(jdk1.7)上输出以下:
2016-12-17T16:11:19.650+0800: 0.093: [GC [PSYoungGen: 5427K->568K(38400K)] 5427K->568K(124416K), 0.0016819 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 2016-12-17T16:11:19.652+0800: 0.095: [Full GC [PSYoungGen: 568K->0K(38400K)] [ParOldGen: 0K->463K(86016K)] 568K->463K(124416K) [PSPermGen: 2514K->2513K(21504K)], 0.0109008 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] Heap PSYoungGen total 38400K, used 998K [0x00000007d5c80000, 0x00000007d8700000, 0x0000000800000000) eden space 33280K, 3% used [0x00000007d5c80000,0x00000007d5d79a60,0x00000007d7d00000) from space 5120K, 0% used [0x00000007d7d00000,0x00000007d7d00000,0x00000007d8200000) to space 5120K, 0% used [0x00000007d8200000,0x00000007d8200000,0x00000007d8700000) ParOldGen total 86016K, used 463K [0x0000000781600000, 0x0000000786a00000, 0x00000007d5c80000) object space 86016K, 0% used [0x0000000781600000,0x0000000781673eb0,0x0000000786a00000) PSPermGen total 21504K, used 2520K [0x000000077c400000, 0x000000077d900000, 0x0000000781600000) object space 21504K, 11% used [0x000000077c400000,0x000000077c676178,0x000000077d900000)
解释以下:
2016-12-17T16:11:19.650+0800 -XX:+PrintGCDateStamps的做用,就是GC的时间了 0.093:表示的从JVM启动以来通过的秒数 GC [PSYoungGen:.... GC发生的区域 PSYoungGen表示采用的收集器为Parallel Scavenge 若是使用的是Serial收集器,新生代名为“Default New Generation”,显示就是“[DefNew” 若是使用的是ParNew收集器,新生代名称为“[ParNew”,意为“Parallel New Generation” 若是采用的是Parallel Scavenge收集器,新生代名称就是“PSYoungGen” “Full”,说明此次GC是发生了Stop-The-World
GC日志,暂时就先写这么多吧,在后续的文章中再详细介绍GC日志。
注:如下参数总结来自《深刻理解JVM》一书
UseSerialGC : 是否使用Serial收集器
启用后将使用Serial + Serial Old
的组合来进行垃圾回收
这也是Client模式下的默认值
UseParNewGC : 是否使用ParNew收集器
将使用ParNew + Serial Old
的组合来进行垃圾回收
UseConcMarkSweepGC
启用后将使用ParNew + CMS + Serial Old
的组合来进行垃圾回收
Serial Old 做为CMS的后备收集器(Concurrent Mode Failure)
UseParallelGC
使用Parallel Scavenge + Serial Old
的组合来进行垃圾回收
这也是Server模式下的默认值
UseParallelOldGC
使用 Parallel Scavenge + Parallel Old
的组合来进行垃圾回收
SurvivorRatio
新生代中Eden和Survivor的比值
默认为8,即:Eden:Survivor=8:1
PretenureSizeThreshold
这个大小值,表示对象大小大于多少以后直接分配到老年代而不进入新生代
MaxTenuringThreshold
这个年龄值表示对象在通过多少次Minor GC以后就进入老年代
每次Minor GC以后,对象的该属性值就加1
UseAdaptiveSizePolicy
动态调节堆中各个区域的大小和进入老年代的年龄
HandlePromotionFailure
是否容许分配担保失败
担保失败指的是: 老年代的剩余空间大小没法容纳新生代中的Eden和Survivor的状况
ParallelGCThreads
并行GC的线程数
GCTimeRatio
GC时间占总时间的比例
只有在使用Parallel Scavenge的状况下生效
默认值:99
MaxGCPauseMillis
GC的最大停顿时间
只有在使用Parallel Scavenge的状况下生效
CMSInitiatingOccupancyFraction
CMS收集器在老年代空间被占用多少后触发GC
只对CMS收集器生效
默认值:68%
UseCMSCompactAtFullCollection
CMS收集器在完成垃圾回收后是否进行内存碎片整理
只对CMS收集器生效
CMSFullGCsBeforeCompaction
CMS通过多少次GC后再进行碎片整理
也就是设置CMS收集器在进行N次垃圾收集后再进行一次碎片整理
只对CMS收集器生效
Minor GC指的是新生代的GC
Minor GC比较频繁
速度也比较快
Full GC/Major GC指的是老年代的GC
Full GC的速度通常比Minor GC慢10倍左右
Full GC的出现每每会有Minor GC的伴随
《深刻理解JVM》
《Java虚拟机规范》-JDK1.7