Java GC之常见监控与分析命令总结(上)

上一篇文章简单写了几种常见的垃圾收集器的参数设置,设置参数的时候离不开对对系统进行监控和分析,所以总结一下监控和分析的常见命令。 jps:JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程 命令格式: jps \[options\] \[hostid\] hostid为RMI注册表中注册的主机名,其他常用参数如下: -q:只输出LVMID,省略主类的名称 -m:输出虚拟机进程启动的时候传递给主类main()方法的参数 -l:输出主类的全名,如果进程执行的是jar包,输出jar路径 -v:输出虚拟机进程启动时JVM参数 命令执行样例: jps -l jstat:JVM Statistics Monitoring Tool,用于收集Hotspot虚拟机各方面的运行数据 命令格式: jstat \[option vmid [interval[s|ms\] \[count\]]] 对于命令格式中的VMID和LVMID,如过是本地虚拟机进程,VMID和LVMID是一致的,如果是远程虚拟机,那VMID的格式应当是: \[protocol:\] \[//\] lvmid[@hostname[:port]/servername] interval和count代表查询的间隔和次数,如果省略这两个参数,说明只查一次,其他常用参数: -class:监视装载类、卸载类、总空间以及类装载所耗费的时间 -gc:监视java堆状况,包括eden区、两个survivor区、老年代、永久代等的容量、已用空间、GC时间合计信息 -gccapacity:监视内容与-gc基本相同,但输出主要关注java堆各个区域使用到最大、最小空间 -gcutil:监视内容与-gc基本相同,但输出主要关注已使用控件占总空间的百分比 -gccause:与-gcutil功能一样,但是会额外输出导致上一次gc产生的原因 -gcnew:监视新生代GC情况 -gcnewcapacity:监视内容与-gcnew基本相同,输出主要关注使用到的最大、最小空间 -gcold:监视老年代GC情况 -gcoldcapacity:监视内容与-gcold基本相同,输出主要关注使用到的最大、最小空间 -gcpermcapacity:输出永久代使用到的最大、最小空间 -compiler:输出JIT编译过的方法、耗时等信息 -printcompilation:输出已经被JIT编译过的方法 命令执行样例: jstat -gcutil 2764 1000 jinfo:Configuration Info for Java,显示虚拟机的配置信息 使用jps命令的-v参数可以查看虚拟机启动时显示指定的参数列表,但如果想知道未被显式指定的参数的系统默认值,除了去找资料以外,就得使用jinfo的-flag选项,命令格式: jinfo [option] pid 执行样例:查询CMSInitiatingOccupancyFraction参数值 jinfo -flag CMSInitiatingOccupancyFraction 2764 jmap:Memory Map for Java,生成虚拟机的内存转储快照(heapdump文件) 命令格式: ...

March 19, 2017 · 1 min · 159 words · Bridge Li

Java GC之常见垃圾收集器参数总结

上一篇文章简单写了几种常见的垃圾收集器,俗话说,好记性不如烂笔头,今天总结一下这些垃圾收集器的参数总结,供自己和需要的读者将来查阅 -XX:+UseSerialGC : Jvm运行在Client模式下的默认值,打开此开关后,使用Serial + Serial Old的收集器组合进行内存回收 -XX:+UseParNewGC : 打开此开关后,使用ParNew + Serial Old的收集器进行垃圾回收 -XX:+UseConcMarkSweepGC : 使用ParNew + CMS + Serial Old的收集器组合进行内存回收,Serial Old作为CMS出现“Concurrent Mode Failure”失败后的后备收集器使用 -XX:+UseParallelGC : Jvm运行在Server模式下的默认值,打开此开关后,使用Parallel Scavenge + Serial Old的收集器组合进行回收 -XX:+UseParallelOldGC : 使用Parallel Scavenge + Parallel Old的收集器组合进行回收 -XX:SurvivorRatio : 新生代中Eden区域与Survivor区域的容量比值,默认为8,代表Eden:Subrvivor = 8:1 -XX:PretenureSizeThreshold : 直接晋升到老年代对象的大小,设置这个参数后,大于这个参数的对象将直接在老年代分配 -XX:MaxTenuringThreshold : 晋升到老年代的对象年龄,每次Minor GC之后,年龄就加1,当超过这个参数的值时进入老年代 -XX:UseAdaptiveSizePolicy : 动态调整java堆中各个区域的大小以及进入老年代的年龄 -XX:+HandlePromotionFailure : 是否允许新生代收集担保,进行一次minor gc后, 另一块Survivor空间不足时,将直接会在老年代中保留 -XX:ParallelGCThreads : 设置并行GC进行内存回收的线程数 -XX:GCTimeRatio : GC时间占总时间的比列,默认值为99,即允许1%的GC时间,仅在使用Parallel Scavenge 收集器时有效 -XX:MaxGCPauseMillis : 设置GC的最大停顿时间,在Parallel Scavenge 收集器下有效 ...

March 5, 2017 · 1 min · 96 words · Bridge Li

Java GC之常见垃圾收集器

上一篇文章简单写了JVM的常见垃圾回收算法,今天就让我们看看根据这些算法有哪些常见的垃圾收集器,他们有什么特点,然后根据自己的应用特点和要求组合出各个年代所使用的收集器。 上图展示了JDK1.7Update14之后的HotSpot虚拟机的7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用。虚拟机所处的区域,则表示它是属于新生代收集器还是老年代收集器 Serial收集器 Serial收集器是最基本、发展历史最悠久的收集器,在JDK 1.3.1之前是虚拟机新生代收集的唯一选择。见名知意它是一个单线程的收集器,“单线程”的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束,也就是传说中的Stop The World,简称STW。那么它是不是已经被淘汰的一个垃圾收集器呢,事实上并不是,由于与其他收集器的单线程比简单而高效,对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。所以Serial收集器是虚拟机运行在Client模式下的默认新生代收集器。它的运行示意图如下: ParNew收集器 ParNew收集器其实就是Serial收集器的多线程版本,是一个并行垃圾收集器,并行的含义是:多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。除了使用多条线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一样,在实现上,这两种收集器也共用了相当多的代码。ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器,还有一个和性能无关的但是很重要的原因:除了Serial收集器外,目前只有它能与CMS收集器配合工作。和Serial收集器相比,ParNew收集器在单CPU的环境中绝对不会有比Serial收集器更好的效果,甚至由于存在线程交互的开销,该收集器在通过超线程技术实现的两个CPU的环境中都不能百分之百地保证可以超越Serial收集器。然而,随着可以使用的CPU的数量的增加,它对于GC时系统资源的有效利用还是很有好处的。其运行示意图如下: Parallel Scavenge收集器 Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器。那么他有什么应用场景呢?事实上Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,其他收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput),所以由于与吞吐量关系密切,Parallel Scavenge收集器也经常称为“吞吐量优先”收集器。所以它的应用场景体现在:停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。除此之外和ParNew收集器相比,它具有自适应调节策略。Parallel Scavenge收集器有一个参数-XX:+UseAdaptiveSizePolicy。当这个参数打开之后,就不需要手工指定新生代的大小、Eden与Survivor区的比例、晋升老年代对象年龄等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics),其运行示意图如下: Serial Old收集器 Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。它的主要应用场景:1. 给Client模式下的虚拟机使用(还记得Client模式下新生代的默认垃圾收集器是啥吗?);2. 在Server模式下,那么它主要还有两大用途:一种用途是在JDK 1.5以及之前的版本中与Parallel Scavenge收集器搭配使用,另一种用途就是作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。运行示意图见Serial垃圾收集器 Parallel Old收集器 Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。这个收集器是在JDK 1.6中才开始提供的,在此之前,新生代的Parallel Scavenge收集器一直处于比较尴尬的状态。原因是,如果新生代选择了Parallel Scavenge收集器,老年代除了Serial Old收集器外别无选择(Parallel Scavenge收集器无法与CMS收集器配合工作)。由于老年代Serial Old收集器在服务端应用性能上的“拖累”,使用了Parallel Scavenge收集器也未必能在整体应用上获得吞吐量最大化的效果,由于单线程的老年代收集中无法充分利用服务器多CPU的处理能力,在老年代很大而且硬件比较高级的环境中,这种组合的吞吐量甚至还不一定有ParNew加CMS的组合“给力”。直到Parallel Old收集器出现后,“吞吐量优先”收集器终于有了比较名副其实的应用组合。运行示意图见Parallel Scavenge垃圾收集器 CMS收集器 在JDK 1.5时期,HotSpot推出了一款在强交互应用中几乎可认为有划时代意义的垃圾收集器——CMS收集器,这款收集器是HotSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。并发的含义就是:指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上,他可不用同于并行,并行会有STW,并发几乎没有STW。CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。它的运行示意图如下: 从图上可以看出,CMS收集器是基于“标记—清除”算法实现的,它的运作过程相对于前面几种收集器来说更复杂一些,整个过程分为4个步骤: ①. 初始标记(CMS initial mark) 初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,需要“Stop The World”。 ...

