GC

GC

1. 如何定位垃圾

垃圾就是没有引用的对象,那么如何确定是否还存在引用呢

  • 引用计数(ReferenceCount):存在循环引用问题
  • 根可达算法(RootSearching):从跟开始寻找(线程栈变量,静态变量,常量池,JNI指针),找不到的就算垃圾


2. 垃圾回收算法

  • 标记清除(mark sweep) - 位置不连续 产生碎片 效率偏低(两遍扫描)

  • 拷贝算法 (copying) - 没有碎片,浪费空间

  • 标记压缩(mark compact) - 没有碎片,效率偏低(两遍扫描,指针需要调整)



3. JVM中的垃圾回收器

10种如下,JVM回收内存通常是组合使用,不同的垃圾回收器采用不同的策略

左边6个分代模型中,下面的是老年代,上面的是年轻代,一般是如图所示搭配(但也可以混杂搭配,看其他虚线)

1.8默认是PS+PO(新生代Parallel Scavenge,老年代Parallel Old),但是可以用分区模型G1(推荐,有些公司运维有JVM调优经验的话说不定会用这个)

下面介绍一下各个组合

3.1 分代模型-Serial系列

如图解释的很清楚,当垃圾回收线程来了,工作线程STW,垃圾回收线程完了,工作线程才能继续

STW:Stop The World,如字面意思,马上!!停止!!!

为什么有时候机器会突然卡顿一下,就是因为STW了

3.2 分代模型-Parallel系列

随着内存越来越大,Serial有点慢,于是出现了Parallel

和Serial差不多也是STW,不过Parallel嘛,垃圾回收线程是多个且并行的

3.3 分代模型-CMS系列

还是随着内存的越来越大,Parallel很慢,原因:线程数和效率不是线性提升的,因为线程需要上下文切换ContextSwitch,到达一定数量后,线程多反而耗费了大量时间在此

CMS:concurrent mark sweep

Parnew和Parallel Scavenge基本一样,只是为了配合CMS而产生的

CMS过程如下:

初始标记也是STW,但是只GCRoot扫描根(前面说的根可达算法可还记得?),很少所以时间可以接受

重新标记也是STW,这里有个bug,后面讲

CMS三色标记法和错标问题

并发标记存在问题,就是错标

情况1:在标记的时候突然有个对象引用没了(成为了垃圾,称为浮动垃圾),这种就是漏标了,这种问题不大,大不了下次gc再清理就是了

情况2:一个对象本来是垃圾,突然又有引用了(存在这种情况嘛?有的,缓存),这种错标就比较严重了

所以存在重新标记的过程

三色标记法

黑色:对象标记,成员field也被标记

灰色:对象标记,成员还未标记

白色:没有遍历到的字段

标记是怎么标记的呢?CMS标记采用三色标记法,重新标记阶段扫描就是扫描灰色的对象。说回情况2,如果第一次标记后黑色的对象有个指针域指向了某个白色对象,再次标记的时候不会再扫描黑色对象,此时就产生了错标

解决办法

  • CMS解决办法:incremental update:简单的说就是A引用变了的时候重新将A置为灰色,这样就可以重新扫描到了

这种办法存在一个bug,就是说对象A有两个field,多线程的时候垃圾回收线程标记了field1不是垃圾,然后扫描field2,工作线程使得field1重新指向了一个垃圾对象D,这样本来A应该被标记为灰色,但是垃圾回收线程不知道filed1改变了,标记完field2后将A置为黑色

因此才需要remark过程,但是这个过程导致CMS这个原本号称“解决STW”的算法产生了历史上最长的STW,也就是因为这样,CMS并没有作为任何一个JDK版本的默认垃圾回收器

  • G1解决办法:SATB

3.4 JVM的堆内存模型

JVM堆的具体内存分区如下,图中的数字是大小比例

可以看到新生代用的拷贝算法,但不是一般的拷贝,是eden:survivor1:survivor2=8:1:1,第一次gc从eden到s1,然后在s1和s2之间来回跳,到达一定的次数后进入老年代(这个次数默认15,CMS默认6,可以通过-XX:MaxTenuringThreshold配置)



