1. 垃圾回收算法
1.1 “标记-清除”算法
对内存对象进行可达性分析并标记,标记完成后统一回收。书中说有两个不足:①标记和清除效率不高;②内存空间是大量不连续的内存碎片。标记是每个回收算法都要经历的,而且在标记后,清除也是每个算法都要经历的,只是方式不同,所以我觉得标记和清除效率不高不是“标记-清除”算法的不足,而是每个算法都有,最主要的问题还是内存空间是大量不连续内存碎片。
1.2 “复制”算法
将内存空间分为两块,每次只使用其中一块内存空间。当内存空间满了,将还存活的对象复制到另一块内存空间,再将当前内存空间直接清空即可。由于新生代对象生存周期短,所以没必要1:1分配空间,常用的方式是将内存空间分为一块Eden和两块Survivor区域,之间比例是8:1:1,每次要垃圾回收时,将Eden和其中一个Survivor还存活的内存对象复制到另一块Survivor内存即可,然后将原先的Eden和Survivor清空即可。如果Survivor存不下要复制的内存对象,那么需要将多余的对象放入老年代,如果多余的对象小于老年代内存空间,那么此次的Minor GC垃圾回收方案是成功的;但如果多余的对象大于老年代内存空间,则要判断HandlePromotionFailure标志位是否允许分配担保,如果不允许,那么直接进行Full GC;如果允许,那么判断老年代剩余的内存空间是否大于历次进入老年代的内存对象,如果大于,那么会进行一次有风险的Minor GC,如果Minor GC失败了再进行Full GC。为了避免Full GC,一般允许分配担保。最主要的问题是对于生存周期长的对象来说,会复制多次,此时效率变低。
1.3 “标记-整理”算法
先标记所有存活的对象,然后让对象往内存空间一端移动,直接清除边界外的内存空间。
1.4 “分代”回收算法
年轻代对象生存周期短,那么使用复制算法;而老年代对象生存周期长,使用“标记-清除”或“标记-整理”算法。
2. 垃圾回收器
垃圾回收分为新生代和老年代回收,不同代有不同的回收器,先放一张《深入理解java虚拟机》书中回收器的搭配图,如下。
2.1 Serial回收器
新生代回收器,使用“复制”回收算法。单线程处理垃圾回收,并在处理垃圾时,暂停所有用户线程。优点是简单高效,对于内存空间几百兆来说,垃圾回收导致用户线程暂停时间可以控制在几十毫秒内,对用户的影响可接受。对应的老年代回收器是Serial Old,“标记-整理”回收算法。
在垃圾回收中,并行指有多条垃圾回收线程回收垃圾;而并发指垃圾回收线程和用户线程一起运行。
2.2 ParNew回收器
多线程版的Serial回收器(并行回收器),也是使用“复制”回收方式。还有一个特点就是它可以和CMS老年代回收器一起搭配使用。
2.3 Parallel Scavenge回收器
也是一个多线程版的“复制”算法回收器(并行回收器),唯一不同的是其它垃圾回收器是尽量缩短用户线程暂停的时间,而它的关注点是吞吐量,吞吐量=用户代码运行时间/(用户代码运行时间+垃圾收集时间)。提高吞吐量可以高效利用CPU。可通过控制最大垃圾回收停顿时间-XX:MaxGCPauseMills和吞吐量大小-XX:GCTimeRatio两个参数控制吞吐量。对应的老年代回收器是Parallel Old,“标记-整理”回收算法。
2.4 Concurrent Mark Sweep(CMS)回收器
多线程的老年代回收器(并行、并发回收器),采用“标记-清除”算法,以最短回收停顿时间为目标。主要有初始标记、并发标记、重新标记以及并发清除四个步骤。初始标记:标记GC Roots能直接关联的需;并发标记:进行GC Roots跟踪各个节点的过程;重新标记(并行阶段):修正并发标记期间用户程序更改的那些标记对象;由于最耗时的并发标记和并发清除可以与用户线程一起工作,所以整体回收使用户线程暂停的时间很短。缺点:①并发阶段会占用一部分CPU资源,导致吞吐量降低;②使用“标记-清除”算法,会有大量的内存碎片存在。③无法处理浮动垃圾,并发清除过程,用户线程产生的垃圾只能下次清理。
2.5 Garbage-First(G1)回收器
新生代(复制算法)、老年代(标记-整理)一体的回收器。使用G1回收器,堆内存被划分为多个大小(1~32MB)相等的区域(默认是2048个)。回收步骤:①初始标记;②并发标记;③最终标记;④筛选回收。前三个步骤与CMS一样,根据用户期望的GC停顿时间优先回收价值最大(回收所获得的空间大小以及回收所需时间)的内存区域。优点就是可以根据用户设定的GC停顿时间来制定区域的垃圾回收列表。
3. 内存分配
3.1 对象优先在新生代的Eden中分配内存
3.2大对象(长字符串或长数组)直接进入老年代
3.3 长期存活的对象将进入老年代:对象在Eden创建,经过Minor GC并能被Survivor容纳,将被移动到Survivor空间中,并且对象年龄设为1;然后再Survivor中每经历一次Minor GC,年龄就增加一岁,默认增加到15岁晋升到老年代,可通过-xx:MaxTenuringThreshold设置。
3.4年龄动态判定:如果Survivor空间中相同年龄对象总和大小超过Survivor空间的一半,那么年龄大于或等于该年龄的对象可进入老年代。