如何判定对象为垃圾对象
引用计数法 – 一般不采用
在对象中添加一个引用计算器,被引用一次计数器值就加 1;当引用失效时,计数器值就减 1;计数器为 0 时,对象就是不可能再被使用的,简单高效,缺点是无法解决对象之间相互循环引用的问题。
-XX:+PrintGCDetails --查看垃圾回收器
package MyDemo.work;
public class Main {
private Object instance;
public Main() {
byte[] m = new byte[20 * 1024 * 1024];
}
public static void main(String[] args) {
Main m1 = new Main();
Main m2 = new Main();
//m1,m2循环引用
m1.instance = m2;
m2.instance = m1;
//断开m1,m2循环引用
m1 = null;
m2 = null;
/*
最后面两句将 m1 和 m2 赋值为 null,也就是说 m1 和 m2 指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数器都不为 0,那么垃圾收集器就永远不会回收它们。
*/
System.gc();
}
}
可达性分析法 – 主要使用
根搜索算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点 GC ROOT
开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。
如上图中的 ObjF
、ObjD
、ObjE
通过 GC Root
是无法找到的,所以它们是无用节点。
根搜索算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点 GC ROOT
开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。
如上图中的 ObjF
、ObjD
、ObjE
通过 GC Root
是无法找到的,所以它们是无用节点。
Java
中可作为 GC Root
的对象:
- 虚拟机栈中引用的对象(局部变量表)
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中引用的对象(Native对象)
如何回收
-
回收策略
- 标记-清除算法
- 复制算法
- 标记-整理算法
- 分代收集算法
标记-清除算法
效率问题,空间问题
-
算法过程需要暂停整个应用,效率不高。
-
标记清除后会产生大量不连续的内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。
复制算法
为了解决标志-清除算法的缺陷,由此有了复制算法。
复制算法将可用内存分为两块,每次只用其中一块,当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的内存空间一次性清理掉。
- 优点:实现简单,不易产生内存碎片,每次只需要对半个区进行内存回收。
- 缺点:内存空间缩减为原来的一半;算法的效率和存活对象的数目有关,存活对象越多,效率越低。
标记-整理算法
为了更充分利用内存空间,提出了标记-整理算法。
此算法结合了“标记-清除”和“复制”两个算法的优点。
该算法标记阶段和“标志-清除”算法一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。
分代收集算法
1、根据对象存活周期的不同将内存划分为几块。
2、一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
3、在新生代中,每次垃圾收集时都发现有大批对象死去(回收频率很高),只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
4、老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。
其中,新生代又细分为三个区:Eden,From Survivor,ToSurviver,比例是8:1:1。
垃圾回收器
Serial收集器
Serial收集器是一个新生代收集器,单线程执行,使用复制算法。它在进行垃圾收集时,必须暂停其他所有的工作线程(用户线程)。是Jvm client模式下默认的新生代收集器。对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。
- Serial Old收集器
- Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。
- 主要意义也是在于给Client模式下的虚拟机使用。
- 如果在Server模式下,那么它主要还有两大用途:
一种用途是在JDK 1.5以及之前的版本中与Parallel Scavenge收集器搭配使用[1],
另一种用途就是作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。
Parnew
-
Serial收集器的多线程版本
-
单CPU不如Serial,因为存在线程交互的开销
-XX:+UseParNewGC
新生代并行(ParNew),老年代串行(Serial Old)
-XX:ParallelGCThreads=n
设置并行收集器收集时使用的CPU数。并行收集线程数。一般最好和计算机的CPU相当
Parallel Scavenge 收集器
达到可控制的吞吐量
并行收集器,追求高吞吐量,高效利用 CPU
。吞吐量一般为 99%
, 吞吐量 =
用户线程时间 /
(用户线程时间 +
GC线程时间)。适合后台应用等对交互相应要求不高的场景。
- -XX:MaxGCPauseMillis 垃圾收集器停顿时间
- -XX:CGTimeRatio 吞吐量大小
并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。
- Parallel Old 收集器
Parallel Scavenge 收集器的老年代版本,并行收集器,吞吐量优先。
Cms
- 以获取最短回收停顿时间为目标的收集器。
- 非常符合互联网站或者B/S系统的服务端上,重视服务的响应速度,希望系统停顿时间最短的应用
- 基于“标记—清除”算法实现的
- CMS收集器的内存回收过程是与用户线程一起并发执行的
运作过程:
- 初始标记,“Stop The World”,只是标记一下GC Roots能直接关联到的对象,速度很快
- 并发标记,并发标记阶段就是进行GC RootsTracing的过程
- 重新标记,Stop The World”,是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,但远比并发标记的时间短
- 并发清除(CMS concurrent sweep)
优点:并发收集、低停顿
缺点:
- 对CPU资源非常敏感(占用大量cpu资源)。
- 无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。
- 一款基于“标记—清除”算法实现的收集器
G1收集器
1、当今收集器技术发展的最前沿成果之一
2、G1是一款面向服务端应用的垃圾收集器。
- 优点:
- 并行与并发:充分利用多CPU、多核环境下的硬件优势
- 分代收集:不需要其他收集器配合就能独立管理整个GC堆
- 空间整合:“标记—整理”算法实现的收集器,局部上基于“复制”算法不会产生内存空间碎片
- 可预测的停顿:能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒
- G1收集器的运作大致可划分为以下几个步骤:
- 初始标记:标记一下GC Roots能直接关联到的对象,需要停顿线程,但耗时很短
- 并发标记:是从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行
- 最终标记:修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录
- 筛选回收:对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划