Loading... # java垃圾回收器以及jvm调优 ## 什么是垃圾 程序的运行需要申请内存,在C语言中,有malloc与free,C++中有new与析构函数,它们都需要手动回收内存,这样就经常出现问题,比如说忘记回收了,就会造成内存泄漏,或者回收了多次,造成非法访问。java则放弃了手动回收,引入了GC(Garbage Collector)的概念,我们new出一个对象,当没有强引用指向它时,就会被识别成垃圾,GC会负责回收。C与C++手工回收,开发效率低,执行效率高,GC处理垃圾,开发效率高,执行效率低。 **P.S.**垃圾就是没有任何强引用指向的一个或者多个对象(循环引用)。 ## 如何定位垃圾 1. 引用计数法(ReferenceCount) - 该方式会记录对象的引用数量,当引用为零时,对象会被回收,缺点是无法解决循环引用问题。a引用b,b引用c,c又引用a,它们互相引用,但是没有任何其它引用指向它们,这时,abc就是一坨垃圾,但由于引用数量不是零,造成GC无法回收。Python用的是引用计数法,但内部具体怎么解决的循环引用问题,不太清楚。 2. 根可达算法(RootSearching) - 该方式是从根对象(GC roots)开始遍历,遍历的到的就是存活对象,遍历不到就是垃圾,循环引用由于从根对象不可达,所以,也会被识别为垃圾。 哪些对象是根对象? JVM stack(jvm栈),native method stack(本地方法栈),run-time constant pool(运行时常量池),static references in method area(方法区中的静态引用),Clazz(装载的各种Class对象),总结起来就是线程栈变量、静态变量、常量池、JNI指针。 ## 常见的垃圾回收算法 - 标记清除(mark sweep) - 找到没用的对象并标记,然后直接清除掉。 ![mark-sweep.png][1] 问题:位置不连续,产生碎片。效率偏低(两遍扫描) - 拷贝算法 (copying) - 将内存分成两块儿,a和b,只用其中一块儿,回收a时,找到有用的复制到b中,然后将a中的对象全部清除,复制时会按照顺序排列,防止碎片产生。 ![copying.png][2] 优点: 没有碎片,效率高。 缺点: 空间浪费,移动复制对象,需要调整对象的引用。 - 标记压缩(mark compact) - 与标记清除一样,需要将对象进行标记,不同的是清除时不是直接清除,而是先将所有有用的对象向一端移动,然后清除端边界之外的其余对象。 ![mark-compact.png][3] 优点: 没有碎片 缺点: 效率偏低(两遍扫描,指针需要调整) ## JVM内存分代模型(用于分代垃圾回收算法) 分代模型是部分垃圾回收器所使用的模型。除Epsilon、ZGC、Shenandoah之外的其它GC都是逻辑分代的。其中G1是逻辑分代,物理不分代,剩下的不光逻辑分代,物理也分代。 GC将内存空间分为新生代 + 老年代 + 永久代(1.7)Perm Generation/ 元数据区(1.8) Metaspace,其中Perm Generation或Metaspace就是方法区的实现,1.8之前是Perm Generation,从1.8开始,改为了Metaspace。Perm Generation必须指定大小,Metaspace可以指定也可以不指定,不指定的话受限于物理内存。 新生代与老年代的大小默认是1:2,可以通过参数修改,新生代包括一个Eden区和两个suvivor区(s0,s1也可以叫s1,s2或者from,to),大小比例默认为8:1:1,当Eden区空间不足时,会触发YGC(MinirGC),大多数对象会被回收,活着的进入S0。再次YGC,Eden与S0中的存活对象进入S1,回收Eden与S0。存活对象会在s0和s1之间不断拷贝(copying算法),每拷贝一次年龄加1,当年龄足够时,会进入老年代。这个年龄最大是15,可以修改,CMS默认是6,其他默认都是15。当对象过大,suvivor区装不下时也会直接进入老年代。 老年代中保存的都是一些顽固分子(多次GC没有回收掉),当老年代满了的时候,就会触发FULL GC(FGC/MajorGC),新生代与老年代同时进行回收,因为在GC工作时,正常的工作线程是停止的,也就是stop-the-world,简称STW,程序无法提供响应,所以GC调优的目的就是尽量减少FGC。 ![堆内存逻辑分区.png][4] ### 对象分配过程 对象分配时,首先会尝试进行栈上分配,满足栈上分配的条件是: - 线程私有小对象 - 无逃逸: 只在某个代码块中存在,出了该代码块,无意义 - 支持标量替换: 能用普通类型属性,来代替整个对象 栈上分配的好处是,没有进入堆空间,用完之后直接弹栈,不必进行垃圾回收。当栈上分配不满足时,会优先考虑线程本地分配TLAB(Thread Local Allocation Buffer),每个线程都有一个自己的私有空间,占用Eden区的1%,当对象比较小时,可以进行线程本地分配,优点是多线程不用竞争,就可以申请到eden的空间,提升效率。当对象过大时,就会直接进入老年代,其它情况进入Eden区,在eden区每经过一次YGC会交换一次suvivor空间,当年龄足够时,进入老年代。当老年代空间不足时,会执行FGC。 动态年龄: 如果在suvivor空间中,相同年龄的对象的大小总和超过suvivor空间的一半,那么大于等于该年龄的对象会直接进入老年代,不用等到MaxTenuringThreshold中要求的年龄。 分配担保: YGC期间,suvivor区空间不够了,对象通过分配担保,直接进入老年代。 ## 常见的垃圾回收器 ![Garbage Collectors.jpg][5] JDK诞生时,有了Serial,它是单线程的垃圾回收器,由于物理内存越来越大,单线程不能满足需求,出现了PS,多线程垃圾回收。为了配合CMS,诞生了PN,CMS是1.4版本后期引入的,它是里程碑式的GC,开启了并发回收的模式,但是CMS问题较多,因此目前所有版本的默认GC都不是CMS。 - Serial 年轻代,串行回收a stop-the-world,copying collector which uses a single GC thread,单cpu效率最高,虚拟机是Client模式的默认垃圾回收器 - SerialOld 老年代,串行回收a stop-the-world,mark-sweep-compact collector that uses a single GC thread - PS(Parallel Scavenge) 年轻代,并行回收a stop-the-world,copying collector which uses multiple GC threads,PS+PO为java8默认的垃圾回收器 - PO(ParallelOld) 老年代,并行回收a compacting collector that uses multiple GC threads. - PN(ParNew) 年轻代,配合CMS的并行回收,为PS的改版。PN+CMS响应时间优先,PS+PO吞吐量优先。 - CMS(ConcurrentMarkSweep) 老年代,并发回收,垃圾回收和应用程序同时运行,降低STW的时间,CMS既然是MarkSweep,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代分配对象分配不下的时候,使用SerialOld回收老年代,这是一个单线程垃圾收集器,当内存很大,几十甚至上百G时,造成程序卡死,GC回收好几天的现象。 - G1 算法:三色标记+SATB - ZGC 算法:ColoredPointers(染色指针) + LoadBarrier(读屏障) - Shenandoah 算法:BrooksPointers(转发指针) + LoadBarrier(读屏障) - Eplison DEBUG用的垃圾回收器,一般用不到 ### 常见垃圾回收器组合参数设定(1.8) - -XX:+UseSerialGC: Serial New (DefNew) + Serial Old - 小型程序,默认情况下不会是这种选项。 - -XX:+UseParNewGC: ParNew + SerialOld - 这个组合已经很少用(在某些版本中已经废弃) - -XX:+UseConc(urrent)MarkSweepGC: ParNew + CMS + Serial Old - -XX:+UseParallelOldGC: Parallel Scavenge + Parallel Old - -XX:+UseParallelGC: Parallel Scavenge + Parallel Old (1.8默认),可以指定老年代为SerialOld - -XX:+UseG1GC: G1 ## JVM调优 ### JVM常用命令行参数 HotSpot参数分类 ``` 标准: - 开头,所有的HotSpot都支持 非标准:-X 开头,特定版本HotSpot支持特定命令 不稳定:-XX 开头,下个版本可能取消 ``` - java -XX:+PrintCommandLineFlags 查看jvm默认参数 - -Xmn 设置新生代大小,-Xms初始堆大小,-Xmx最大堆大小,建议初始最大设置成一样的,防止弹性变化。 - -XX:+PrintGC 打印GC信息,-XX:+PrintGCDetails打印GC详细信息,-XX:+PrintGCTimeStamps打印GC时间,-XX:+PrintGCCauses打印GC的原因 - java -XX:+PrintFlagsInitial 默认参数值 - java -XX:+PrintFlagsFinal 最终参数值 - java -XX:+PrintFlagsFinal | grep xxx 找到对应的参数,如java -XX:+PrintFlagsFinal -version |grep GC ### PS GC日志详解 每种垃圾回收器的日志格式是不同的! PS日志格式 ![GC日志详解.png][6] heap dump部分: ``` eden space 5632K, 94% used [0x00000000ff980000,0x00000000ffeb3e28,0x00000000fff00000) 后面的内存地址指的是,起始地址,使用空间结束地址,整体空间结束地址 ``` ![GCHeapDump.png][7] total = eden + 1个survivor ### 调优前的基础概念 1. 吞吐量:用户代码时间 /(用户代码执行时间 + 垃圾回收时间) 2. 响应时间:STW越短,响应时间越好 所谓调优,首先确定,吞吐量优先,还是响应时间优先?还是在满足一定的响应时间的情况下,要求达到多大的吞吐量。 吞吐量优先:科学计算,数据挖掘,thrput。一般选择PS+PO 响应时间优先:网站,GUI,API。1.8以上选择G1 ### 调优的概念 1. 根据需求进行JVM规划和预调优 2. 优化JVM运行环境(慢,卡顿) 3. 解决JVM运行过程中出现的各种问题(OOM) ### 步骤 1. 熟悉业务场景(没有最好的垃圾回收器,只有最合适的垃圾回收器) 1. 要求响应时间、停顿时间 [CMS G1 ZGC] (需要给用户作响应) 2. 要求吞吐量[PS] 2. 选择回收器组合 3. 计算内存需求 4. 选定CPU(越高越好) 5. 设定年代大小、升级年龄 6. 设定日志参数 - -Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause (5个日志文件循环覆盖,每个20M,文件名为 创建时间.log) - 或者每天产生一个日志文件 7. 观察日志情况 ### 优化环境 1. 有一个50万PV的资料类网站(从磁盘提取文档到内存)原服务器32位,1.5G 的堆,用户反馈网站比较缓慢,因此公司决定升级,新的服务器为64位,16G 的堆内存,结果用户反馈卡顿十分严重,反而比以前效率更低了。 - 为什么原网站慢? 很多用户浏览数据,很多数据load到内存,内存不足,频繁GC,STW长,响应时间变慢 - 为什么会更卡顿? 内存越大,FGC时间越长 - 怎么办? PS+PO换成 PN + CMS 或者 G1 2. 系统CPU经常100%,如何调优? - CPU100%那么一定有线程在占用系统资源 - 找出哪个进程cpu高(top) - 该进程中的哪个线程cpu高(top -Hp) - 导出该线程的堆栈 (jstack) - 查找哪个方法(栈帧)消耗时间 (jstack) - 工作线程占比高还是垃圾回收线程占比高 3. 系统内存飙高,如何查找问题? - 导出堆内存 (jmap) - 分析 (jhat jvisualvm mat jprofiler ... ) 4. 如何监控JVM - jstat jvisualvm jprofiler arthas top... ### 解决JVM运行中的问题 1. top命令观察到问题:内存不断增长 CPU占用率居高不下 2. top -Hp 观察进程中的线程,哪个线程CPU和内存占比高 3. jps定位具体java进程 jstack 定位线程状况,重点关注:WAITING BLOCKED eg. waiting on <0x0000000088ca3310> (a java.lang.Object) 假如有一个进程中100个线程,很多线程都在waiting on ,一定要找到是哪个线程持有这把锁,搜索jstack dump的信息,看哪个线程持有这把锁RUNNABLE。 4. jinfo pid 查看进程详细信息,作用不大 5. jstat -gc 动态观察gc情况,jstat -gc 4655 500 : 每个500个毫秒打印GC的情况 6. jmap - histo 4655 | head -20,查找有多少对象产生,取前20条 7. jmap -dump:format=b,file=xxx pid 生成堆转储文件 - 线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿 - 设定HeapDumpOnOutOfMemoryError参数,OOM时候会自动产生堆转储文件 - 很多服务器备份(高可用),停掉这台服务器对其他服务器不影响 - 在线定位 8. -XX:+HeapDumpOnOutOfMemoryError OOM时候会自动产生堆转储文件 9. 使用MAT / jhat /jvisualvm 进行dump文件分析 10. 找到代码的问题 11. 栈溢出问题 -Xss设定太小 ### jconsole远程连接 1. 程序启动加入参数 ``` java -Djava.rmi.server.hostname=192.168.17.11 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=11111 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false XXX ``` 2. 如果遭遇 Local host name unknown:XXX的错误,修改/etc/hosts文件,把XXX加入进去 ``` 192.168.17.11 basic localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 ``` 3. 配置防火墙,放行设置的端口 4. windows上打开 jconsole远程连接 192.168.17.11:11111 也可以使用jvisualvm远程连接。 生产环境我们一般不用图形化界面远程连接,首先,需要配置一些远程参数,生产环境一般没有权限去配置,而且远程线程一直监听也会占用资源,一般只在上线之前测试的时候才会考虑图形化界面。 ### arthas在线排查工具 - 为什么需要在线排查? 在生产上我们经常会碰到一些不好排查的问题,例如线程安全问题,用最简单的threaddump或者heapdump不好查到问题原因。为了排查这些问题,有时我们会临时加一些日志,比如在一些关键的函数里打印出入参,然后重新打包发布,如果打了日志还是没找到问题,继续加日志,重新打包发布。对于上线流程复杂而且审核比较严的公司,从改代码到上线需要层层的流转,会大大影响问题排查的进度。 - jvm观察jvm信息 - thread定位线程问题 - dashboard 观察系统情况 - heapdump + jhat分析 - jad反编译 动态代理生成类的问题定位,第三方的类(观察代码),版本问题(确定自己最新提交的版本是不是被使用) - redefine 热替换 目前有些限制条件:只能改方法实现(方法已经运行完成),不能改方法名, 不能改属性 - sc - search class ### GC算法的基础概念 - Card Table 由于做YGC时,需要扫描整个OLD区,效率非常低,所以JVM设计了CardTable, 如果一个OLD区CardTable中有对象指向Y区,就将它设为Dirty,下次扫描时,只需要扫描Dirty Card。Card Table是用BitMap实现的 ### CMS CMS是一个老年代并发垃圾回收器,采用三色标记算法防止漏标 1. initial mark初始标记阶段,该阶段是STW的,CMS会标记GC roots对象 2. concurrent mark并发标记阶段,该阶段是并发执行的,不会影响工作线程 3. remark重新标记阶段,该阶段是STW的,用于对并发标记阶段发生改变的对象进行重新标记 4. concurrent sweep并发清除,该阶段是并发执行的,将标记为垃圾的对象清除 CMS的问题 Memory Fragmentation(内存碎片) ``` -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction 默认为0 指的是经过多少次FGC才进行压缩 ``` Floating Garbage(浮动垃圾) CMS并发清除阶段,因为工作线程依然在执行,所以会有新的垃圾产生,这些垃圾必须等到下次CMS执行时才会被回收,这些垃圾就是浮动垃圾。如果浮动垃圾产生过快,导致老年代没有足够的空间分配对象时,就会触发FGC,CMS的FGC是调用SerialOld执行的,当内存特别大的时候,导致停顿时间特别长。 解决方案: 降低CMS触发的阈值 PromotionFailed 解决方案类似,保持老年代有足够的空间 ``` –XX:CMSInitiatingOccupancyFraction 68% 可以降低这个值 ``` 当老年代的容量达到68%时,会触发CMS,降低该值,让CMS更早执行,给新对象保留出足够的空间。 ### G1 Carbage First Garbage Collector(G1 GC),垃圾优先,也就是说G1会优先回收那些垃圾最多的Region。G1是一种服务端应用使用的垃圾收集器,目标是多核、大内存的机器上,它在大多数情况下可以实现指定的GC暂停时间,同时还能保持较高的吞吐量。 G1在逻辑上分代,物理上不分代。它将内存分成一个一个的Region,每一个Region在逻辑上属于某一个分代,而且同一时刻只能属于某个分代,Region被回收之后,分代有可能会改变。年轻代、幸存区、老年代这些概念还存在,成为逻辑上的概念,这样方便复用之前的分代框架逻辑。在物理上不需要连续,则带来了额外的好处——有的分区内垃圾对象特别多,有的分区内垃圾对象很少,G1会优先回收垃圾对象特别多的分区,这样可以花费较少得到时间来回收这些分区的垃圾,这也就是G1名字的由来,即首先收集垃圾最多的分区。 新生代其实并不适用于这种算法,依然是在新生代满了的时候,对整个新生代进行回收——整个新生代中的对象,要么被回收、要么晋升,至于新生代也采取分区机制的原因,则是因为这样跟老年代的策略统一,方便调整代的大小。 G1还是一种带压缩的收集器,在回收老年代的分区时,是将存活的对象从一个分区拷贝到另一个可用分区,这个拷贝的过程就实现了局部的压缩。每个分区的大小从1M到32M不等,但是都是2的幂次方。 ![G1GC.png][8] 它有四种分代,Old(老年代),Eden,Survivor,Humongous(大对象区域) G1的特点 - 并发收集 - 压缩空闲时间不会延长GC的暂停时间 - 更易预测的GC暂停时间 - 适用于不需要实现很高吞吐量的场景 #### 基本概念 - CSet: Collection Set - 一组可被回收的分区集合,在CSet中存活的数据会在GC过程中被移动到另一个可用分区,CSet中的分区可以来自Eden空间、survivor空间或者老年代。CSet会占用不到整个堆空间的1%大小。简单理解为垃圾最多的card的表。 - RSet: RememberedSet - 记录了其他Region中的对象到本Region的引用。RSet的价值在于,使得垃圾收集器不需要扫描整个堆找到谁引用了当前分区中的对象,只需要扫描RSet即可。 G1新老年代的比例为5%-60%。一般不用手工指定,并且建议不要手工指定,因为这是G1预测停顿时间的基准。G1会跟踪每一次STW,动态改变年轻代的大小,以达到指定的停顿时间。 GC何时触发 - YGC - Eden空间不足 - FGC - Old空间不足 - System.gc() G1也会产生FGC,java10以前是串行FullGC,之后是并行FullGC,所谓G1的调优就是不要触发FGC,如果产生FGC,可以 - 扩内存 - 提高CPU性能(垃圾回收速度越快,内存空间越大) - 降低MixedGC触发的阈值,让MixedGC提早发生(默认是45%) #### MixedGC G1的MixedGC相当于一个CMS,过程为 - 初始标记 STW - 并发标记 - 最终标记 STW (重新标记) - 筛选回收 STW (并行) 区别于CMS,G1是筛选回收,也就是回收垃圾最多的分区。 #### 三色标记法 白色: 未被标记的对象 灰色: 自身被标记,成员变量未被标记 黑色: 自身和成员变量均已标记完成 什么时候会出现漏标 ![111.jpg][9] 如图,A已标记为黑色,B由于D还未标记,所以是灰色,此时,若A引用了D,B到D的引用删除了,那么由于A已经是黑色,不会重新标记,导致D被漏标,当做垃圾被清理,这是一个充分必要条件,A要指向D,B到D的引用删除,缺一不可。解决漏标,只要打破两个条件之一即可: 1. incremental update -- 增量更新,关注引用的增加,把黑色重新标记为灰色,下次重新扫描属性。CMS使用该方式 2. SATB (snapshot at the beginning) -- 关注引用的删除,当B->D消失时,要把这个引用推到GC的堆栈,保证D还能被GC扫描到。G1使用该方式 G1为什么会使用SATB?incremental update将对象重新标记为灰色,首先,GC并不知道哪些被标记为灰色,需要遍历所有对象,找到灰色的,然后还要对其中的所有成员变量进行重新标记,性能较低,而SATB将引用push到堆栈,只需要从堆栈中拿出这些引用,就可以定位到漏标的对象,由于有RSet的存在,不需要扫描整个堆去查找指向白色的引用,效率比较高。 ### 日志分析 **CMS** [GC (Allocation Failure) [ParNew: 6144K->640K(6144K), 0.0265885 secs] 6585K->2770K(19840K), 0.0268035 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] > ParNew:年轻代收集器 > 6144->640:收集前后的对比 > (6144):整个年轻代容量 > 6585 -> 2770:整个堆的情况 > (19840):整个堆大小 ``` [GC (CMS Initial Mark) [1 CMS-initial-mark: 8511K(13696K)] 9866K(19840K), 0.0040321 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] //8511 (13696) : 老年代使用(最大) //9866 (19840) : 整个堆使用(最大) [CMS-concurrent-mark-start] [CMS-concurrent-mark: 0.018/0.018 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] //这里的时间意义不大,因为是并发执行 [CMS-concurrent-preclean-start] [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] //标记Card为Dirty,也称为Card Marking [GC (CMS Final Remark) [YG occupancy: 1597 K (6144 K)][Rescan (parallel) , 0.0008396 secs][weak refs processing, 0.0000138 secs][class unloading, 0.0005404 secs][scrub symbol table, 0.0006169 secs][scrub string table, 0.0004903 secs][1 CMS-remark: 8511K(13696K)] 10108K(19840K), 0.0039567 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] //STW阶段,YG occupancy:年轻代占用及容量 //[Rescan (parallel):STW下的存活对象标记 //weak refs processing: 弱引用处理 //class unloading: 卸载用不到的class //scrub symbol(string) table: //cleaning up symbol and string tables which hold class-level metadata and //internalized string respectively //CMS-remark: 8511K(13696K): 阶段过后的老年代占用及容量 //10108K(19840K): 阶段过后的堆占用及容量 [CMS-concurrent-sweep-start] [CMS-concurrent-sweep: 0.005/0.005 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] //标记已经完成,进行并发清理 [CMS-concurrent-reset-start] [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] //重置内部结构,为下次GC做准备 ``` **G1** ``` [GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0015790 secs] //young -> 年轻代 Evacuation-> 复制存活对象 //initial-mark 混合回收的阶段,这里是YGC混合老年代回收 [Parallel Time: 1.5 ms, GC Workers: 1] //一个GC线程 [GC Worker Start (ms): 92635.7] [Ext Root Scanning (ms): 1.1] [Update RS (ms): 0.0] [Processed Buffers: 1] [Scan RS (ms): 0.0] [Code Root Scanning (ms): 0.0] [Object Copy (ms): 0.1] [Termination (ms): 0.0] [Termination Attempts: 1] [GC Worker Other (ms): 0.0] [GC Worker Total (ms): 1.2] [GC Worker End (ms): 92636.9] [Code Root Fixup: 0.0 ms] [Code Root Purge: 0.0 ms] [Clear CT: 0.0 ms] [Other: 0.1 ms] [Choose CSet: 0.0 ms] [Ref Proc: 0.0 ms] [Ref Enq: 0.0 ms] [Redirty Cards: 0.0 ms] [Humongous Register: 0.0 ms] [Humongous Reclaim: 0.0 ms] [Free CSet: 0.0 ms] [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)] [Times: user=0.00 sys=0.00, real=0.00 secs] //以下是混合回收其他阶段 [GC concurrent-root-region-scan-start] [GC concurrent-root-region-scan-end, 0.0000078 secs] [GC concurrent-mark-start] //无法evacuation,进行FGC [Full GC (Allocation Failure) 18M->18M(20M), 0.0719656 secs] [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)], [Metaspace: 38 76K->3876K(1056768K)] [Times: user=0.07 sys=0.00, real=0.07 secs] ``` ### GC常用参数 * -Xmn -Xms -Xmx -Xss 年轻代 最小堆 最大堆 栈空间 * -XX:+UseTLAB 使用TLAB,默认打开 * -XX:+PrintTLAB 打印TLAB的使用情况 * -XX:TLABSize 设置TLAB大小 * -XX:+DisableExplictGC 禁用System.gc() * -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 Eden与Survivor的比例 * -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的堆空间占用比例,达到时触发mixedGC,默认45% ### 问题 1. 如果采用ParNew + CMS组合,怎样做才能够让系统基本不产生FGC 1. 加大JVM内存 2. 加大Young的比例 3. 提高Y-O的年龄 4. 提高S区比例 5. 避免代码内存泄漏 2. 生产环境中能够随随便便的dump吗? 小堆影响不大,大堆会有服务暂停或卡顿(加live可以缓解),dump前会有FGC [1]: https://www.princelei.club/usr/uploads/2020/04/4047664105.png [2]: https://www.princelei.club/usr/uploads/2020/04/1977294775.png [3]: https://www.princelei.club/usr/uploads/2020/04/1131137435.png [4]: https://www.princelei.club/usr/uploads/2020/04/1853345265.png [5]: https://www.princelei.club/usr/uploads/2020/04/1448850093.jpg [6]: https://www.princelei.club/usr/uploads/2020/04/2598616662.png [7]: https://www.princelei.club/usr/uploads/2020/04/1522773367.png [8]: https://www.princelei.club/usr/uploads/2020/04/57913378.png [9]: https://www.princelei.club/usr/uploads/2020/04/1346900121.jpg Last modification:November 18th, 2020 at 09:18 pm © 允许规范转载