火影推荐程序连载13-JVM之垃圾回收机制(GC)

1.1 年轻代:分三个区。一个Eden区,两个Survivor区(from区和to区)。
大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。

1.2 年老代
在年轻代中经历了N次((ParNew默认15))垃圾回收后仍然存活的对象,就会被放到年老代中。年轻代放不下的大对象直接进入老年代。
tip1:对象动态年龄计算规则
虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold(默认15次)才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

1.3 持久代
用于存放静态文件,如今Java类、方法等
JDK1.8中,永久代已经从java堆中移除,String直接存放在堆中,类的元数据存储在meta space中,meta space占用外部内存,不占用堆内存。


-------------------------------------------------------------
二、GC回收算法
-------------------------------------------------------------


2.1 标记清除:标记阶段(标记从根节点开始的所有可达对象)和清除阶段 缺点:两个阶段效率都很低;回收后内存空间不连续,产生碎片多,易导致提前GC。

2.2 复制算法:内存等分两块,相互复制存活的对象后清洗垃圾 缺点:内存利用率低

2.3 标记压缩法:先标记,然后存活的向一段移动,清理存活端标记以外的内存。(老年代使用,无需需要第二块相同的内存) 优缺点:无内存碎片,但是耗时。

2.4 分代算法:复制算法(新生代) 标记压缩法和标记清楚法(老年代) 卡表(数据结构,一个比特位的集合),用来表示老年代对象是否持有新生代对象的引用,可以加快新生代回收的速度。

2.5 分区算法:将整个堆空间划分为连续不同的小的空间。

2.6 基本思想:对象的可触及性

2.6.1 可触及的
可复活的(finalize()函数)
不可触及的(finalize()函数只能调用一次)

2.6.2 引用和可触及的强度

2.6.2.1 强引用

2.6.2.2 软引用

2.6.2.3 弱引用

2.6.2.4 虚引用


-------------------------------------------------------------
三、分代垃圾回收
-------------------------------------------------------------


3.1 young代采用复制算法

3.2 old代使用标记清除或者标记清理

3.3 Tip:对象优先在Eden去分配,大的对象直接进入老年代,长期存活对象进入老年代。


-------------------------------------------------------------
四、垃圾回收器
-------------------------------------------------------------


4.1 串行收集器

单线程GC,启动时会停止应用,适用于配置小的服务器(1C2G),基本已弃用


4.2 并行收集器PS(吞吐量优先)

JDK1.6~1.8默认使用。垃圾线程并行,启动时应用会等待。适用于交互较弱的场景。吞吐量:花费在垃圾收集时间和花费在应用时间的占比。


4.3 并发收集器(响应时间优先)(并行GC前会额外触发新生代的GC)

JDK1.9默认使用G1。应用和垃圾并行。适用于对响应时间有要求的场景。响应时间:花费在垃圾收集时间和花费在应用时间的占比。


CMS(以获取最短回收停顿时间为目标的收集器,基于并发“标记清理”实现)

过程:1)初始标记 2)并发标记 3)预清理 4)重新标记 5)并发清除 6)并发重置

优点:并发收集、低停顿。

缺点:
1)CMS对CPU资源敏感。在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。
2)CMS无法处理浮动垃圾,可能会出现“Concurrent Mode Failure(并发模式故障)”失败而导致Full GC产生。
3)CMS容易出现大量空间碎片。当空间碎片过多,将会给大对象分配带来很大的麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。


G1(面向服务端应用的垃圾收集器)

过程:1)初始标记 2)根区域扫描 3)并发标记 4)重新标记 5)独占清理 6)并发清理

特点:
1、并行于并发:G1能充分利用CPU、多核,使用多个CPU来缩短stop-The-World停顿时间。
2、分代收集:虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。
3、空间整合:与CMS的“标记--清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。
4、可预测的停顿:这是G1相对于CMS的另一个大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内。


