聊聊JVM(一)相对全面的GC总结

最近时间比较紧张,要写的东西也有很多,只能想到一点写一点。关于GC,网上的资料太多,之前对一个系统调优的时候又回顾了一下,找了几篇广泛流传的资料,大部分都是大同小异,这里总个总结,希望能够做个相对的全集,并写出一些新的点,比如Card Marking(卡片标记)等。

首先是大家都要提到的GC的基础算法:标记清除,标记整理,复制,分代。这些算法的第一步都是做的一件事: 标记(Mark)。

JVM的标记算法采用了根搜索算法(Root Tracing)。根有几种:

1. JVM栈的Frame里面的引用

2. 静态类,常量的引用

3. 本地栈中的引用

4. 本地方法的引用

一般我们能控制的就是JVM栈中的引用和静态类,常量的引用。标记也分为几个阶段,比如

1. 标记直接和根引用的对象

2. 标记间接和根引用的对象

3. 由于分代算法,被老年代对象所引用的新生代的对象

对于第三种,JVM采用了Card Marking(卡片标记)的方法,避免了在做Minor GC时需要对整个老年代扫描。具体的方法如下:

1. 将老年代的内存分片,1个片默认是512byte

2. 如果老年代的对象发生了修改,就把这个老年代对象所在的片标记为脏 dirty。或者老年代对象指向了新生代对象,那么它所在的片也会被标记为dirty

3. 没有标记为脏的老年代片它没有指向新的新生代对象,所以可以不需要去扫描

4. Minor GC扫描老年代空间时,只需要去扫描脏的卡片的对象,不需要扫描整个老年代空间

所以做Minor GC时标记的时间 = T(stack_scan) + T(card_scan) + T(old_root_scan).

T(stack_scan): 级联扫描在JVM栈里的根的时间

T(card_scan): 级联扫描卡表中脏卡片的时间

T(old_root_scan): 扫描在老年代中的直接的根的时间。注意是直接的根,不会去级联扫描老年代的对象。因为扫描都是从根开始的,一开始不知道根到底是在老年代还是新生代

和Card Marking相关的一个重要的JVM参数是-XX:UseCondCardMark 。使用这个参数的原因是在高并发的情况下,Card标记为脏的操作本身就存在着竞争,使用这个参数可以避免卡片被重复标记为脏,从而提高性能。

说完了标记,下面提一下几种基础的GC算法,没有什么新的点,直接引用网上的图

标记-清除算法

复制算法

标记--整理算法

分代算法是将对象分为新生代和老年代,然后使用不同的GC策略来进行回收,提高整体的效率。

由于新生代的大部分对象都会在一次Minor GC中死亡,存活的对象很少,所以新生代的GC收集器都采用了复制算法。新生代分为Eden + S0 + S1. S0和S1就是用来实现复制的,在任何一次Minor GC后,S0和S1总是只有一个区域有数据,另一个区域为空,以便于下一次复制使用

当新生代空间不能满足大对象分配时,老年代空间为它提供了分配担保,大对象可以直接进入老年代。有两个JVM参数可以控制新生代进入老年代的门槛:

PretenureSizeThreshold: 单位是B,设置了对象大小的阀值

MaxTenuringThreshold: 设置了进入老年代的年龄的阀值

老年代对象一般都是存活时间久,老年代的空间本来就大,所以没有更多空间来提供分配担保,所以老年代一般采用标记--清理或者标记--整理算法。

下面这张图很好地介绍了JDK6的各种GC收集器以及各自的特点:

1. 新生代都采用复制算法

2. CMS采用了标记--清除算法,由于标记清除算法会生成内存碎片,所以JVM提供了参数来使CMS可以在几次清除后作一次整理

-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。

-XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片

3. Serial Old(MSC)和Parallel Old都采用标记整理算法

4. UseSerialGC默认会在新生代使用Serial收集器,在老年代使用Serial Old收集器,这两个都是单线程的收集器

5. UseConcMarkSweepGC默认会再新生代使用ParNew收集器,这是个并发的收集器。在老年代会使用CMS + Serial Old收集器,当CMS失败的时候,会启用Serial Old做FULL GC

6. UseParallelOldGC默认会在新生的使用Parallel Scavenge收集器,在老年代使用Parallel Old收集器。这两个收集器都是吞吐量优先,所谓吞吐量优先就是它可以严格控制GC的时间,从而保证吞吐量。但是吞吐量提高了,新生代和老年代的空间就是动态调整的,而不是按照初始配置的大小。因为单位时间清除的垃圾量近乎一个常量,既然要保证时间,那么必须保证垃圾总量,而垃圾总量可以通过新生代和老年代的大小来控制的

