程序员的自我修养
Home » Java语言 » gc老生常谈

gc老生常谈

0条评论980次浏览

算是笔记吧。看了很多次的jvm、gc,但都没记住。这次遇到了自己写得线上服务出现了oom+死锁+频繁full gc(扶额),总算是记住了jvm和gc的基础知识。也是醉了。

jvm模型

主要分3个模块吧:堆、栈、本地方法栈。

堆=young(eden + survivor(from + to)) + tenured + permanent,也有说permanent不属于堆的,不过从gc日志来看我更倾向前者,但从gc参数来看应该是后者。

结合java8的gc来看:

young

par new generation就是新生代young,其大小等于eden区+from/to区,注意这里不是eden+from+to。为什么呢?因为你永远不能同时使用from和to。eden、from和to的比例默认是8:1:1,可以通过参数-XX:SurvivorRatio调节。

新对象的分配发生eden区域内,当新对象(非大对象)无法在该区域分配时便引发Minor GC。大对象放不下的时候直接去tenured区分配。Minor GC就是将eden+from/to的存活对象copy到to/from中去,顺带存活了若干次的会放到tenured区,而且放不下的也会去old区。

tenured

concurrent mark-sweep generation就是老年代tenured,这里因为gc用了CMS,所以才显示成concurrent mark-sweep generation的吧?young和tenured比例通过-XX:NewRatio调节。

前面提到的“大对象/放不下/Minor GC中存活若干次”是对象进入tenured的三种方式。当tenured发生垃圾回收的时候就是full gc。stop the world!Minor GC也是stop the world!

permanent

Metaspace应该是java8里面才这么叫?我记得以前好像不是叫Metaspace。反正意思就是permanent永久代。permanent区不够用的时候触发的也是full gc。

permanent存放了要加载的类信息、静态变量、final类型的常量、字符串常量、属性和方法信息。-XX:MaxPermSize可以调节大小,默认好像是64MB。

注意new String("xx")是放在eden区的!只有String a = "xx"才是放在该区域。

每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果。无限递归的时候都知道是什么错吧。

本地方法区

顾名思义,就是放一些native方法的地方,比如如果用netty,各种native方法就是放这里。

gc参数

Young

-XX:+UseSerialGC

-XX:+UseParallelGC

-XX:+UseParallelOldGC

-XX:+UseParNewGC

Tenured

-XX:+UseSerialGC

-XX:+UseParallelGC

-XX:+UseParallelOldGC

-XX:+UseConcMarkSweepGC

-Xmx 堆最大大小

-Xms 堆初始大小

-Xmn 控制eden(新生代)大小

-XX:SurvivorRatio 控制eden和survivor比例

-XX:NewRatio 控制young与tenured比例

-XX:+PrintGCDetails 打印gc信息

-XX:+PrintGCTimeStamps

-XX:+PrintGCDateStamps

-XX:+PrintHeapAtGC gc时打印heap信息

-Xloggc: gc日志路径

-XX:MaxPermSize permanent最大大小

-XX:PermSize permanent初始大小

gc算法

新生代:Serial、Parallel Scavenge、ParNew。老年代:Serial Old、Parallel Old、CMS。G1应该是没有新生代老年代的说法了,具体还没看。

Serial是单线程模式下用,目前基本淘汰了吧。Parallel和CMS比起来,网上有如下结论:使用CMS应用的吞吐量会相对下降,但有更好的最差响应时间。如果你的应用对峰值处理有要求,而对一两秒的停顿可以接受,则使用(-XX:+UseParallelGC);如果应用对响应有更高的要求,停顿最好小于一秒,则使用(-XX:+UseConcMarkSweepGC)。

新服务oom、死锁、频繁full gc的排查

结论在前:oom的原因是有线程池没有关掉;频繁full gc是一次性加入到线程池的任务太多;死锁是LinkedBlockingQueue为空但我却用take去取对象。

服务中用到了netty,NioEventLoopGroup就是线程池。代码中对于正确完成的任务进行了关闭NioEventLoopGroup的操作,但是抛异常的任务却没有关闭。最终导致NioEventLoopGroup对象过多,线上运行1个月左右就会oom。

查找手段:jmap -histo:live jid发现NioEventLoopGroup和ApnsClient对象不是1:1,从代码来看一个ApnsClient有一个属性NioEventLoopGroup,正常情况应该是1:1。jstack -l jid发现非常非常多的NioEventLoopGroup线程存在,而实际上ClientCache里面根本没有缓存那么多的ApnsClient。最终判断出NioEventLoopGroup对象没有正常被回收,最终定位到代码抛异常时没有关闭NioEventLoopGroup。

频繁full gc的查找过程类似,在发送巨型任务时jmap一次查看到占用内存最多的对象都是线程池相关的,基本就得出结论了。一个有意思的事情是占用内存最多的是[C对象和String对象,而我的项目里面没有new String的代码,这2个对象个数基本是1:1,用String的总bytes除以对象个数正好是24,不论何时jmap,结果都是24。至今没有弄明白,难道是每个进入线程池的任务有一个长度为24的String类型的id?

(转载本站文章请注明作者和出处 程序员的自我修养 – SelfUp.cn ,请勿用于任何商业用途)
分类:Java语言
标签:,
发表评论


profile
  • 文章总数:79篇
  • 评论总数:280条
  • 分类总数:31个
  • 标签总数:44个
  • 运行时间:1130天

大家好,欢迎来到selfup.cn。

这不是一个只谈技术的博客,这里记录我成长的点点滴滴,coding、riding and everthing!

最新评论
  • 张瑞昌: 有很多,比较常见的是Jacob迭代法,一次迭代O(n^3), 迭代次数不清楚。 如果是手动算的话按照定义求就可以了
  • Anonymous: :mrgreen:
  • lc277: 你好 我想问下一般删除节点要多久,要删除的datanode大概用了 1t,解除授权已经30多小时还没完成,请问是出现什么问题了吗 麻烦告诉下谢谢 qq1844554123
  • Anonymous: 你好 我想问下一般删除节点要多久,要删除的datanode大概用了 1t,解除授权已经30多小时还没完成,请问是出现什么问题了吗
  • Anonymous: :smile: :grin: :eek:
  • 李雪璇: 想要完整代码,可以帮忙发给我吗
  • Anonymous: 请问一下,那个 user的推荐结果楼主查看了么? 为什么输入数据 最高是五分,输出结果都是7分8分啥的?怎么设置输出的分数的最 大值?
  • Anonymous: 那个 user的推荐结果楼主查看了么? 为什么输入数据 最高是五分,输出结果都是7分8分啥的?
  • Anonymous: stopGracefullyOnShutdown在yarn- client模式下我测试的无效,你的呢
  • Anonymous: 另外,import的lib包能否发个列表.
  • Anonymous: 部分程序已经无法使用, 另外 你的import代码 也应该放上来哈
  • wzq: 赞
  • 增达网: 受教了!呵呵!
  • Anonymous: :!: :smile: :oops: :grin: :eek: :shock:
  • 27: :razz: dsa会报错,rsa不会
  • Anonymous: 看错了 忽略我
  • Anonymous: UserSideCF这个类在哪里
  • 晴子: 在主节点初始化CM5数据库的时候报错误:Verifying that we can write to /opt/cm-5.9.0/etc/cloudera-scm -server log4j:ERROR Could not...
  • zhangnew: 就4题 :?:
  • linxh: “ 但要是遇到预先并不知道数组的长度而又需要获取正确的(或者称之 为原始的)split长度时,该如何处理呢。。? ” 印象中可以split函数参数传-1?