jvm之GC垃圾回收方面记录

1、垃圾回收机制算法

1)引用计数方式(已废弃)
	给每个对象设置一个引用值,当对象被引用的时候计数器+1,引用失效时计数器-1,当计数器为0的时候确认当前对象需要被销毁(垃圾回收)
2)引用链模式
	通过一些gcroots对象作为节点,查询向下的引用链,如果存在没有被引用链连接的对象,说明该对象需要被回收了
	GCroots包括 虚拟机栈中的引用对象(本地变量表)、方法区域中的类静态属性的引用、方法区常量的引用、本地方法JNI的对象引用(Native方法)

2、回收的区域

1)新生代(默认比例大小8:1:1)
	(1)Eden区:java新建对象绝大部分h会分配到eden区(如果对象过大直接分配老生代),当eden区内存不足时会触发gc
	(2)SurvivorFrom和To区:再GC开始的时候,对象只会存于Eden和from区,to区是空的,一次gc后存活的对象会移动到To区,并且年龄+1,如果年龄到15则存入老生代、每次gc完成后from和To的功能互换。
2)老生代:废弃常量,无用的类

3、回收算法

1)标记-清除算法
	在标记阶段就要标记所有需要被回收的对象,清除阶段紧随标记阶段,将标记阶段确认不可用的对象清楚。缺点:效率不高,且回收后存在大量不连续的空间
2)复制算法
	将内存分为大小相等的2分,每次只使用其中一份,当垃圾回收的时候,把存活的对象复制到另一份内存空间中,然后删除当前内存空间的所有对象。缺点:消耗大,而且造成内存使用率不高
3)标记-整理算法
	把存活的对象往内存的一端移动 然后回收收集内存另一端的对象
4)分代收集
	新生代的对象按照复制算法回收、老生代的对象按照标记整理的算法回收

4、收集器

新生代收集器

1)serial收集器(新生代收集器)
	单线程执行,使用复制算法、垃圾回收时必须暂停所有的工作线程、时默认的新生代收集器、对于单个cpu的环境来说、serial收集器没有线程交互,专心做垃圾收集有最高的效率
2)Parnew收集器(新生代收集器)
	就是serial收集器的多线程版本,可通过-XX:+UseConcMarkSweepGC或者-XX:+UsePerNewGC来强制开启,可通过-XX:ParallelGCThreads来调整或限制垃圾收集器线程的数量
3)parallel Scavenge收集器(新生代收集器)
	使用的也是复制算法,是一个并行的多线程收集器,他关注的时垃圾回收的吞吐量。可以通过-XX:MaxGCPauseMillis设置最大的垃圾回收暂停时间,通过-XX:GCTimeRatio设置吞吐量大小,并不是暂停时间越短越高效,暂停时间越短,收集的次数肯定会增加,吞吐量也下来了。
	-XX:GCTimeRatio参数是 垃圾收集时间占总时间的比率,默认值99,也就是最大允许1%的垃圾回收时间。。可以通过-XX:+UseAdaptiveSizePolicy开关开启自适应调节策略(就不需要手动指定新生代的大小和晋升老年代的年龄等参数了)

老年代收集器

4)serial old收集器
	就是serial收集器的老年代版本,也是单线程收集器,使用标记-整理算法,一般给client模式下虚拟机使用,如果再server模式 一种是搭配JDK1.5之前的parallelScavenge的新生代收集器一起使用,另一种就是作为CMS收集器的后备预案
5)Parallel Old收集器
就是parallel Scavenge收集器的老年代版本,使用多线程的标记-整理算法,JDK1.6之后才提供。再注重吞吐量的场合,优先使用parallel Scavenge搭配Parallel Old收集器来使用
6)CMS收集器
	是一种注重最短停顿时间为目标的收集器,一般用于大型互联网项目,注重服务响应时间的需求上,它使用的是标记-清除算法,主要分成4个阶段:初始标记、并发标记、重新标记、并发清楚