7. 对于和用户有交互的应用,比如Web应用,一个重要的考量是系统的响应时间,要保证系统的响应时间就要保证由GC导致的stop the world次数少,或者让用户线程和GC线程一起运行。所以Web应用是使用CMS收集器的一个重要场景。CMS减少了stop the world的次数,不可避免地让整体GC的时间拉长了

8. 对于计算密集型的应用可能会考虑计算的吞吐量,这时候可以使用Parallel Scavenge收集器来保证吞吐量

9. Serial, ParNew, Parallel Scanvange, Parallel Old, Serial Old全程都会Stop the world,JVM这时候只运行GC线程,不运行用户线程

10. CMS主要分为 initial Mark, Concurrent Mark, ReMark, Concurrent Sweep等阶段,initial Mark和Remark占整体的时间比较较小,它们会Stop the world. Concurrent Mark和Concurrent Sweep会和用户线程一起运行。

下面这张图对GC的日志信息做了说明:

关于JVM调优的各种参数设置,网上一抓一大把,这里不多说了。有一个调优的整体的原则:

1. 先做一个JVM的性能测试,了解当前的状态

2. 明确调优的目标,比如减少FULL GC的次数,减少GC的总时间,提高吞吐量等

3. 调整参数后再进行多次的测试,分析,最终达到一个较为理想的状态。各种参数要根据系统的自身情况来确定,没有统一的解决方案

将各种工具的文章页很多,这里从解决问题的角度出发列出几个。

查看JVM启动参数

1. jps -v

2. jinfo -flags pid

3. jinfo pid -- 列出JVM启动参数和system.properties

4. ps -ef | grep java

查看当前堆的配置

1. jstat -gc pid 1000 3  -- 列出堆的各个区域的大小

2. jstat -gcutil pid 1000 3 -- 列出堆的各个区域使用的比例

3. jmap -heap pid  -- 列出当前使用的GC算法,堆的各个区域大小

查看线程的堆栈信息

1. jstack -l pid

dump堆内的对象

1. jmap -dump:live,format=b,file=xxx pid

2. -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=xxx  -- 设置JVM参数,当JVM OOM时输出堆的dump

3. ulimit  -c unlimited  -- 设置Linux ulimit参数,可以产生coredump且不受大小限制。之前在线上遇到过一个极其诡异的问题,JVM整个进程突然挂了,这时候依靠JVM本身生成dump文件已经不行了,只有依赖Linux,让系统来生成进程挂掉的core dump文件

使用jstack 可以来获得这个coredump的线程堆栈信息:  jstack "$JAVA_HOME/bin/java" core.xxx > core.log

获得当前系统占用CPU最高的10个进程,线程

ps Hh -eo pid,tid,pcpu,pmem | sort -nk3 |tail > temp.txt

图形化界面

1. jvisualvm 里面有很多插件,比如Visual GC,可以可视化地看到各个堆区域时候的状态,从而可以对整体GC的性能有整体的认识

就说到这吧,有遗漏的后面再补充

参考资料:  Understanding GC pauses in JVM, HotSpot‘s minor GC.

《深入理解JVM》

《Think in GC》

时间: 2024-11-09 23:24:34

聊聊JVM(一)相对全面的GC总结的相关文章

聊聊JVM(一)相对全面的GC总结(转)

转至:http://blog.csdn.net/iter_zc/article/details/41746265 最近时间比较紧张,要写的东西也有很多,只能想到一点写一点.关于GC,网上的资料太多,之前对一个系统调优的时候又回顾了一下,找了几篇广泛流传的资料,大部分都是大同小异,这里总个总结,希望能够做个相对的全集,并写出一些新的点,比如Card Marking(卡片标记)等. 首先是大家都要提到的GC的基础算法:标记清除,标记整理,复制,分代.这些算法的第一步都是做的一件事: 标记(Mark)

聊聊JVM(二)说说GC的一些常见概念

转自CSDN 上一篇总结GC的基础算法,各种GC收集器的基本原理,还是比较粗粒度的概念.这篇会整理一些GC的常见概念,理解了这些概念,相信对GC有更加深入的理解 1. 什么时候会触发Minor GC? Eden区域满了,或者新创建的对象大小 > Eden所剩空间 CMS设置了CMSScavengeBeforeRemark参数,这样在CMS的Remark之前会先做一次Minor GC来清理新生代,加速之后的Remark的速度.这样整体的stop-the world时间反而断 Full GC的时候会

