JVM垃圾回收算法与垃圾回收器
谁要GC?
栈(线程)—不需要GC
堆(对象)——需要GC
方法区(存放字节码常量)——需要GC
GC如何判断对象的存活?
1.使用引用技术算法。
2.可达性分析。
引用计数算法
给对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。
优点:快,方便,实现简单。
缺点::对象相互引用时(A.instance=B 同时 B.instance=A),产生循环引用,很难判断对象是否该回收。
可达性分析
判定对象是否存活的。这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的。
作为 GC Roots 的对象包括下面几种:
当前虚拟机栈中局部变量表中的引用的对象
当前本地方法栈中局部变量表中的引用的对象
方法区中类静态属性引用的对象
方法区中的常量引用的对象
finalize
finalize 可以完成对象的拯救,但是 JVM 不保证一定能执行。
各种引用(Reference)
强引用
一般的Object o= new Object()属于强引用,如果有GCRoot强引用,垃圾回收器绝对不会回收它,当内存不足时抛出OOM异常,使程序停止。
/**
* 可达性分析算法
*/
public class GCRoots {
Object o =new Object();
static Object GCRoot1 =new Object(); //GC Roots
final static Object GCRoot2 =new Object();
//
public static void main(String[] args) {
//可达
Object object1 = GCRoot1; //=不是赋值,在对象中是引用,传递的是右边对象的地址
Object object2 = object1;
Object object3 = object1;
Object object4 = object3;
}
public void king(){
//不可达(方法运行完后可回收)
Object object5 = o;//o不是GCRoots
Object object6 = object5;
Object object7 = object5;
}
//本地变量表中引用的对象
public void stack(){
Object ostack =new Object(); //本地变量表的对象
Object object9 = ostack;
//以上object9 在方法没有(运行完)出栈前都是可达的
}
}
软引用 SoftReference
垃圾回收器在内存充足的时候不会回收它,而在内存不足时会回收它。挼引用非常适合缓存,一些有用但是并非必需,用软引用关联的对象,系统将要发生 OOM 之前,这些对象就会被回收。
/**
* 软引用 -Xms30m -Xmx30m
*/
public class TestSoftRef {
//对象
public static class User{
public int id = 0;
public String name = "";
public User(int id, String name) {
super();
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
}
}
//
public static void main(String[] args) {
User u = new User(1,"King"); //new是强引用
SoftReference<User> userSoft = new SoftReference<User>(u);
u = null;//干掉强引用,确保这个实例只有userSoft的软引用
System.out.println(userSoft.get()); //看一下这个对象是否还在
System.gc();//进行一次GC垃圾回收 千万不要写在业务代码中。
System.out.println("After gc");
System.out.println(userSoft.get());
//往堆中填充数据,导致OOM
List<byte[]> list = new LinkedList<>();
try {
for(int i=0;i<100;i++) {
System.out.println("*************"+userSoft.get());
list.add(new byte[1024*1024*1]); //1M的对象
}
} catch (Throwable e) {
//抛出了OOM异常时打印软引用对象
System.out.println("Exception*************"+userSoft.get());
}
}
}
结果
User [id=1, name=King]
[GC (System.gc()) 1785K->724K(9728K), 0.0997625 secs]
[Full GC (System.gc()) 724K->675K(9728K), 0.0049574 secs]
After gc
User [id=1, name=King]
*************User [id=1, name=King]
*************User [id=1, name=King]
*************User [id=1, name=King]
*************User [id=1, name=King]
*************User [id=1, name=King]
*************User [id=1, name=King]
*************User [id=1, name=King]
*************User [id=1, name=King]
[GC (Allocation Failure) -- 7972K->7980K(9728K), 0.0167924 secs]
[Full GC (Ergonomics) 7980K->7793K(9728K), 0.0068365 secs]
[GC (Allocation Failure) -- 7793K->7793K(9728K), 0.0003036 secs]
[Full GC (Allocation Failure) 7793K->7775K(9728K), 0.0058510 secs]
Exception*************null
弱引用 WeakReference
垃圾回收器在扫描到该对象时,无论内存充足与否,都会回收该对象的内存。
/**
* 弱引用
*/
public class TestWeakRef {
public static class User{
public int id = 0;
public String name = "";
public User(int id, String name) {
super();
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
}
}
public static void main(String[] args) {
User u = new User(1,"King");
WeakReference<User> userWeak = new WeakReference<User>(u);
u = null;//干掉强引用,确保这个实例只有userWeak的弱引用
System.out.println(userWeak.get());
System.gc();//进行一次GC垃圾回收
System.out.println("After gc");
System.out.println(userWeak.get());
}
}
结果
User [id=1, name=King]
After gc
null
总之软引用 SoftReference 和弱引用 WeakReference,可以用在内存资源紧张的情况下以及创建不是很重要的数据缓存。
什么时候发生GC?
Minor GC
特点: 发生在新生代上,发生的较频繁,执行速度较快
触发条件: Eden 区空间不足\空间分配担保。
Full GC
特点: 主要发生在老年代上(新生代也会回收),较少发生,执行速度较慢
触发条件:
1.调用 System.gc()
2.老年代区域空间不足
3.空间分配担保失败
4.JDK 1.7 及以前的永久代(方法区)空间不足
5.CMS GC 处理浮动垃圾时,如果新生代空间不足,则采用空间分配担保机制,如果老年代空间不足,则触发 Full GC。
复制算法
优点
简单高效,不会出现内存碎片问题
缺点
内存利用率低,只有一半
存活对象较多时效率明显会降低
标记清除算法
过程:
- 首先标记所有需要回收的对象
- 统一回收被标记的对象
优点
利用率百分之百
缺点
1.效率低。
2.标记清除后会产生大量不连续的控件碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大时,无法找到足够的连续内存而不得不触发GC。
标记整理算法
优点
利用率百分之百
没有内存碎片
缺点
标记和清除的效率都不高
效率相对标记-清除要低
垃圾回收器
分代收集
根据各个年代的特点选取不同的垃圾收集算法,新生代使用复制算法,老年代使用标记整理或者标记-清除算法。
查看垃圾回收器
在命令行输入
java -XX:+PrintCommandLineFlags -version
在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。
并行:垃圾收集的多线程的同时进行。
并发:垃圾收集的多线程和应用的多线程同时进行。
各种垃圾回收器
Serial/Serial Old
最古老的,单线程,独占式,成熟,适合单 CPU 服务器
-XX:+UseSerialGC 新生代和老年代都用串行收集器
-XX:+UseParNewGC 新生代使用 ParNew,老年代使用 Serial Old
-XX:+UseParallelGC 新生代使用 ParallerGC,老年代使用 Serial Old
ParNew
和 Serial 基本没区别,唯一的区别:多线程,多 CPU 的,停顿时间比 Serial 少
-XX:+UseParNewGC 新生代使用 ParNew,老年代使用 Serial Old
除了性能原因外,主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合工作。
Parallel Scavenge(ParallerGC)/Parallel Old
关注吞吐量的垃圾收集器,高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
所谓吞吐量就是 CPU 用于运行用户代码的时间与 CPU 总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机总共运行了 100 分钟,其中垃圾收集花掉 1 分钟,那吞吐量就是 99%。
Concurrent Mark Sweep (CMS)
CMD收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的 Java 应用集中在互联网站或者 B/S 系统的服务端上。
-XX:+UseConcMarkSweepGC ,一般新生代使用 ParNew,老年代的用 CMS
优点
由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说,CMS 收集器的内存回收过程是与用户线程一起并发执行的。
缺点
会产生浮动垃圾
采用标记清除算法,内存会有碎片。
G1 垃圾回收器
G1 中重要的参数:
-XX:+UseG1GC 使用 G1 垃圾回收器
内存布局的改变
G1 把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离。
例如:堆内存8G,分为1000份区域,则每一个区域的空间为8G/1000=8m。
特点
1.空间整合,不会产生内存碎片。
2.可预测的停顿,1000区域一个区域10M*100个(急需回收)
GC模式
Young GC复制回收算法
选定所有年轻代里的 Region。通过控制年轻代的 region 个数,即年轻代内存大小,来控制 young GC 的时间开销。(复制回收算法)。
Mixed GC
Mixed GC 不是 full GC,如果它无法跟上程序分配内存的速度,就会触发Full CG来手机整个GC Heap.
全局并发标记(Global Concurrent Marking)
初始标记
并发标记
最终标记
回收