4. JVM调优实战经验

  1. 服务器升级加大了内存,反而更加卡顿。原因是内存越大,FGC时间越长。解决办法:PS -> PN + CMS 或者 G1

  2. 线上CPU突然100%。那么一定有线程在占用系统资源,

    1. 找出哪个进程cpu高(top)
    2. 该进程中的哪个线程cpu高(top -Hp)
    3. 导出该线程的堆栈 (jstack)
    4. 查找哪个方法(栈帧)消耗时间 (jstack)
    5. 工作线程占比高 | 垃圾回收线程占比高
  3. 系统内存飙高,如何查找问题?(面试高频)

    1. 导出堆内存 (jmap)
    2. 分析 (jhat jvisualvm mat jprofiler … )
  4. 如何监控JVM

    1. jstat jvisualvm jprofiler arthas top…

附录

1. 对象分配过程

  1. 一些基本类型的变量是有机会分配在栈上的比如局部变量,怎么判定是否能分配在栈上?如果逃逸分析没有逃逸,标量替换可以替换,则分配在栈上
  • 逃逸分析:看附录2
  • 标量替换:即一个对象可以用两个基本类型的变量来替换它,就称为可以标量替换,比如一个对象只有两个int的类型,那么我们完全可以用两个int变量代替这个类(结构体)
  1. 如果对象太大,直接老年代,否则会先TLAB(ThreadLocalAllocationBuffer线程本地分配缓冲区),这个细节一般不会有人问起,但是确实存在的,Eden区有一部分TLAB,线程争用后才进入Eden区的公共空间


2. 逃逸分析

逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法引用,例如作为调用参数传递到其他方法中,称为方法逃逸。甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。如果能证明一个对象不会逃逸到方法或线程之外,也就是别的方法或线程无法通过任何途径访问到这个对象,则可能为这个变量进行一些高效的优化:

  1. 栈上分配

Java虚拟机中,如果确定一个对象不会逃逸出方法之外,那让这个对象在栈上分配内存将会是一个很不错的主意,对象所占用的内存空间就可以随栈帧出栈而销毁。在一般应用中,不会逃逸的局部对象所占的比例很大,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了,垃圾收集系统的压力将会小很多。

  1. 同步消除

线程同步本身是一个相对耗时的过程如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问,那这个变量的读写肯定就不会有竞争,对这个变量实施的同步措施也就可以消除。

  1. 标量替换

标量是指一个数据已经无法再分解成更小的数据来表示了,Java虚拟机的原始数据类型都不能再进一步分解,它们就可以称为标量。如果逃逸分析证明一个对象不会被外部访问,并且这个对象可以被拆散的话,那程序真正执行的时候将可能不创建这个对象,而改为直接创建它的若干个被这个方法使用的成员变量来代替。除了可以让对象的成员变量在栈上(栈上存储的数据,有很大的概率会被虚拟机分配到物理机器高速寄存器中存储)分配和读写之外,还可以为后续进一步的优化手段创建条件。



3. JVM常用命令

3.1 监控相关

基础命令

  • jps:查询运行中的java进程编号和名称,相当于ps命令,但只有java相关的
  • jinfo pid:作用不大,可以查看某个进程的jdk信息,比如jdk版本啊classpath啊encoding啊当前的vm参数啊等等
  • jstack pid:java堆栈信息,类似于idea里面的debug控制台左边,显示了某个进程的所有线程运行了哪个方法,可以用来查死锁
  • jmap -histo pid:查看某个java进程内存分配状况
  • jstat:可以观察到classloader,compiler,gc相关信息,实时监控资源和性能

工具(arthas)

上面介绍的基本命令基本上不用,因为通常都用工具,就很像git,一般都用sourcetree或者idea而不是纯命令行,因为工具更加强大,所以我们来看看

gui工具:jconsole, jvisualvm这些可视化工具都很不错,而且是jdk自带的,但是只能在本地开发压测和debug用,线上服务器通常没有gui,所以用的更多的是arthas(阿里开源的工具,下载并启动jar包,java -jar arthas-boot.jar,在黑框上模拟图形化界面)