February 26, 2017 · 1 min · 152 words · Bridge Li

Java GC之垃圾回收算法

上一篇文章简单写了一下JVM如何判断一个对象是否已经死了,当判断出一个对象已经死了之后,接下来就要进行垃圾回收了,所以在进行垃圾回收之前,先让我们看看垃圾回收的算法有哪些。 标记-清除算法 标记清除见名知意该算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成之后统一回收所有被标记的对象,至于如何标记就是上一篇文章中所讲的方法了。这种算法比较简单,容易理解,同时也是一个最基础的垃圾回收算法,后面所讲的算法都是对他的改进,至于为什么需要改进,因为他主要存在两个不足: ①. 效率问题,标记和清除两个阶段的效率都不高; ②. 空间问题,标记清除之后会留下大量的不连续的内存碎片,内存碎片会导致当后面在程序的运行过程中可能需要给较大的对象分配空间时,无法找到足够的内存而不得不提前触发另一次垃圾回收。这种算法的执行过程如下图: 复制算法 复制算法为了解决标记清除算法的效率问题:它将内存分为容量大小相同的两块,每次只使用其中一块,当这一块的内存空间用完了,他就讲里面存活着的对象复制到另一块上面,然后再把已使用过的这一块内存空间一次性清理掉,这样使得每次都是对半个内存区域进行会回收,内存分配时也不用考虑空间碎片的问题,只需要移动指针,按顺序分配即可,实现简单,运行高效。但是他也有缺点:每次使用的内存空间只有整个空间的一半,这“浪费”有点高啊。复制算法的执行过程如下图: 不过现代的商业JVM都采用了这种算法来回收新生代,究其原因不仅仅他解决了效率问题,更是经研究表明:新生代中的对象高达98%都是“朝生夕死”的,所以这样一来就不需要1:1来划分空间了。直接将内存空间分为一块较大的Eden区和两块较小的Survivor区,每次使用Eden和其中一块Survivor区,当需要回收时,直接将Eden和Survivor中还存活的对象一次性的复制到另一块Survivor区,最后在清理Eden和刚使用过的Survivor。HotSpot虚拟机Eden和Survivor的大小比例默认为8:1,也就是每次使用的空间大小是90%,只“浪费”了10%。但是98%的对象回收也不能保证,每次存活的对象所使用的空间小于10%,所以当Survivor空间不够时,就需要其他空间(一般是老年代)进行分配担保,如果另一块Survivor空间没有足够的空间存放上一次新生代垃圾回收存活的对象时,这些对象将直接通过分配担保机制进入老年代。 标记-整理算法 复制算法适用于新生代的对象“朝生夕死”,如果一个区域内的对象老是不死,不仅内次都需要复制大量的对象,效率很低,而且还需要额外的空间进行担保(分配担保是一个很复杂的东西,今后有机会会说到),所以对于老年代的对象,这种算法是不适合的,于是就提出了标记-整理算法。 标记-整理算法和标记-清除算法一样,也是分两个阶段,而且第一个阶段也一样,都是标记,所不同的是第二个阶段,不是对可回收的对象进行直接清理,而是让还存活着的对象向一端移动,然后直接清理掉端边界以为的内存空间,该算法的执行过程如下图: 分代收集算法 当前的商业JVM都是采用的这种算法,其实这种算法并没有什么新思想,而是根据对象存活周期的不同将内存划分几块,一般是把Java堆分为两块:新生代和老年代,这样就可以根据各个代的不同特点采取最适当的垃圾收集算法。新生代的对象大多都是“朝生夕死”的,而且还可以有老年代进行担保,那就采用复制算法,只需要付出少量的存活对象的复制成本就可以完成收集,而且一般也不需要启用担保策略,而老年代的对象存活率一般比较高、没有空间进行担保,就只有采用“标记-清理”或者“标记-整理”算法来进行回收了。 参考资料:周志明《深入理解Java虚拟机》第二版第三章

