Open heidsoft opened 6 years ago
http://aandds.com/blog/java-jvm.html http://tacy.github.io/blog/2014/03/24/hotspot-gc/ http://ifeve.com/useful-jvm-flags-part-8-gc-logging/ https://blog.csdn.net/iycynna_123/article/details/64444211 https://segmentfault.com/a/1190000013509330 http://alaric.iteye.com/blog/2263682 https://my.oschina.net/7001/blog/870988 https://juejin.im/post/5a9b811a6fb9a028e46e1c88 https://www.jianshu.com/p/0522dc5aeba6 https://blog.csdn.net/sinat_25306771/article/details/52374498 https://www.jianshu.com/p/a65af5c2514b
分析
当频繁full gc时,jstack打印出堆栈信息如下:
sudo -u admin -H /opt/taobao/java/bin/jstack `pgrep java` > #your file path#
可以看到的确是在跑低价信息
另外在应用频繁full gc时和应用正常时,也执行了如下2种命令:
sudo -u admin -H /opt/taobao/java/bin/jmap -histo `pgrep` > #your file path#
sudo -u admin -H /opt/taobao/java/bin/jmap -histo:live `pgrep` > #your file path#(live会产生full gc)
目的是确认以下2种信息:
(1)是否存在某些引用的不正常,造成对象始终可达而无法回收(Java中的内存泄漏)
(2)是否真是由于在频繁full gc时同时又有大量请求进入分配内存从而处理不过来,
造成concurrent mode failure?
下图是在应用正常情况下,jmap不加live,产生的histo信息:
下图是在应用正常情况下,jmap加live,产生的histo信息:
下图是在应用频繁full gc情况下,jmap不加live和加live,产生的histo信息:
从上述几个图中可以看到:
(1)在应用正常情况下,图中标红的对象是被回收的,因此不是内存泄漏问题
(2)在应用频繁full gc时,标红的对象即使加live也是未被回收的,因上就是在频繁full gc时,
同时又有大量请求进入分配内存从而处理不过来的问题
先从解决问题的角度,看怎样造成频繁的full gc?
从分析CMS GC开始
先给个CMS GC的概况:
(1)young gc
可以看到,当eden满时,young gc使用的是ParNew收集器
ParNew: 2230361K->129028K(2403008K), 0.2363650 secs解释:
1)2230361K->129028K,指回收前后eden+s1(或s2)大小
2)2403008K,指可用的young代的大小,即eden+s1(或s2)
3)0.2363650 secs,指消耗时间
2324774K->223451K(3975872K), 0.2366810 sec解释:
1)2335109K->140198K,指整个堆大小的变化
(heap=(young+old)+perm;young=eden+s1+s2;s1=s2=young/(survivor ratio+2))
2)0.2366810 sec,指消耗时间
[Times: user=0.60 sys=0.02, real=0.24 secs]解释:指用户时间,系统时间,真实时间
(2)cms gc
当使用CMS收集器时,当开始进行收集时,old代的收集过程如下所示:
a)首先jvm根据-XX:CMSInitiatingOccupancyFraction,-XX:+UseCMSInitiatingOccupancyOnly
来决定什么时间开始垃圾收集
b)如果设置了-XX:+UseCMSInitiatingOccupancyOnly,那么只有当old代占用确实达到了
-XX:CMSInitiatingOccupancyFraction参数所设定的比例时才会触发cms gc
c)如果没有设置-XX:+UseCMSInitiatingOccupancyOnly,那么系统会根据统计数据自行决定什么时候
触发cms gc;因此有时会遇到设置了80%比例才cms gc,但是50%时就已经触发了,就是因为这个参数
没有设置的原因
d)当cms gc开始时,首先的阶段是CMS-initial-mark,此阶段是初始标记阶段,是stop the world阶段,
因此此阶段标记的对象只是从root集最直接可达的对象
CMS-initial-mark:961330K(1572864K),指标记时,old代的已用空间和总空间
e)下一个阶段是CMS-concurrent-mark,此阶段是和应用线程并发执行的,所谓并发收集器指的就是这个,
主要作用是标记可达的对象
此阶段会打印2条日志:CMS-concurrent-mark-start,CMS-concurrent-mark
f)下一个阶段是CMS-concurrent-preclean,此阶段主要是进行一些预清理,因为标记和应用线程是并发执行的,
因此会有些对象的状态在标记后会改变,此阶段正是解决这个问题
因为之后的Rescan阶段也会stop the world,为了使暂停的时间尽可能的小,也需要preclean阶段先做一部分
工作以节省时间
此阶段会打印2条日志:CMS-concurrent-preclean-start,CMS-concurrent-preclean
g)下一阶段是CMS-concurrent-abortable-preclean阶段,加入此阶段的目的是使cms gc更加可控一些,
作用也是执行一些预清理,以减少Rescan阶段造成应用暂停的时间
此阶段涉及几个参数:
-XX:CMSMaxAbortablePrecleanTime:当abortable-preclean阶段执行达到这个时间时才会结束
-XX:CMSScheduleRemarkEdenSizeThreshold(默认2m):控制abortable-preclean阶段什么时候开始执行,
即当eden使用达到此值时,才会开始abortable-preclean阶段
-XX:CMSScheduleRemarkEdenPenetratio(默认50%):控制abortable-preclean阶段什么时候结束执行
此阶段会打印一些日志如下:
CMS-concurrent-abortable-preclean-start,CMS-concurrent-abortable-preclean,
CMS:abort preclean due to time XXX
h)再下一个阶段是第二个stop the world阶段了,即Rescan阶段,此阶段暂停应用线程,对对象进行重新扫描并
标记
YG occupancy:964861K(2403008K),指执行时young代的情况
CMS remark:961330K(1572864K),指执行时old代的情况
此外,还打印出了弱引用处理、类卸载等过程的耗时
i)再下一个阶段是CMS-concurrent-sweep,进行并发的垃圾清理
j)最后是CMS-concurrent-reset,为下一次cms gc重置相关数据结构
(3)full gc:
有2种情况会触发full gc,在full gc时,整个应用会暂停
a)concurrent-mode-failure:当cms gc正进行时,此时有新的对象要进行old代,但是old代空间不足造成的
b)promotion-failed:当进行young gc时,有部分young代对象仍然可用,但是S1或S2放不下,
因此需要放到old代,但此时old代空间无法容纳此
频繁full gc的原因
从日志中可以看出有大量的concurrent-mode-failure,因此正是当cms gc进行时,有新的对象要进行old代,
但是old代空间不足造成的full gc
进程的jvm参数如下所示:
影响cms gc时长及触发的参数是以下2个:
-XX:CMSMaxAbortablePrecleanTime=5000
-XX:CMSInitiatingOccupancyFraction=80
解决也是针对这两个参数来的
根本的原因是每次请求消耗的内存量过大
解决
(1)针对cms gc的触发阶段,调整-XX:CMSInitiatingOccupancyFraction=50,提早触发cms gc,就可以
缓解当old代达到80%,cms gc处理不完,从而造成concurrent mode failure引发full gc
(2)修改-XX:CMSMaxAbortablePrecleanTime=500,缩小CMS-concurrent-abortable-preclean阶段
的时间
(3)考虑到cms gc时不会进行compact,因此加入-XX:+UseCMSCompactAtFullCollection
(cms gc后会进行内存的compact)和-XX:CMSFullGCsBeforeCompaction=4
(在full gc4次后会进行compact)参数
但是运行了一段时间后,只不过时间更长了,又会出现频繁full gc
计算了一下heap各个代的大小(可以用jmap -heap查看):
total heap=young+old=4096m
perm:256m
young=s1+s2+eden=2560m
young avail=eden+s1=2133.375+213.3125=2346.6875m
s1=2560/(10+1+1)=213.3125m
s2=s1
eden=2133.375m
old=1536m
可以看到eden大于old,在极端情况下(young区的所有对象全都要进入到old时,就会触发full gc),
因此在应用频繁full gc时,很有可能old代是不够用的,因此想到将old代加大,young代减小
改成以下:
-Xmn1920m
新的各代大小:
total heap=young+old=4096m
perm:256m
young=s1+s2+eden=1920m
young avail=eden+s1=2133.375+213.3125=1760m
s1=1760/(10+1+1)=160m
s2=s1
eden=1600m
old=2176m
此时的eden小于old,可以缓解一些问题
改完之后,运行了2天,问题解决,未频繁报full gc
https://my.oschina.net/goldwave/blog/168516
https://www.reddit.com/r/docker/comments/7u06xo/docker_and_the_jvm_garbage_collector/ https://dzone.com/articles/how-to-decrease-jvm-memory-consumption-in-docker-u https://jaxenter.com/nobody-puts-java-container-139373.html https://www.youtube.com/watch?v=sJ-_htmU0TE&feature=youtu.be&t=20m17s https://developers.redhat.com/blog/2017/03/14/java-inside-docker/
Partial GC:并不会堆整个GC堆进行收集
young gc:只收集 young gen 的GC
old gc:只收集 old gen 的GC,只有CMS的 concurrent collection
mixed GC:收集整个 young gen 以及部分 old gen 的GC,只有G1
Full GC:收集整个堆,包括young gen、old gen、perm gen(如果存在的话)等
其实在各种文章或书上还可以看到Minor GC、Major GC的字眼,其中minor GC和young gc对应,而Major GC通常是和Full GC是等价的,由于HotSpot VM发展了这么多年,外界对各种名词的解读已经完全混乱了,所以Major GC有时也可能是指old gc,在下定论之前一定要先问清楚。
单线程、并行、并发
在GC收集器实现中,分为了单线程、并行和并发。
单线程收集器:如 Serial GC,这个比较好理解,即垃圾收集过程中只有单一线程在进行收集工作,实现也最简单。
并行收集器:如Parallel GC,每次运行时,不管是YGC,还是FGC,会 stop-the-world,暂停所有的用户线程,并采用多个线程同时进行垃圾收集。
并发收集器:如CMS GC,在新生代进行垃圾收集时和并行收集器类似,都是并行收集(当然具体算法中,你也可以设置成采用单线程进行收集),而且都会stop-the-world,主要的区别在于老年代的收集上,CMS在老年代进行垃圾收集时,大部分时间可以和用户线程并发执行的,只有小部分的时间stop-the-world,这就是它的优势,可以大大降低应用的暂停时间,当然也是有劣势的。
算法组合
Hotspot VM实现的几种GC算法组合中,其中CMS GC使用最广,因为现在都是大内存时代。
1、Serial GC
Serial generational collector (-XX:+UseSerialGC)
是全局范围的Full GC,这种算法组合是最早出现的,当年的Java堆内存大小都还不大,使用Serial GC进行单线程收集,还感觉不出来GC耗时导致应用暂停的问题
2、Parallel GC
Parallel for young space, serial for old space generational collector (-XX:+UseParallelGC).
Parallel for young and old space generational collector (-XX:+UseParallelOldGC)
当Java堆慢慢变大时,发现已经无法忍受GC耗时带来的应用暂停了,出现了Parallel GC,采用多线程的方式进行垃圾收集,很明显可以提升垃圾收集效率。
3、CMS GC
Concurrent mark sweep with serial young space collector (-XX:+UseConcMarkSweepGC
–XX:-UseParNewGC)
Concurrent mark sweep with parallel young space collector (-XX:+UseConcMarkSweepGC)
当Java堆达到更大时,比如8G,使用Parallel GC带来的应用暂停已经很明显了,所有又出现了 CMS GC,这是目前我看到线上环境使用的比较多的GC策略,在参数中添加-XX:+UseConcMarkSweepGC,对于 young gen,会自动选用 ParNewGC,不需要额外添加 -XX:+UseParNewGC。
CMS虽然好,因为它的特殊算法,大部分的收集过程可以和用户线程并发执行,大大降低应用的暂停时间,不过也会带来负面影响,在收集完 old gen 之后,CMS并不会做整理过程,会产生空间碎片,如果这些碎片空间得不到利用,就会造成空间的浪费,整个过程中可能发生 concurrent mode failure,导致一次真正意义的 full gc,采用单线程对整个堆(young+old+perm) 使用MSC(Mark-Sweep-Compact)进行收集,这个过程意味着很慢很慢很慢,而且这个碎片问题是无法预测的.
4、G1 GC
G1 garbage collector (-XX:+UseG1GC),本文不对G1进行介绍
触发条件
young gc
对于 young gc,触发条件似乎要简单很多,当 eden 区的内存不够时,就会触发young gc,我们看看在 eden 区给对象分配一块内存是怎样一个过程,画了一个简单的流程图,我一直觉得一个好的示意图可以让一个枯燥的过程变得更有意思。
在 eden 区分配空间内存不足时有两种情况,为对象分配内存、为TLAB分配内存,总之就是内存不够,需要进行一次 young gc 为eden区腾出空间为后续的内存申请做准备,然后由一个用户线程通知VM Thread,接下去要执行一次 young gc。
full gc
1、old gen 空间不足
当创建一个大对象、大数组时,eden 区不足以分配这么大的空间,会尝试在old gen 中分配,如果这时 old gen 空间也不足时,会触发 full gc,为了避免上述导致的 full gc,调优时应尽量让对象在 young gc 时就能够被回收,还有不要创建过大的对象和数组。
2、统计得到的 young gc 晋升到 old gen的对象平均总大小大于old gen 的剩余空间
当准备触发一次 young gc时,会判断这次 young gc 是否安全,这里所谓的安全是当前老年代的剩余空间可以容纳之前 young gc 晋升对象的平均大小,或者可以容纳 young gen 的全部对象,如果结果是不安全的,就不会执行这次 young gc,转而执行一次 full gc
3、perm gen 空间不足
如果有perm gen的话,当系统中要加载的类、反射的类和调用的方法较多,而且perm gen没有足够空间时,也会触发一次 full gc
4、ygc出现 promotion failure
promotion failure 发生在 young gc 阶段,即 cms 的 ParNewGC,当对象的gc年龄达到阈值时,或者 eden 的 to 区放不下时,会把该对象复制到 old gen,如果 old gen 空间不足时,会发生 promotion failure,并接下去触发full gc
在GC日志中,有时会看到 concurrent mode failure 关键字,这是因为什么原因导致的问题呢? 对这一块的理解,很多文章都是说因为 concurrent mode failure 导致触发full gc,其实应该反过来,是full gc 导致的 concurrent mode failure,在cms gc的算法实现中,通常说的cms是由一个后台线程定时触发的,默认每2秒检查一次old gen的内存使用率,当 old gen 的内存使用率达到-XX:CMSInitiatingOccupancyFraction设置的值时,会触发一次 cms gc,对 old gen 进行并发收集,而真正的 full gc 是通过 vm thread线程触发的,而且在判断当前ygc会失败的情况下触发full gc,如上一次ygc出现了promotion failure,如果执行 full gc 时,发现后台线程正在执行 cms gc,就会导致 concurrent mode failure。
对于以上这些情况,CMSInitiatingOccupancyFraction参数的设置就显得尤为重要,设置的太大的话,发生CMS时的剩余空间太小,在ygc的时候容易发生promotion failure,导致 concurrent mode failure 发生的概率就增大,如果设置太小的话,会导致 cms gc 的频率会增加,所以需要根据应用的需求对该参数进行调优。
5、执行 System.gc()、jmap -histo:live <pid>、jmap -dump ...
参考资料
Major GC和Full GC的区别是什么?触发条件呢
作者:占小狼
链接:https://www.jianshu.com/p/2750c7c202ef
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
-XX:+UseParallelGC 开启此参数使用parallel scavenge & parallel old搜集器(server模式默认值) -XX:+UseParallelOldGC 开启此参数在年老代使用parallel old搜集器 -XX:ParallelGCThreads=4 回收时开启的线程数。默认与CPU个数相等。
The repository is linked on http://openjdk.java.net/. Clone it and execute theget_source.sh script.
$ hg clone http://hg.openjdk.java.net/jdk8/jdk8
$ cd jdk8 && sh get_source.sh
hg clone http://hg.openjdk.java.net/jdk10/jdk10 && cd jdk10 && chmod +x get_source.sh && ./get_source.sh
hg clone http://hg.openjdk.java.net/jdk8/jdk8 && cd jdk8 && chmod +x get_source.sh && ./get_source.sh
hg clone http://hg.openjdk.java.net/jdk9/jdk9 && cd jdk9 && chmod +x get_source.sh && ./get_source.sh
java -XshowSettings:vm -version 显示设置
https://stackoverflow.com/questions/2129044/java-heap-terminology-young-old-and-permanent-generations https://juejin.im/post/5a9b811a6fb9a028e46e1c88 https://stackoverflow.com/questions/40672443/java-process-memory-usage-keeps-increasing-infinitely https://arhipov.blogspot.com/2011/01/java-bytecode-fundamentals.html https://www.toptal.com/java/hunting-memory-leaks-in-java
https://www.pushtechnology.com/support/kb/understanding-the-java-virtual-machine-heap-for-high-performance-applications/ https://codeahoy.com/2017/08/06/basics-of-java-garbage-collection/ http://onemogin.com/java/gc/java-gc-tuning-generational.html http://jprante.github.io/2012/11/28/Elasticsearch-Java-Virtual-Machine-settings-explained.html https://www.infoq.cn/article/Secrets-of-the-Bytecode-Ninjas
https://gist.github.com/bossiernesto/ccb3a847e83ae0ddf7db0b0eae30870f https://xuxinkun.github.io/2016/05/16/memory-monitor-with-cgroup/ https://docs.docker.com/config/containers/runmetrics/ https://www.cnblogs.com/duanxz/p/10247494.html https://unix.stackexchange.com/questions/17936/setting-proc-sys-vm-drop-caches-to-clear-cache https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/resource_management_guide/sec-memory http://lovestblog.cn/blog/2016/07/20/jstat/ https://www.zybuluo.com/zero1036/note/872396 https://crunchify.com/jvm-tuning-heapsize-stacksize-garbage-collection-fundamental/ https://skorks.com/2010/03/how-to-quickly-generate-a-large-file-on-the-command-line-with-linux/ https://www.cyberciti.biz/faq/howto-create-lage-files-with-dd-command/ https://atbug.com/java8-metaspace-size-issue/ http://lovestblog.cn/blog/2016/10/29/metaspace/ https://www.cnblogs.com/yjd_hycf_space/p/7755633.html https://www.kernel.org/doc/Documentation/sysctl/vm.txt https://unix.stackexchange.com/questions/253816/restrict-size-of-buffer-cache-in-linux https://stackpointer.io/unix/linux-clear-memory-cache/403/ https://www.dynatrace.com/news/blog/how-to-identify-a-java-memory-leak/ https://www.journaldev.com/4098/java-heap-space-vs-stack-memory https://blogs.oracle.com/jonthecollector/presenting-the-permanent-generation https://betsol.com/2017/06/java-memory-management-for-java-virtual-machine-jvm/ https://stackoverflow.com/questions/31257968/how-to-access-jmx-interface-in-docker-from-outside https://www.quora.com/How-does-memory-management-work-in-Java https://zhanjindong.com/2016/03/02/jvm-memory-tunning-notes https://zhanjindong.com/2015/12/13/thinking-about-high-performance-web-service https://www.ibm.com/developerworks/linux/library/j-nativememory-linux/ https://stackoverflow.com/questions/38153381/how-to-debug-leak-in-native-memory-on-jvm https://www.baeldung.com/native-memory-tracking-in-jvm
https://www.cnblogs.com/peida/archive/2012/12/31/2840241.html http://s0docs0docker0com.icopy.site/config/containers/runmetrics/ https://www.cnblogs.com/youxin/p/4744652.html https://www.binarytides.com/linux-netstat-command-examples/ https://www.cyberciti.biz/tips/linux-investigate-sockets-network-connections.html https://www.cnblogs.com/duanxz/p/6115722.html https://docs.oracle.com/en/java/javase/12/vm/native-memory-tracking.html#GUID-710CAEA1-7C6D-4D80-AB0C-B0958E329407 https://qsli.github.io/2017/12/02/google-perf-tools/ https://coldwalker.com/2018/08//troubleshooter_native_memory_increase/ https://yq.aliyun.com/articles/657790 https://www.bbsmax.com/R/MyJxYAmM5n/ https://cloud.tencent.com/developer/article/1176832 https://www.cnblogs.com/zhaoyl/p/5515317.html https://blog.csdn.net/ma_mxr/article/details/87686922 https://kkewwei.github.io/elasticsearch_learning/2016/12/20/gdb%E8%B0%83%E8%AF%95java%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95/ https://gist.github.com/miguno/548cd72eaec017c475448cb9b2ced258 https://stackoverflow.com/questions/6637448/how-to-find-the-address-of-a-string-in-memory-using-gdb http://openinx.github.io/2019/02/23/netty-memory-management/ https://caorong.github.io/2016/08/27/netty-hole/ https://stackoverflow.com/questions/41300520/what-is-locked-ownable-synchronizers-in-thread-dump https://blog.csdn.net/ztguang/article/details/51015758 https://fangjian0423.github.io/2016/06/04/java-thread-state/ https://blog.jrwang.me/2016/java-thread-states/ https://www.cnblogs.com/charlieroro/p/10180827.html
DefNew 与 ParNew
HotSpot VM的开发历史
内存导出日志