聊聊JVM(四)深入理解Major GC, Full GC, CMS

很多人都分不清Major GC, Full GC的概念,事实上我查了下资料,也没有查到非常精确的Major GC和Full GC的概念定义.分不清这两个概念可能就会对这个问题疑惑:Full GC会引起Minor GC吗? 经过一系列的查找和对JVM表现的分析,基本可以给Full GC和Major GC下一个定义了,这篇说一说概念和理由. 这篇文章Major GCs – Separating Myth from Reality 基本讨论的也是这个问题,但是它没有给出实际的证明. 我们可以认为Maj

JVM初探- 内存分配、GC原理与垃圾收集器

JVM初探- 内存分配.GC原理与垃圾收集器 标签 : JVM JVM内存的分配与回收大致可分为如下4个步骤: 何时分配 -> 怎样分配 -> 何时回收 -> 怎样回收. 除了在概念上可简单认为new时分配外, 我们着重介绍后面的3个步骤: I. 怎样分配- JVM内存分配策略 对象内存主要分配在新生代Eden区, 如果启用了本地线程分配缓冲, 则优先在TLAB上分配, 少数情况能会直接分配在老年代, 或被拆分成标量类型在栈上分配(JIT优化). 分配的规则并不是百分百固定, 细节主要取

JVM调优——之CMS GC日志分析

最近在学习JVM和GC调优,今天总结下CMS的一些特点和要点,首先贴上一个实际的CMS GC log,先来解读下各个元素. /* 从下面的GC日志可以看出,当前最新版本(JDK1.8)中的CMS大致分为6步 1. CMS Initial Mark 初始标记 2. CMS-concurrent-mark 并发标记 3. CMS-concurrent-preclean 预处理 4. CMS-concurrent-abortable-preclean 预处理 5. CMS Final Remark 重

聊聊JVM的年轻代

聊聊JVM的年轻代 1.为什么会有年轻代 我们先来屡屡,为什么需要把堆分代?不分代不能完成他所做的事情么?其实不分代完全可以,分代的唯一理由就是优化GC性能.你先想想,如果没有分代,那我们所有的对象都在一块,GC的时候我们要找到哪些对象没用,这样就会对堆的所有区域进行扫描.而我们的很多对象都是朝生夕死的,如果分代的话,我们把新创建的对象放到某一地方,当GC的时候先把这块存"朝生夕死"对象的区域进行回收,这样就会腾出很大的空间出来. 2.年轻代中的GC     HotSpot JVM把年

你的JVM还好吗?GC初步诊断

阿飞的博客 JVM的GC机制绝对是很多程序员的福音,它让Java程序员省去了自己回收垃圾的烦恼.从而可以把大部分时间专注业务身上,大大提高了业务开发速度,让产品需求尽快的落地抢占市场.但是也正因为如此,导致很多Java程序员对JVM和GC知之甚少,以我愚见大家对JVM&GC不够了解的有几个原因: 门槛太高.我们平常接触的spring,dubbo,java集合&J.U.C,网上都有无数优秀的文章对其深入的分析.而且都是基于Java语言,我们在学习的过程中,可以自己很容易的debug源码更深入

最全面的Android Studio使用教程

最全面的Android Studio使用教程 Android Studio是一套面世时间还不长的IDE(即集成开发环境),目前已经免费向谷歌及Android的开发人员发放.Android Studio以IntelliJ IDEA为基础,后者同样是一套相当出色的Android开发环境.在今天的文章中,我们将共同了解如何创建一个全新Android项目,同时充分发挥 Android Studio所提供的各项功能优势. 1.项目设置 在开始畅游Android Studio的世界之前,大家首先需要点击此处

目前见到的最傻瓜全面的STRUTS入门教程^_^

目前见到的最傻瓜全面的STRUTS入门教程^_^ 一  Jakarta Struts学习之新手上路 Web 应用开发早期曾经是那么的“简单”,那个时候还只是纯HTML页面和浏览器特效而已.由于还涉及不到动态数据操作和商业应用,也就省去了很多麻烦.但是这 样的“简单”只是过眼云烟,如今我们不得不为复杂的基于Web的商业应用开发采用诸多技术. 本文将介绍如何利用Struts进行应用开发的前台整合的开发过程.Struts是一个为开发基于模型(Model)-视图(View)-控制器 (Controlle