-------------------------------------------------------------
五、调优思路
-------------------------------------------------------------

  string e =www.qiaoheibpt.com "abc";
  
  //这里e还是使用字符串的留存性,且使用的还是a的地址。证明c分配的内存引用并没有放入常量池替换
  
  Assert.True(www.yachengyl.cn string.ReferenceEquals(www.letianhuanchao.cn a, e));
  
  Assert.False(www.feihongyul.cn string.ReferenceEquals(www.fengmingpt.com, e));
  
  string f www.baihuayl7.cn= "abc" + "abc";
  
  string g www.jintianxuesha.com= a +www.xinxingyulep.cn b;
  
  string h =www.jujinyule.com "abcabc";
  
  //f在编译期间确定,实际还是从常量池中获取
  
  //IsInterned 表示从常量池中获取对应的字符串,获取失败返回null
  
  //a+b实际上是发生了字符串组合运算,内部重新new了一个新的字符串,所以f,g引用地址不同
  
  Assert.False(string.ReferenceEquals(www.xinxingyulep.cn, g));
  
  Assert.True(string.ReferenceEquals(www.baihua178.cn string.IsInterned(www.lecaixuangj.cn), h));
  
  Assert.True(string.ReferenceEquals(www.yixinpt2.cn f,www.pinguo2yl.com h));
5.1 前瞻
1、尝试多种垃圾回收器,G1并不是最好的
2、并发不等于并行。垃圾回收的过程实际上有两步,启动GC周期和GC自身运行,这是不同的两件事。并发针对的是GC周期,而并行针对GC算法自身。
3、平均事务时间不是最需要被关注的指标,有可能用户正好经历了那个长时间GC的场景,那将是毁灭性的。
4、GC调优并不能解决所有的事。如果程序修改程度大,那应该优先优化架构及代码。
5、GC日志并不会对性能造成太大的影响,在GC未被优化之前,开启GC日志是有必要的。
6、降低新对象的分配率可以改善GC的运行状况。粗略地把系统中的对象分为三种:长命(long-lived)对象,对它们我们一般做不了什么;中等寿命(mid-lived)对象,最大的问题可能出现在这;短命(short-lived)对象,它们的释放和回收通常都很快,在下个GC周期来临时就会消失

5.2 思路
1、理解应用需求和问题。
2、掌握GC的状态。
3、思考选择的GC是否符合我们的应用特征。
4、分析确认需要调整的参数。
5、验证调优。

5.3 查看设置参数
java -XX:+PrintFlagsInitial <pid>查看初始值
-XX:+PrintFlagsFinal 查看最终值(初始值可能被修改掉)
-Xms 默认情况下堆内存的64分之一
-Xmx 默认情况下对内存的4分之一
-Xmn 默认情况下堆内存的3分之一
-XX:NewRatio 默认为2
-XX:SurvivorRatio 默认为8
-XX:+PrintGCDetails 开启GC详细日志-Xloggc:/cpic/cpicapp/perfma/xowl/../logs/xowl/gc.log -XX:+PrintGCDetails

5.4 GC一般合理表现
分析结果显示GC耗时在0.1-0.3秒以内的话,一般不需要花费额外的时间做GC调优。然而, 如果GC耗时达到1-3秒甚至10秒以上,就需要立即对系统进行GC调优 。
Minor GC执行迅速(50毫秒以内)
Minor GC执行不频繁(间隔10秒左右一次)
Full GC执行迅速(1秒以内)
Full GC执行不频繁(间隔10分钟左右一次)


-------------------------------------------------------------
六、参数调优
-------------------------------------------------------------


6.1 PS

6.1.1 吞吐量:-XX:GCTimeRatio=<N>垃圾收集时间与应用程序时间的比率设置为1/(1+<n>),默认值是99%(垃圾收集时间的1%)。
-Xmx<N>:指定最大堆占用空间。
优先级保证:暂停时间>吞吐量>堆空间。如果不设置初始堆内存和最大堆内存,则初始堆大小为物理内存的1/64,最大内存为1/4,年轻代大小为堆内存的1/3。
-Xms (初始堆内存) and -Xmx (最大堆内存):
如果知道应用程序需要多少堆才能正常工作,那么可以将-Xms和-Xmx设置为相同的值。如果不知道,那么JVM将首先使用初始堆大小,然后自动增长,直到它找到堆使用和性能之间的平衡。

