当进程启动后会创建垃圾回收线程,来回收内存中无用的对象
1.垃圾回收的时机
(1)System.gc()
显示的调用此方法, 建议JVM进行fgc(full gc),虽然只是建议而非一定,但多半都会进行fgc, 增加fgc的频率,所以一般不用此方法,而是让JVM自己管理它的内存
(2)jvm 垃圾回收机制决定
创建对象需要分配内存, 当内存不足时 出发GC;
finalize(),java.lang.Object中的方法,当JVM发现不在有引用指向某对象时,垃圾收集器会在对象上调用该方法
2.垃圾回收策略—如何判断对象可回收
2.1引用计数算法
给对象添加一个引用计数器,当有引用指向该对象时,计数器加1 ,当引用失效时,计数器值减一, 当值为0时,说明没有引用指向该对象. 该算法实现简单,效率高,但它很难解决对象之间循环引用的问题
2.2可达性算法
把一系列称为GC Roots的对象作为起始点,从这些起始点向下搜索,搜索走过的路径称为GC Roots引用链,当对象到GC Roots 没有任何引用链相连(从GC Roots到这个对象不可达)时,说明该对象是不可用的。
如下图,object5 和object6 到GC Roots是不可达的,因此被判定为可回收对象
3.需要垃圾回收的内存划分
3.1方法区(JDK1.7)或元空间(JDK1.8)
JDK1.7的方法区在GC中被称为永久代
JDK1.8的元空间在本地内存中,GC也是对元空间的垃圾回收
主要回收废弃的常量和无用的类, 此区域GC的"性价比"很低
3.2堆
java堆是垃圾收集器主要管理区域,也被称为"GC堆";
由于垃圾收集器 分代 采用不同的收集算法, 所以 java堆可细分为:
(1)新生代(Young Generation) 又可分为Eden ,from surviver ,to surviver
发生在新生代的GC 称为young GC(YGC) 或Minor GC ,因为java对象大多具备朝生夕灭的特性,所以manor GC发生的很频繁,而且回收速度很快.
(2)老年代(old Generation) 发生在老年代的GC被称为Major GC.出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的回收速度比Manor GC的速度要慢10倍左右.
(3)Full GC 在不同的语义中,定义也不同,可以是老年代GC,可以是全堆(老年代+新生代),也可以指用户线程暂停(stop-the-world)的垃圾回收.
4.垃圾回收算法
4.1标记–清除算法(Mark Sweep)
老年代收集算法.
分为标记和 清除 两个阶段,先把所有回收对象标记上,在统一清理,
不足:①效率不高,② 会产生大量不连续的内存碎片,导致后续为大对象分配内存时,没有足够的连续的内存,就会提前触发垃圾收集动作
4.2标记–整理算法(Mark Compact)
老年代收集算法
和"标记–清除算法"过程一致,但清除阶段不是直接清除,而是把存活对象移动到一端,然后清除掉边界以外的内存
4.3复制算法(Copying)
新生代收集算法
把内存按容量分为两块大小相等的区域, 每次只用一块儿,当这一块要用完了就把所有存活对象复制到另一块上去,然后把这一块儿全部清理掉.
实现简单,效率高
不足:内存利用率太低了
5.垃圾回收过程
当eden空间不足时,会触发Manor GC:(对象优先在Eden上分配),把Eden和用过得Surviver中存活的对象复制到另一块Surviver上,然后把Eden和之前那块Surviver清理掉
年老对象进入到老年代: JVM给对象定义一个对象年龄计数器,当对象经过一次Manor GC后仍然存活,那么年龄加一,当增加到一定值后(默认15),就进入到老年代.
当surviver内存不足时,通过分配担保机制,存活对象进入到老年代.
6.垃圾收集的影响
6.1用户线程暂停–stop-the-world (STW)
垃圾回收工作要在垃圾回收线程里进行,所以,多数时候,当进行垃圾回收时,或进行期中一步骤时,会暂停用户线程.
垃圾回收需要先标记,在标记对象时,用户线程并发执行时,可能把已标记的对象重新加入引用链,此时,再回收这个对象就会产生问题.
所以用户线程会在特定安全点停下来开始GC;
①抢先试中断:不需要线程的代码主动配合,而是在发生GC时把所有线程中断,若不在安全点,就恢复线程,让它跑到安全点.(此方法用的极少)
②主动式中断:在执行GC时,会设置一个标志,线程主动去轮询这个标志,当中断标志为真时就自己中断挂起, 轮询标志的地方和安全点是重合的.
6.2评价垃圾回收器的指标----吞吐量和停顿时间(用户体验)
吞吐量:CPU用于运行用户代码的时间 除以 CPU总的运行时间 , 即吞吐量=运行用户代码时间/
(运行用户代码时间+垃圾收集时间)
单次停顿时间与总的停顿时间一般是反比
用户体验优先:用户线程单次停顿时间短,即使总的停顿时间长一点也可以接受。
吞吐量优先:用户线程总的停顿时间短,即使单次停顿时间长一点也可以接受。
7.垃圾收集器
1.CMS收集器(老年代收集器,并发GC)
特性:
并发收集、低停顿
“标记-清除”算法
整个过程分为4个步骤:
- 初始标记(CMS initial mark) 初始标记仅仅只是标记一下GC Roots能直接关联到的对象,
速度很快, 需要“Stop The World”。 - 并发标记(CMS concurrent mark) 并发标记阶段就是进行GC Roots Tracing的过程。
- 重新标记(CMS remark) 重新标记阶段是为了修正并发标记期间因用户程序继续运作而导
致标记产生 变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍
长一些,但远比并发标 记的时间短,仍然需要“Stop The World”。 - 并发清除(CMS concurrent sweep) 并发清除阶段会清除对象。
缺陷:
①抢占CPU资源
②无法处理浮动垃圾:由于并发清理过程,用户线程也在执行,所以会产生对象和垃圾, 而产生的垃圾没有标记 ,需要累积到下一次CMS集中处理,所以,需要预留一部分空间给并发执行的程序. 如果在清除阶段,内存不足,就会临时调用单线程的Serial Old收集器来重新进行老年代的垃圾收集,这样的话,CMS原本降低停顿时间的目的不仅没完成,和直接使用Serial Old收集器相比,还增加了前面几个阶段的停顿时间。
③会产生不连续的空间碎片—由"标记-清除"的算法决定.
2 .G1收集器
用户体验优先
把heap区分为很多region块.然后并行对其垃圾回收
无论如何,G1收集器采用的算法都意味着 一个region有可能属于Eden,Survivor或者Tenured
内存区域。
年轻代垃圾收集
在G1垃圾收集器中,年轻代的垃圾回收过程使用复制算法。把Eden区和Survivor区的对象复制到新的
Survivor区域。老年代垃收集
对于老年代上的垃圾收集,G1垃圾收集器也分为4个阶段,基本跟CMS垃圾收集器一样,但略有不同:
初始标记:需要暂停用户线程,同Manor GC一起执行,
并发标记: 发现哪些Tenured region中对象的存活率很小或者基本没有对象存活,那么G1就会在这个阶段将其回收掉,而不用等到后面的清除阶段.
最终标记(CMS中的Remark阶段) - 在这个阶段G1做的事情跟CMS一样, 但是采用的算法不
同,G1采用一种叫做SATB(snapshot-at-the-begining)的算法能够在Remark阶段更快的标
记可达对象。
筛选回收(Clean up/Copy)阶段 - 在G1中,没有CMS中对应的Sweep阶段。相反 它有一个
Clean up/Copy阶段,在这个阶段中,G1会挑选出那些对象存活率低的region进行回收,这个
阶段也是和minor gc一同发生的,如下图所示: