一.概述:
垃圾回收的方法是判斷沒有個對象的可觸及性瞻凤,即從根節(jié)點開始是否可以訪問到這個對象世杀。如果訪問到了,說明當前對象正在使用瞻坝,如果從所以根節(jié)點都無法訪問到某個對象,說明這個對象不再使用了所刀。一般情況下,此對象要被回收浮创。但是,一個無法觸及的對象有可能在某一個條件下復活自己溜族,那么對它回收就不合理了。因此斩祭,需要給出一個對象可觸及性狀態(tài)的定義乡话,并規(guī)定在什么狀態(tài)下,才可以安全回收對象绑青。
可觸及性包括三種:
1.可觸及的:從根節(jié)點可以觸及到這個對象。
2.可復活的:對象所有引用被釋放闸婴,但是在finalize()中可能復活該對象。
3.不可觸及的:對象的finalize()被調(diào)用后降狠,并且沒有被復活,那么就進入不可觸及狀態(tài)榜配,不可觸及的對象不可能被復活吕晌,因為finalize()函數(shù)只會被調(diào)用一次蛋褥。
只有不可觸及的對象可以回收睛驳。
如何確定根節(jié)點:
- 棧中引用的對象:線程棧中函數(shù)引用的局部變量。
- 方法區(qū)中靜態(tài)成員或者常量引用的對象(全局對象)
- JNI方法棧中引用對象
二.對象的復活:
當對象沒有被引用的時候淫茵,這時如果gc,如何不被gc回收呢痘昌?如下代碼:
/**
* 對象的復活:
* 1.System.gc()之前會執(zhí)行對象的finalize()方法炬转。
* 2.對象的finalize()方法讓obj又復活了辆苔。
* 3.但是一個對象的finalize()方法只能執(zhí)行一次扼劈,所以第二次不能復活。
* 4.System.gc()只是開始回收荐吵,調(diào)用了finalize()后才是真正意義的回收赊瞬。
*
* 如果沒有"obj=null;//**第二次贼涩,不可復活 "這個語句,obj就不可能被回收了呢遥倦?不見得!K跎浮堡称!很危險**
* 經(jīng)驗:
* 1.避免使用finalize().
* 2.優(yōu)先級低瞎抛,何時調(diào)用不確定却紧。(什么時候gc,程序無法確定,系統(tǒng)決定)
* 3.可以用try-catch-finally來替代它啄寡。
* Created by chenyang on 2017/2/2.
*
*/
public class CanReliveObj {
public static CanReliveObj obj;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("CanReliveObj finalize called");
obj=this;
}
@Override
public String toString() {
return "I am CanReliveObj";
}
public static void main(String[] args) throws InterruptedException{
obj=new CanReliveObj();
obj=null;//引用清空,可復活
System.gc();
Thread.sleep(1000);
if(obj==null){
System.out.println("obj 是 null");
}else {
System.out.println("obj可用");
}
System.out.println("第2次gc");
obj=null;//不可復活
System.gc();
Thread.sleep(1000);
if(obj==null){
System.out.println("obj是null");
}else {
System.out.println("obj可用");
}
}
}
如上面的代碼懒浮,finalize()的危險识藤,因為如果沒有"obj=null;第二次砚著,不可復活 "這個語句痴昧,obj就不可能被回收了呢?不見得8献!餐胀!很危險
finalize()淺析:
在說明finalize()的用法之前要樹立有關(guān)于java垃圾回收器幾個觀點:
- "對象可以不被垃圾回收" : java的垃圾回收遵循一個特點, 就是能不回收就不會回收.只要程序的內(nèi)存沒有達到即將用完的地步, 對象占用的空間就不會被釋放.因為如果程序正常結(jié)束了,而且垃圾回收器沒有釋放申請的內(nèi)存, 那么隨著程序的正常退出, 申請的內(nèi)存會自動交還給操作系統(tǒng); 而且垃圾回收本身就需要付出代價, 是有一定開銷的, 如果不使用,就不會存在這一部分的開銷.
- 垃圾回收只能回收內(nèi)存, 而且只能回收內(nèi)存中由java創(chuàng)建對象方式(堆)創(chuàng)建的對象所占用的那一部分內(nèi)存, 無法回收其他資源, 比如文件操作的句柄, 數(shù)據(jù)庫的連接等等.
- 垃圾回收不是C++中的析構(gòu). 兩者不是對應關(guān)系, 因為第一點就指出了垃圾回收的發(fā)生是不確定的, 而C++中析構(gòu)函數(shù)是由程序員控制(delete) 或者離開器作用域時自動調(diào)用發(fā)生, 是在確定的時間對對象進行銷毀并釋放其所占用的內(nèi)存.
- 調(diào)用垃圾回收器(GC)不一定保證垃圾回收器的運行
- finalize()的功能 : 一旦垃圾回收器準備釋放對象所占的內(nèi)存空間, 如果對象覆蓋了finalize()并且函數(shù)體內(nèi)不能是空的, 就會首先調(diào)用對象的finalize(), 然后在下一次垃圾回收動作發(fā)生的時候真正收回對象所占的空間.
- finalize()有一個特點就是: JVM始終只調(diào)用一次. 無論這個對象被垃圾回收器標記為什么狀態(tài), finalize()始終只調(diào)用一次. 但是程序員在代碼中主動調(diào)用的不記錄在這之內(nèi).
經(jīng)驗:
- 避免使用finalize()瘤载,操作不慎可能導致錯誤。
- 優(yōu)先級低鸣奔,何時被調(diào)用惩阶, 不確定扣汪。何時發(fā)生GC不確定
- 可以使用try-catch-finally來替代它。
三.引用的類型和可觸及性的強度:
java提供了4個級別的引用:強引用崭别,軟引用,弱引用和虛引用紊遵。除了強引用外侥蒙,其他3種引用均可以在java.lang.ref包中找到。如下顯示了三種引用對應的類学搜。
強引用--指向的對象不會被回收
強引用就是程序中一般使用的引用類型论衍,強引用的對象時可觸及的瑞佩,不會被回收坯台。但是,軟引用稠炬,弱引用和虛引用的對象是軟可觸及,弱可觸及和虛可觸及首启,在一定的條件下,是可以被回收的毅桃。
下面是個強引用例子:
Class A{
StringBuffer str=new StringBuffer("Hello world");
public void getStr(){
StringBuffer str1=str;
StringBuffer str2=str1;
}
}
假設str是對象成員變量准夷,那么局部變量str1將被分配在棧上,而對象StringBuffer實例被分配在了堆上冕象。局部變量str1指向StringBuffer實例所在堆空間,通過str1可以操作該實例渐扮,那么str1就是StringBuilder實例的強引用掖棉。
強引用特點:
- 強引用可以直接訪問目標對象膀估。
- 強引用所指向的對象在任何時候都不會被系統(tǒng)回收,寧可拋出OOM異常察纯,也不會回收強引用所指的對象。
- 強引用可導致內(nèi)存泄漏香伴。
軟引用--指向的對象可以被回收的引用
如果一個對象只有被軟引用所引用,當堆空間不足的時候回被回收即纲。下面的例子演示了軟引用會在系統(tǒng)堆內(nèi)存不足的時候被回收:
import java.lang.ref.SoftReference;
/**軟引用:
* 軟引用是比強引用弱一點的引用類型博肋,如果一個對象僅僅只有軟引用,那么當堆空間不足時匪凡,
* 就會被回收。
*
* 啟動參數(shù):-Xmx10m -Xms10m -XX:+UseSerialGC -XX:+PrintGCDetails -Xmn1m
* Created by chenyang on 2017/2/2.
*/
public class SoftRef {
public static class User{
public User(int id,String name){
this.id=id;
this.name=name;
}
public int id;
public String name;
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public static void main(String[] args) {
User u=new User(1,"geym");//強引用
SoftReference<User> userSoftRef=new SoftReference<User>(u);//建立軟引用
u=null;//去除強引用
System.out.println(userSoftRef.get());//軟引用存在
System.gc();
System.out.println("After GC:");
System.out.println(userSoftRef.get());//gc后軟引用還存在唇跨,因為堆空間還是比較充足的。
byte[] b=new byte[1024*924*7];//堆空間被其他對象占據(jù)轻绞,這是堆空間比較緊張
System.gc();
System.out.println(userSoftRef.get());//堆空間比較緊張時佣耐,軟引用的對象被回收。
}
}
使用參數(shù)-Xmx10m運行上述代碼兼砖,得到:
[id=1,name=geym](從軟引用獲取數(shù)據(jù))
After GC:
[id=1,name=geym](GC沒有清除軟引用)
null (由于內(nèi)存緊張,軟引用雖然還在但是對象還是被回收了)
當內(nèi)存資源緊張時讽挟,軟引用指向的對象會被回收。所以耽梅,軟引用不會引起OOM。
每一個軟引用都可以附帶一個引用隊列诅迷,當對象的可達性狀態(tài)發(fā)生改變時(由可達變?yōu)椴豢蛇_)佩番,軟引用對象就會進入引用隊列罢杉,通過這個引用隊列,就可以跟蹤對象的回收情況:
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
/**
*
* 每一個軟引用都可以附帶一個引用隊列滩租,當對象的可達性發(fā)生改變(由可達變?yōu)椴豢蛇_),
* 軟引用對象就會進入引用隊列猎莲。通過這個引用隊列技即,可以跟蹤對象的回收情況益眉。
*
* 執(zhí)行參數(shù)-Xmx10m
* Created by chenyang on 2017/2/2.
*/
public class SoftRefQ {
public static class User{
public User(int id,String name){
this.id=id;
this.name=name;
}
public int id;
public String name;
}
static ReferenceQueue<User> softQueue=null;
public static class CheckRefQueue extends Thread{
@Override
public void run() {
while (true){
if(softQueue!=null){
UserSoftReference obj=null;
try {
obj=(UserSoftReference)softQueue.remove();
}catch (InterruptedException e){
e.fillInStackTrace();
}
if(obj!=null){
System.out.println("user id"+obj.uid+" is delete");
}
}
}
}
}
public static class UserSoftReference extends 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 t=new CheckRefQueue();
t.setDaemon(true);
t.start();
User u=new User(1,"chenyang");
softQueue=new ReferenceQueue<User>();
UserSoftReference userSoftRef=new UserSoftReference(u,softQueue);
u=null;
System.out.println(userSoftRef.get());
System.gc();
System.out.println("After GC:");
System.out.println(userSoftRef.get());
System.out.println("try to create byte array and GC");
byte[] b=new byte[1024*925*7];
System.gc();
System.out.println(userSoftRef.get());
Thread.sleep(1000);
}
}
在創(chuàng)建一個軟引用時,指定了一個軟引用隊列年碘,當給定的對象實例被回收時,就會加入這個引用隊列屿衅,通過隊列可以跟蹤對象的回收情況:
使用參數(shù)-Xmx10m運行上述代碼,得到:
[id=1,name=geym](從軟引用獲取對象)
After GC:
[id=1,name=geym](GC沒有清除軟引用指向的對象)
try to create byte array and GC (創(chuàng)建大數(shù)組涡尘,耗盡內(nèi)存)
user id 1 is deleted (引用隊列探測到對象被刪除)
null (由于內(nèi)存緊張,軟引用雖然還在但是對象還是被回收了)
弱引用--指向的對象被GC發(fā)現(xiàn)即回收
弱引用是一種比軟引用弱的引用類型考抄。在GC時蔗彤,只要發(fā)現(xiàn)弱引用的對象不管堆空間如何都會將對象回收川梅。由于垃圾回收器的線程優(yōu)先級很低然遏,因此不一定很快發(fā)現(xiàn)持有弱引用的對象。這樣待侵,弱引用對象可以存在較長的時間。一旦弱引用被GC回收怨酝,就會加入一個注冊的引用隊列中。
弱引用例子:
/**
* 弱引用是一種比軟引用較弱的引用類型凫碌。在系統(tǒng)gc時,只要發(fā)現(xiàn)弱引用盛险,不管系統(tǒng)堆空間使用情況如何,
* 都會將對象進行回收苦掘。但是,由于垃圾回收期的線程通常優(yōu)先級很低惯驼,并不一定很快發(fā)現(xiàn)持有的弱引用對象。
* 軟引用和弱引用非常適合那些可有可無的緩存數(shù)據(jù)祟牲。當系統(tǒng)堆內(nèi)存很低時,系統(tǒng)回收抖部。當系統(tǒng)堆內(nèi)存很高時说贝,
* 這些緩存又能存在很長時間,從而起到加速系統(tǒng)的作用慎颗。
* Created by chenyang on 2017/2/2.
*/
public class WeakRef {
public static class User {
public User(int id, String name) {
this.id=id;
this.name=name;
}
public int id;
public String name;
}
public static void main(String[] args) {
User u=new User(1,"chenyang");
WeakReference<User> userWeakRef=new WeakReference<User>(u);
u=null;
System.out.println(userWeakRef.get());
System.gc();
//不管當前內(nèi)存空間是否夠用乡恕,都會回收它的內(nèi)存
System.out.println("After GC:");
System.out.println(userWeakRef.get());
}
}
輸出為:
[id=1,name=geym](從弱引用獲取對象)
After GC:
null (弱對象被回收了)
軟引用和虛引用的使用場景:
軟引用和弱引用非常適合那些可有可無的緩存數(shù)據(jù)。當系統(tǒng)堆內(nèi)存不足時俯萎,系統(tǒng)回收傲宜。當系統(tǒng)堆內(nèi)存充足時,這些緩存又能存在很長時間夫啊,從而起到加速系統(tǒng)的作用函卒。
虛引用:
虛引用是所有引用類型中最弱的一個。一個吃魚虛引用的對象撇眯,跟沒有引用幾乎一樣谆趾,隨時會被GC回收。當試圖通過虛引用的get()方法取得強引用時沪蓬,總會失敗。而且来候,虛引用必須和引用隊列一起使用跷叉,它的作用在于跟蹤GC回收的過程。
當GC準備回收一個對象時,如果發(fā)現(xiàn)它還有虛引用云挟,就會在回收對象后梆砸,將這個虛引用加入引用隊列,以通知應用程序?qū)ο蟮幕厥涨闆r园欣。
下面給出一個例子帖世,使用虛引用跟蹤一個可復活對象的回收。
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
/**
* Created by chenyang on 2017/2/2.
*/
public class TraceCanReliveObj {
public static TraceCanReliveObj obj;
static ReferenceQueue<TraceCanReliveObj> phantomQueue=null;
public static class CheckRefQueue extends Thread{
@Override
public void run() {
while (true){
if(phantomQueue!=null){
PhantomReference<TraceCanReliveObj> objt=null;
try {
objt=(PhantomReference<TraceCanReliveObj>)phantomQueue.remove();
}catch (InterruptedException e){
e.fillInStackTrace();
}
if(objt!=null){
System.out.println("TraceCanReliveObj is delete");
}
}
}
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("CanReliveObj finalize called");
obj=this;
}
@Override
public String toString() {
return "I am CanReliveObj";
}
public static void main(String[] args) throws InterruptedException{
Thread t=new CheckRefQueue();
t.setDaemon(true);
t.start();
phantomQueue=new ReferenceQueue<TraceCanReliveObj>();
obj=new TraceCanReliveObj();
PhantomReference<TraceCanReliveObj> phantomRef=new PhantomReference<TraceCanReliveObj>(obj,phantomQueue);
obj=null;
System.gc();
Thread.sleep(1000);
if(obj==null){
System.out.println("obj 是null");
}else {
System.out.println("obj 可用");
}
System.out.println("第二次gc");
obj=null;
System.gc();
Thread.sleep(1000);
if(obj==null){
System.out.println("obj是null");
}else {
System.out.println("obj 可用");
}
}
}
輸出以下結(jié)果:
CanReliveObj finalize called (對象復活)
obj可用
第2次gc (第二次對象)
TraceCanReliveObj is delete (引用隊列捕獲到對象被回收)
obj是null