JVM垃圾回收学习笔记
垃圾对象的判断和可触及性:
3种可触及性的状态
可触及的:
从根节点开始,可以到达这个对象。可复活的:
对象的所有引用都被释放,但是对象有可能在finalize()函数中复活。不可触及的:
对象的finalize()函数被调用,并且没有复活,那么就进入不可触及的状态,不可触及的对象不可能被复活,因为finalize()函数一个对象的生命周期中只会被调用一次。
以上三种状态中,只有在对象不可触及是才可以被安全的回收。
finalize()->对象的复活
对象通过finalize()函数来复活自己。如下例子:
public class FinalizeObjectDemo {
public static FinalizeObjectDemo demo;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("FinalizeObjectDemo finalize called");
demo = this;
}
@Override
public String toString() {
return "I am FinalizeObjectDemo";
}
public static void main(String[] args) throws InterruptedException {
demo = new FinalizeObjectDemo();
demo = null;
System.gc();
Thread.sleep(1000);
if (demo == null) {
System.out.println("demo is null");
}else{
System.out.println("demo is object");
}
System.out.println("second GC");
demo = null;
System.gc();
Thread.sleep(1000);
if (demo == null) {
System.out.println("demo is null -");
}else{
System.out.println("demo is object -");
}
}
}
控制台输出:
FinalizeObjectDemo finalize called
demo is object
second GC
demo is null -
如上例子所示结果证明,finalize()导致引用外泄,对象复活,此时对象又变为可触及的状态。但是每个对象的生命周期只会调用一次finalize()函数。所以第二次GC时,则完成了GC的安全回收。
注意:
不推荐使用finalize()函数来释放资源。
- 由于finalize()函数有可能发生引用外泄,无意中复活对象。
- 由于finalize()函数是被系统调用的,不能准确的把控调用。因此用于资源的释放不是一个明智的方案。推荐使用try-catch-finally语句中进行资源的释放。
引用和可触及性的强度
java中存在四个级别的引用:
强引用
,软引用
,弱引用
,虚引用
以上四种,除了强引用之外,其他三种在一定的条件下,均可以被回收。
强引用-不可回收的引用
强引用则是程序中常用的引用类型。都是可触及,可达的。
下面的例子则是一个强引用:
public class ReferenceDemo { public static void main(String[] args) { //强引用 StringBuffer stringBuffer = new StringBuffer("hello world"); //强引用 StringBuffer stringBuffer1 = stringBuffer; System.out.println(stringBuffer == stringBuffer1); } } 控制台输出结果:true
上述例子中,两个引用都属于强引用,强引用具有以下特点:
- 强引用可以直接访问对象。
- 强引用所指向的对象,在任何时候都不会被回收。
- 强引用可能导致内存泄露。
软引用-可被回收的引用
软引用是比强引用弱一点的引用类型,当一个对象只持有软引用时,当堆空间不足时,则可以进行回收。
下面的例子演示了当Heap的内存空间不足时被回收
启动参数设置:-Xmx10m
:import java.lang.ref.ReferenceQueue; public class SoftRefQueue { public static class User{ private int id; private String name; public User(int id, String name) { this.id = id; this.name = name; } @Override public String toString() { return "[id:"+this.id+",name:"+this.name+"]"; } } //创建一个软引用队列 static ReferenceQueue<User> softQueue = null; public static class UserSoftReference extends java.lang.ref.SoftReference<User> { int uid; public UserSoftReference(User referent, ReferenceQueue<? super User> q) { super(referent, q); uid=referent.id; } } public static void main(String[] args) throws InterruptedException { Thread thread =new Thread(()->{ while (true) { if (softQueue != null) { UserSoftReference obj = null; try { obj = (UserSoftReference) softQueue.remove(); } catch (InterruptedException e) { e.printStackTrace(); } if (obj != null) { System.out.println("user id :"+obj.uid+" is delete"); } } } }); thread.setDaemon(true); thread.start(); User user = new User(1, "james"); softQueue = new ReferenceQueue<User>(); UserSoftReference userSoftReference = new UserSoftReference(user,softQueue); user = null; System.out.println(userSoftReference.get()); System.gc(); System.out.println("GC After"); System.out.println(userSoftReference.get()); System.out.println("try to create byte array and GC"); byte[] bytes = new byte[1024 * 940 * 7]; System.gc(); System.out.println(userSoftReference.get()); Thread.sleep(1000); } } 控制台输出: [id:1,name:james] GC After [id:1,name:james] try to create byte array and GC user id :1 is delete null
以上的例子说明了,在堆内存资源正常的时候,软引用的对象不一定会被回收,但是在内存资源紧张的情况下,软引用的对象还是会被回收的。所以,软引用的对象时不会出现OOM的问题。
上述例子中出现的
软引用队列:ReferenceQueue
,当软引用对象的被回收时,软引用对象则被引入到Queue中去。本例子中开启一个守护线程轮询判断当前软引用对象是否被回收,当软引用被回收时加入这个队列中,则可以通过当前队列来跟踪对象的回收情况。
弱引用-发现即回收
弱引用是比软引用还弱的一种引用类型。在GC回收时,只要碰见弱引用则立即回收。弱引用在被GC回收时,回收的对象也会被加入到一个注册的引用队列中去,这点和软引用很相似。下面是代码举例:
public class WeakReferenceDemo { public static class User{ private int id; private String name; public User(int id, String name) { this.id = id; this.name = name; } @Override public String toString() { return "[id:"+this.id+",name:"+this.name+"]"; } } public static void main(String[] args) { User user = new SoftRefQueue.User(11, "James"); WeakReference<User> userWeakReference = new WeakReference<User>(user); user = null; System.out.println(userWeakReference.get()); System.gc(); System.out.println(userWeakReference.get()); } } 控制台输出结果: [id:11,name:James] null
上述例子说明无论是否内存资源足够与否,弱引用碰见都是立即回收的。
虚引用-对象回收跟踪
**虚引用:**虚引用是所有引用类型中最弱的一个。虚引用随时都会被GC回收。虚引用使用时必须和队列一起使用。它的作用就在于跟踪垃圾回收过程。下面的例子说明虚引用:
import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; public class TraceCanReliveObj { public static TraceCanReliveObj obj; static ReferenceQueue<TraceCanReliveObj> queue = null; @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("obj is GC"); obj= this; } @Override public String toString() { return "I am TraceCanReliveObj"; } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(()->{ while (true) { if (queue != null) { PhantomReference<TraceCanReliveObj> objs = null; try { objs = (PhantomReference<TraceCanReliveObj>) queue.remove(); } catch (InterruptedException e) { e.printStackTrace(); } if (objs!= null) { System.out.println("objs is delete by GC"); } } } }); thread.setDaemon(true); thread.start(); queue = new ReferenceQueue<TraceCanReliveObj>(); obj = new TraceCanReliveObj(); PhantomReference<TraceCanReliveObj> traceCanReliveObjPhantomReference = new PhantomReference<TraceCanReliveObj>(obj,queue); obj = null; System.gc(); Thread.sleep(1000); if (obj == null){ System.out.println("GC is finish"); }else{ System.out.println("GC is failure"); } System.out.println(" the second GC"); obj = null; System.gc(); Thread.sleep(1000); if (obj == null){ System.out.println("GC is finish"); }else{ System.out.println("GC is failure"); } } } 控制台输出: obj is GC GC is failure the second GC objs is delete by GC GC is finish 启动参数输入:-XX:+PrintGCDetails来查看GC日志GC的回收情况
上述例子说明:虚引用可以跟踪对象的回收,因此,可以将一些资源的释放操作放置在虚引用中执行和记录。
垃圾回收的停顿现象
*
垃圾回收器的任务:
*识别和回收垃圾对象进行内存清理。*
STW(Stop-The-World):
*在GC时候的系统短暂性的停顿,为了保证系统中不会有新的垃圾出现。同时停顿保证了系统状态在某一瞬间的一致性。也有益于垃圾回收器更好的标记垃圾对象。
参考书籍:《实战Java虚拟机—JVM故障诊断与性能优化》