初始标记和并发标记还是需要暂停所有的工作线程
1、初始标记:标记一下gcroots能关联到的对象(时间排序2)
2、并发标记:就是gcroots追踪标记对象的过程(时间排序1)	
3、重新标记:为了修正程序运行期间导致标记变动的那部分对象(时间排序3)
4、并发清除
	该收集器无法清楚浮动垃圾 而可能出现“concurrent mode  failure”导致一次full gc的发生,因为他在并发清理阶段,程序还在运行,还在不断产生垃圾,这些垃圾只能等到下一次gc触发时在清理。可以通过设置-XX:CMSInitiatingOccupancyFraction 来设置触发gc 的百分比 ,所以还需要考虑程序运行的内存空间,,JDK1.6之后这个比列阈值达到了92%。如果设置的过高导致“concurrent mode  failure”的出现,他会启动后备预案执行SerialOld收集器,这样停顿时间就更长了就得不偿失了。
	因为CMS使用的时标记-清楚算法,每次清除完毕会有大量的不连续空间,无法分配大对象,所以提供了一个-XX:+UseCMSCompactAtFullCollection参数来触发FullGC时的内存碎片整理,这样一来碎片空间问题解决了,但是停顿时间变长了,所以还有一个参数-XX:CMSFullGCsBeforeCompaction来设置多少次fullGC执行一次碎片整理过程(默认是0,即每次都执行)
7)G1收集器:目前最好的垃圾回收器,好像是用于取代CMS有如下特点,
	与其他收集器不一样,G1收集器虽然还要老生代和新生代的概念,但是再物理上不进行区分了,他将整个java堆分成一系列的独立区域
	并行与并发:充分利用多个cpu来缩短暂停所有线程的时间、部分收集器原本需要停止的java线程来执行gc操作,G1任然可以通过并发的方式让java程序继续运行
	分代收集:不需要与其他收集器合并使用,可以单独管理整个gc
	空间整合:整体来看是标记-整理算法,从局部java堆独立区域来看是复制算法,这就说明了gc后不会产生大量的碎片空间,从而不会因为分配大对象找不到空间而触发下一次GC
	可预测的停顿时间:与cms一样G1关注停顿时间和响应速度,而且她还可以指定一个时间片段内消耗垃圾回收的时间不超过多少毫秒
G1的运作步骤:
1、初始标记(stop the world事件 CPU停顿只处理垃圾)
2、并发标记(与用户线程并发执行)
3、最终标记(stop the world事件,CPU停顿处理垃圾)
4、筛选回收(stop the world事件,根据用户期望的GC停顿事件回收(注意:cms在这一步不需要要stop the world))

G1的回收过程:
1、在垃圾回收的最开始有一个短暂的时间段会停止应用(stop the world)
2、然后应用继续运行,同时G1开始concurrent mark(并发标记)
3、再次停止应用,来一个FInal mark(stop the world)
4、最后根据Garbage First的原则(优先处理那些垃圾多的内存块),选择一些内存块进行回收(stop the world)

收集器总结:目前用的最多的还是parNew+CMS的模式,虽然这种算法会产生大量内存碎片 但是有个FULLGC的方式暴力解决。G1是一种比较完美的收集器,但是目前生成中用的还是比较少

G1垃圾收集器架构如何做到可预测的停顿
因为G1回收的第四步,他是“选择一些内存块”,而不是整代内存来回收,这是G1跟其他GC非常不同的一点,其他GC每次回收都会回收整个内存(Eden,old),而回收内存所需的时间就取决于内存的大小,以及实际垃圾的大小,所以垃圾回收的时间是不可控的,而G1每次并不会回收整代内存,到底回收多少内存就看用户配置的暂停时间,配置的时间短就少回收点,配置的时间长就多回收点,伸缩自如

5、jvm参数详解(常用类)

1)内存相关
-Xms 初始堆大小
-Xmx 最大堆大小
-Xmn 年轻代大小,jvm内存大小=年轻代大小+年老代大小+持久代大小,持久代一般为64M,建议配置为整个堆的3/8左右
-XX:newSize 新生代初始内存的大小,必须小于初始堆大小
-XX:NewRatio 设置年轻代和年老代的比值。比如为3.则年轻代:年老代=1:3
-XX:MaxNewSize 年轻代最大值
-XX:PermSize 持久代的初始值
-XX:MaxPermSize 持久代的最大值
-XX:SurvivorRatio 设置新生代Eden和survivor区的比值大小
-Xss 每个线程的堆栈大小
-XX:+DisableExplicitGC 关闭System.gc()