February 19, 2017 · 1 min · 16 words · Bridge Li

Java GC之对象已死吗

差不多两年以前曾经写过一篇文章:JAVA 性能调优,其实在那篇文章中只是简单的说了,对象的分布。这篇文章继续对分布于堆中的对象的生命周期进行说明,也就是确定堆中的这些对象哪些还是“活着”的,哪些是已经“死去”(即不可能再被任何途径使用的对象)的。 引用计数算法 有很多人认为判断对象是否活着的算法是这样的:给对象添加一个引用计数器,每当有一个地方引用他的时候,计数器就加1,引用失效的时候,计数器减1,当计数器的数值为0时就是不可能在被引用的对象,此时就就可以认为是已死的对象。引用计数器算法实现简单,效率也很高,是一个不错的算法,但是主流的Java虚拟机并没有采用这种算法来管理内存,其中最主要的原因就是:它很难解决对象之间循环引用的问题。 举一个简单的例子:对象objA和objB都有字段instance,赋值令,除此之外,这两个对象再无任何引用,实际上他们已经不可能在被访问到,但是他们因为相互引用对方,计数器都不可能为0,计数器算法是无法通知GC收集器收集他们的。 package demo; /** * testGC()方法执行后,objA和objB会不会被GC呢? * * @author BridgeLi * */ public class ReferenceCountingGC { public Object instance = null; private static final int _1MB = 1024 * 1024; // 这个成员的唯一意义就是占用内存,以便能在GC日志中看清楚是否被回收过 private byte[] bigSize = new byte[2 * _1MB]; public static void testGC() { ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; // 假设发生了GC,看objA和objB是否能被回收 System.gc(); } public static void main(String[] args) { ReferenceCountingGC.testGC(); } } 从这个例子的运行结果来看,虚拟机并没有这两个对象存在相互引用就不收集他们,从而证明了Java虚拟机不是通过引用计数算法来判断对象是否已死的。 ...

February 11, 2017 · 2 min · 215 words · Bridge Li