6.2 G1

6.2.1 启用G1(常用):-XX:+UseG1GC
堆内存(常用):-XX:InitialHeapSize(初始堆内存)-XX:MaxHeapSize(最大堆内存)
年轻代设置(常用):-XX:NewSize(最小) -XX:MaxNewSize(最大)
暂停时间(常用):-XX:MaxGCPauseTimeMillis=<N>(默认200ms)
空闲堆占比:-XX:MinHeapFreeRatio=40(GC后,如果发现空闲堆内存占到整个预估堆内存的40%,则放大堆内存的预估最大值,但不超过固定最大值。)-XX:MaxHeapFreeRatio=70
最大暂停间隔时间:-XX:PauseTimeIntervalMillis
GC停顿时候的并行的GC收集线程数:-XX:ParallelGCThreads=< ergo>根据虚拟机所在的主机的可用CPU线程数来计算的:如果CPU少于8个这个值就是cpu的数量,否则,就等于cpu数量*5/8。每个停顿开始的时候,最大的GC线程数还受限于最大的堆内存,G1的内个线程能使用的最大堆内存是由-XX:HeapSizePerGCThread来设置的。
与应用并发执行的GC线程数:-XX:ConcGCThreads=< ergo>:默认是-XX:ParallelGCThreads/4
region的大小:-XX:G1HeapRegionSize=< ergo>整个堆大概有2048个region,region的大小可以在1-32M之间,必须是2的次方。调整之后会影响分配对象的大小及停顿时间。
可分配的最大对象的大小: -XX:G1HeapRegionSize-XX:G1MaxNewSizePercent


-------------------------------------------------------------
七、针对项目经验集
-------------------------------------------------------------


7.1 这几种GC收集器相比之下,只要JDK版本在1.7u4及以上,推荐使用G1收集器。JDK1.7,1.8都默认使用PS(并行收集器)

7.2 尤其注意docker项目,太保容器设置的JVM默认为PS收集器且堆大小为2G,如果容器内存大于3G,建议手动调整堆大小以及年轻代大小。

7.3 调优实例

7.3.1 实例一(集团2018版XXXXXXXX系统):
压测表现:稳定性压测时结果不稳定且内存消耗一直80%左右,影响时间3天。
内存分析表现:堆内存很大(7G)但年轻代内存非常小,年轻代minGC频繁,老年代内存一直增加直至触发majorGC,且GC暂停时间长,平均200~300ms
调优:调整年轻代内存为3G
export JAVA_OPTS="$JAVA_OPTS -Xmx7g -Xms7g -XX:NewSize=3g -XX:MaxNewSize=3g -XX:+UseG1GC"
优化结果:
复压,内存稳定在60%左右,年轻代GC频繁度减小,GC耗时100ms左右,老年代稳定无majorGC

7.3.2 实例二(寿险2013版XXXXXXXX系统):
压测表现:压测时压不上去,服务器消耗未满载。且压的时间长了TPS会有断崖式下降,TPS和相应时间非常不稳定。影响时间2天。
内存分析表现:heap内存只设置了2G,容器是3.5C7G,老年代一直增长直至触发majorGC,GC频繁且GC暂停时间不稳定。
调优:调整heap内存大小7G,新生代3G。
JAVA_OPTS="-Xmx7000m -Xms7000m -Xmn3072m -XX:PermSize=256M -XX:MaxPermSize=256M
调优结果:
TPS增长40~50且稳定。老年代稳定无majorGC。minorGC频繁度减小,GC暂停时间降低且稳定在几十ms以内。

7.3.3 Tip1:年轻代内存并不是越大越好,虽然会减小GC的频率,但是在GC时会增加回收时间造成GC暂停时间长。
Tip2:docker系统,如果容器内存4G,堆内存设置6G,进程可以启动且显示为6G,但实际只能使用到4G。
Tip3:如果开发和测试都不清楚如何设置堆大小及年轻代大小,可以参考perfma产品 http://xxfox.perfma.com/ ,填写相关参数会给出调优建议

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值