下面介绍arthas一些命令

  • help:帮助文档
  • dashboard:仪表盘模拟图形界面,很强,可以监控CPU和内存,相当于top,看图

dashboard展示效果

  • jvm:相当于jinfo,显示了jvm相关信息,垃圾回收器等

  • thread [线程id]:相当于jstack,只列出了线程列表,要看某个线程的详情可以thread 线程id,要找死锁,直接thread -b可以看到某个死锁阻塞状况

  • heapdump:将内存占用信息导出到具体的文件夹,用于查oom,如果疯狂fgc,或者已经出现了oom,那么可以heapdump导出到具体的文件夹,然后用前面说的gui工具打开进行本地分析,相当于jmap命令

注意:arthas的headdump和jmap在生产环境中都不太能用,因为内存有可能很大,完整dump备份一次要很久,生产环境通常使用XX:+HeapDumpOnOutOfMemoryError,代表在oom之前自动导出


3.2 GC常用参数

  • -Xmn -Xms -Xmx -Xss
    年轻代 最小堆 最大堆 栈空间
  • -XX:+UseTLAB
    使用TLAB,默认打开
  • -XX:+PrintTLAB
    打印TLAB的使用情况
  • -XX:TLABSize
    设置TLAB大小
  • -XX:+DisableExplictGC
    System.gc()不管用 ,FGC
  • -XX:+PrintGC
  • -XX:+PrintGCDetails
  • -XX:+PrintHeapAtGC
  • -XX:+PrintGCTimeStamps
  • -XX:+PrintGCApplicationConcurrentTime (低)
    打印应用程序时间
  • -XX:+PrintGCApplicationStoppedTime (低)
    打印暂停时长
  • -XX:+PrintReferenceGC (重要性低)
    记录回收了多少种不同引用类型的引用
  • -verbose:class
    类加载详细过程
  • -XX:+PrintVMOptions
  • -XX:+PrintFlagsFinal -XX:+PrintFlagsInitial
    必须会用
  • -Xloggc:opt/log/gc.log
  • -XX:MaxTenuringThreshold
    升代年龄,最大值15
  • 锁自旋次数 -XX:PreBlockSpin 热点代码检测参数-XX:CompileThreshold 逃逸分析 标量替换 …
    这些不建议设置

Parallel常用参数

  • -XX:SurvivorRatio
  • -XX:PreTenureSizeThreshold
    大对象到底多大
  • -XX:MaxTenuringThreshold
  • -XX:+ParallelGCThreads
    并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同
  • -XX:+UseAdaptiveSizePolicy
    自动选择各区大小比例

CMS常用参数

  • -XX:+UseConcMarkSweepGC
  • -XX:ParallelCMSThreads
    CMS线程数量
  • -XX:CMSInitiatingOccupancyFraction
    使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收)
  • -XX:+UseCMSCompactAtFullCollection
    在FGC时进行压缩
  • -XX:CMSFullGCsBeforeCompaction
    多少次FGC之后进行压缩
  • -XX:+CMSClassUnloadingEnabled
  • -XX:CMSInitiatingPermOccupancyFraction
    达到什么比例时进行Perm回收
  • GCTimeRatio
    设置GC时间占用程序运行时间的百分比
  • -XX:MaxGCPauseMillis
    停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代

G1常用参数

  • -XX:+UseG1GC
  • -XX:MaxGCPauseMillis
    建议值,G1会尝试调整Young区的块数来达到这个值
  • -XX:GCPauseIntervalMillis
    ?GC的间隔时间
  • -XX:+G1HeapRegionSize
    分区大小,建议逐渐增大该值,1 2 4 8 16 32。
    随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长
    ZGC做了改进(动态区块大小)
  • G1NewSizePercent
    新生代最小比例,默认为5%
  • G1MaxNewSizePercent
    新生代最大比例,默认为60%
  • GCTimeRatio
    GC时间建议比例,G1会根据这个值调整堆空间
  • ConcGCThreads
    线程数量
  • InitiatingHeapOccupancyPercent
    启动G1的堆空间占用比例
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×