2)收集器相关
-XX:+UseParallelGC 选择垃圾收集器为并行收集器,仅对年轻代有效
-XX:+UseParNewGC 设置年轻代收集器parNew
-XX:ParallelGCThreads 设置parallel的并行收集器的线程数量(一般设置为cpu数量)
-XX:+UseParallelOldGC 设置老年代的并行收集器为Parallel Old
-XX:UseG1GC 使用G1收集器
-XX:MaxGCPauseMills 每次年轻代垃圾回收的最长时间(停顿时间)
-XX:GCTImeRatio 设置垃圾回收时间占用程序运行时间的百分比,1/(1+n)
-XX:MaxTenuringThreshold 设置垃圾的再新生代的最大年龄(超过就存入老年代)
-XX:+UseConcMarkSweepGC 使用CMS收集器
-XX:CMSInitiatingOccpuancyFraction 使用cms作为垃圾回收使用n%后开始CMS收集
-XX:CMSTruggerRatio (XX:CMSInitiatingOccpuancyFraction默认为-1,当此值不在0~100区间时由表达式计算)CMSInitiatingOccupancyFraction = (100 - MinHeapFreeRatio) + (CMSTriggerRatio * MinHeapFreeRatio / 100) 处罚cms收集的比例
-XX:CMSFullGCsBeforeCompaction 运行多少次FULLGC后做一次内存空间的压缩(碎片清理)
-XX:+UseCMSCompactAtFullCollection (与CMSFullGCsBeforeCompaction 搭配使用)
(附:-XX:CMSInitiatingOccupancyFraction和-XX:CMSTriggerRatio的区别:https://www.jianshu.com/p/61bf0e9011c4)

3)辅助信息相关
-Xloggc:filename 相关日志输出文件路径
-XX:+PrintGCDetails 打印GC详细信息
-XX:+PrintGCTimeStamps 输出GC的时间戳
-XX:+PrintHeadAtGC 在进行GC的前后打印堆的信息

6、jvm调优问题

1、年老代空间被占满java.lang.OutOfMemoryError: Java heap space

jvm内存=年轻代大小+年老代大小+持久代大小
原因:
	1)产期无法释放的对象运行时候导致对象数量增加引发内存泄漏。
    2)大并发加上大对象导致新生代survivor space区空间不足,大量对象进入老年代,年老代空间占满 GC Full也无法回收抛出ava.lang.OutOfMemoryError: Java heap space
解决方法:
(1)分析代码或者使用工具分析,找到内存泄漏点,进行改善
(2)增加Survivor Space的空间大小	

2、持久代被占满java.lang.OutOfMemoryError: PermGen space

原因:大量动态反射生成的类不断被加载,导致Perm区被占满
解决方案:
(1)增加持久代空间-XX:MaxPermSize=100M
(2)自定义类加载加载的排查下代码问题

3、堆栈溢出java.lang.StackOverflowError

原因:一般是循环问题导致
解决方案:修改代码问题

4、线程堆栈满Fatal: Stack size too small

原因:线程堆栈空间满了
解决方案:增加每个线程的堆栈空间 -Xss2M

5、系统内存被占满java.lang.OutOfMemoryError: unable to create new native thread

原因:操作系统没有足够的资源来产生线程了,
解决方案:
(1)重新设计代码减少创建线程的数量
(2)减少单个线程的堆栈空间-Xss,以便创建更多的线程

7、jvm优化方法

1)优化目标
        优化jvm 的目的就是为了满足高吞吐量、低延迟需求来优化gc

2)优化gc步骤
        1、观察目前垃圾回收的情况、分析老年代和新生代回收的情况、适当的去调整内存大小和 -XX:SurvivorRatio的比例
        2、根据垃圾回收器的特性、选择适合自己业务的垃圾回收机制、目前一般web服务的垃圾回收机制还是CMS+ParNew收集器。CMS收集器会产生大量的碎片空间,根据自己的需求选择相应的压缩频率即可
        3、不断的优化jvm内存比列、老生代:新生代:持久代的比例,调整到适合自己项目的一个完美值

3)优化总结
        总的来说GC优化不是加大内存就能解决的,要综合自身的业务特性和GC时间,减少新生代大小可以缩短新生代GC的停顿时间,因为复制到Survivor区域的数据更少、这样一来就提高了新生代的GC频率、而且有更多的垃圾进入老生代,如果增加新生代的大小又会导致GC的时间间隔和复制时间变高。所以一般需要接合自身业务来进行中间衡量
像是针对大部分数据都在Eden区域被回收、并且几乎没有对象被复制到survivor区域的场景、完全可以减少-XX:MaxTenuringThreshold参数,让数据更早的进入老生代、减少survivor区域的复制工作,来提高效率

猜你喜欢

转载自blog.csdn.net/qdboi/article/details/109291416