java gc
jvm参数配置
OPTS_MEMORY="-Xms4096M -Xmx4096M -Xmn2048M -XX:MaxPermSize=256M -XX:+UseConcMarkSweepGC -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=60 -XX:+CMSClassUnloadingEnabled -XX:ParallelGCThreads=2 -XX:ConcGCThreads=2 -XX:+CMSParallelRemarkEnabled -XX:+CMSIncrementalMode -XX:CMSFullGCsBeforeCompaction=5 -XX:+CMSIncrementalPacing -XX:CMSIncrementalDutyCycleMin=0 -XX:CMSIncrementalDutyCycle=10 -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -verbose:gc -Xloggc:/export/Logs/prommanasyst-service.jd.local/o2o-prommanasyst-gc.log"
survivor区的意义
没有survicor,eden区每进行一次minor gc,存活对象进入老年代,老年代很快就被填充满了,触发major gc,老年代的内存空间远大于新生代,进行一次full gc消耗远大于minor gc,此时如果增大老年代,full gc时间更长,如果减小老年代,full gc更频繁,所以survivor存在的意义,就是减少被送到老年代的对象,减少full gc,survivor筛选只有经历16次的minor gc还能在新生代存活的对象,才进入老年代。
设置两个survivor,解决碎片化,eden,survivor满了触发minor gc,两个区各有存活的对象,如果把eden区的存活对象放在survivor区,很明显两个区的内存都是不连续的,导致碎片化,第一次eden满了,存活对象进入sur0区,eden被清空,等eden再满了,再触发gc,eden,sur0的存活对象被复制到s1(保证s1中来自s0和eden的存活对象占用连续的内存空间,避免碎片化),eden,sur0被清空,下一轮s0,s1调换角色,循环往复,如果对象达到16次,进入老年代,最大的好处就是有一个s区是空的,一个是无碎片的。
对象的存活判定
gc Root:虚拟机栈中的引用变量,方法区中类静态属性引用的对象,方法区中常量引用的对象,本地方法栈中的JNI引用的对象。
可达分析算法,Gc Root作为起始点,从这些节点开始往下搜索,搜索所走过的路径称为引用链,当一个对象到达gc root没有任何引用链相连时,对象不可用。
没有引用链也不是非死不可,要宣告一个对象死亡,至少需要经历两次标记过程,如果没有引用链,它会被第一次标记并且进行一次筛选,筛选的条件是对象是否有必要执行finalize方法,如果没有覆盖此方法或者此方法被虚拟机调用过,虚拟机进行对象的回收;如果覆盖了此方法,对象会被放在一个F-queue的队列中,稍后由一个虚拟机自动建立,低优先级的finalizer线程去对F-queue进行小范围的标记,如果对象要自我拯救,只要重新和引用链的任何一个对象建立关联关系即可,如果还是不存在引用链就要被回收了
gc收集器
并行:多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态
并发:用户线程,垃圾收集器线程同时执行(不一定并行,可能交替执行)
parNew:多线程的serial并行收集器,-xx:+useParNewGc来指定,同时因为和旧生代并发cms收集器的唯一可合作的新生代收集器当设置-xx:useConcMarkSweepGc选项后parnew也可以默认为新生代收集器,同时可以通过-xx:parallelGCThreads参数来限制垃圾收集器的线程数
cms:(concurrent mark sweep)旧生代并发收集器,采用标记清除算法,过程分为四步
1.初始标记:标记下gc root能直接关联到的对象,速度很快,stop the world
2.并发标记:gc root tracing
3.重新标记:修正并发标记期间因用户程序继续运行导致标记产生变动的那一部分对象的标记记录,stop the world
4.并发清除:开始回收
由于耗时最长的并发标记和并发清除过程收集器线程都能与用户线程一起工作,所以总体说cms是并发的,cms会占用一部分线程,导致吞吐量下降,cms默认启动回收线程数是(cpu数量+3)/4
cms缺陷:同时cms无法处理浮动垃圾,并发清理时,程序有新的垃圾产生,只能在下次gc在清理,这时要留足够的内存空间给用户线程,所以cms不能等老年代满了再回收,默认68%,用-XX:CMSFullGCsBeforeCompaction设置多少次full gc后,来一次压缩(默认为0),每次full gc都压缩。
G1收集器:并发清除,基于标记整理算法,不会产生浮动垃圾,也是基于分代回收【将java堆划分为多个大小相等的独立区域,新生代和老年代不再物理隔离】,但是不需要和其他收集器配合就可以独立管理整个GC堆,在回收的时候可以建立可预测时间停顿模型【因为他可以有计划的避免在整个java堆中进行区区域的垃圾收集,根据划分的每个独立区域region里面的垃圾堆积的价值大小(回收所获得的空间和时间)在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的region】
在G1中,还有一种特殊的区域,叫Humongous区域。 如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。
G1提供了两种GC模式,Young GC和Mixed GC,两种都是Stop The World(STW)的。下面我们将分别介绍一下这2种模式。
-
G1 Young GC
Young GC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。在这种情况下,Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间。Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。最终Eden空间的数据为空,GC停止工作,应用线程继续执行。
-
G1 Mixed GC
reference:https://www.cnblogs.com/sidesky/p/10797382.html
gc收集算法
1.标记清除:标记,清除的效率不高,产生碎片,导致分配较大对象没有连续内存,频繁触发gc。
2.copying:新生代收集算法,将内存划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将存活的对象复制到另外一块内存上,然后清理掉已使用的内存,不用考虑碎片,内存分配的时候只要移动堆顶指针,按顺序分配内存就可以,但是缺点是内存变成以前的一半了,不好。因为现在新生代的对象一般98%的都会死亡,所以不需要1:1的比例分配内存空间,而是将内存分配为8:1:1的3块区域,每次只使用一块eden+s区,这样只有10%的空间浪费,同时我们也无法保障每次都有少于10%的对象存活,这时当存活对象大于s区大小时,需要依赖老年代进行分配担保,这些对象直接全部进入老年代。
3.标记整理:旧生代的收集算法,旧生代不能使用copying算法是没有内存空间可以分配担保。和标记清除一样,但是后续步骤不是直接对可回收的对象进行清理,而是让所有活动对象向一端移动,然后清理掉端边界以外的内存。
发生full gc的情况
1.System.gc()
2.持久代不足
3.survivor放不下,大对象直接进入老年代,老年代也放不下
4.新生代对象进入老年代,老年代的连续内存空间不够
jdk命令行 - jmap(查看内存),jstack(查看线程),jstat(查看性能)
从应用程序启动到采样时young gc发生了3428次,用时172s,平均50ms
从应用程序启动到采样时full gc发生3次,用时59s,平均20s
Minor GC执行非常迅速(50ms以内)
Minor GC没有频繁执行(大约10s执行一次)
Full GC执行非常迅速(1s以内)
Full GC没有频繁执行(大约10min执行一次)
明显上面的gc情况不正常,jmap -heap查看堆的情况和gc算法等
或者可以使用jmap–histo:live 打印类存活的对象,看看什么对象大量产生,导致gc
jstack分析死锁:http://www.cnblogs.com/zhengyun_ustc/archive/2013/01/06/dumpanalysis.html
jvm命令汇总:https://www.cnblogs.com/therunningfish/p/5524238.html