本篇文章將深入淺出的介紹Java中的記憶體溢位與記憶體洩漏並說明強引用、軟引用、弱引用、虛引用的特點與使用場景
引用
在棧上的reference
型別儲存的資料代表某塊記憶體地址,稱reference
為某記憶體、某物件的引用
實際上引用分為很多種,從強到弱分為:強引用 > 軟引用 > 弱引用 > 虛引用
平常我們使用的引用實際上是強引用,各種引用有自己的特點,下文將一一介紹
強引用就是Java中普通的物件,而軟引用、弱引用、虛引用在JDK中定義的類分別是SoftReference
、WeakReference
、PhantomReference
下圖是軟引用、弱引用、虛引用、引用佇列(搭配虛引用使用)之間的繼承關係
記憶體溢位與記憶體洩漏
為了更清除的描述引用之間的作用,首先需要介紹一下記憶體溢位和記憶體洩漏
當發生記憶體溢位時,表示JVM沒有空閒記憶體為新物件分配空間,丟擲OutOfMemoryError(OOM)
當應用程式佔用記憶體速度大於垃圾回收記憶體速度時就可能發生OOM
丟擲OOM之前通常會進行Full GC,如果進行Full GC後依舊記憶體不足才丟擲OOM
JVM引數-Xms10m -Xmx10m -XX:+PrintGCDetails
記憶體溢位可能發生的兩種情況:
- 必須的資源確實很大,堆記憶體設定太小 (透過
-Xmx
來調整)
<!---->
- 發生記憶體洩漏,建立大量物件,且生命週期長,不能被回收
記憶體洩漏Memory Leak: 物件不會被程式用到了,但是不能回收它們
物件不再使用並且不能回收就會一直佔用空間,大量物件發生記憶體洩漏可能發生記憶體溢位OOM
廣義記憶體洩漏:不正確的操作導致物件生命週期變長
- 單例中引用外部物件,當這個外部物件不用了,但是因為單例還引用著它導致記憶體洩漏
- 一些需要關閉的資源未關閉導致記憶體洩漏
強引用
強引用是程式程式碼中普遍存在的引用賦值,比如List list = new ArrayList();
只要強引用在可達性分析演算法中可達時,垃圾收集器就不會回收該物件,因此不當的使用強引用是造成Java記憶體洩漏的主要原因
軟引用
當記憶體充足時不會回收軟引用
只有當記憶體不足時,發生Full GC時才將軟引用進行回收,如果回收後還沒充足記憶體則丟擲OOM異常
JVM中針對不同的區域(年輕代、老年代、元空間)有不同的GC方式,Full GC的回收區域為整個堆和元空間
軟引用使用SoftReference
記憶體充足情況下的軟引用
public static void main(String[] args) {
int[] list = new int[10];
SoftReference listSoftReference = new SoftReference(list);
//[I@61bbe9ba
System.out.println(listSoftReference.get());
}
記憶體不充足情況下的軟引用(JVM引數:-Xms5m -Xmx5m -XX:+PrintGCDetails)
//-Xms5m -Xmx5m -XX:+PrintGCDetails
public class SoftReferenceTest {
public static void main(String[] args) {
int[] list = new int[10];
SoftReference listSoftReference = new SoftReference(list);
list = null;
//[I@61bbe9ba
System.out.println(listSoftReference.get());
//模擬空間資源不足
try{
byte[] bytes = new byte[1024 * 1024 * 4];
System.gc();
}catch (Exception e){
e.printStackTrace();
}finally {
//null
System.out.println(listSoftReference.get());
}
}
}
弱引用
無論記憶體是否足夠,當發生GC時都會對弱引用進行回收
弱引用使用WeakReference
記憶體充足情況下的弱引用
public static void test1() {
WeakReference<int[]> weakReference = new WeakReference<>(new int[1]);
//[I@511d50c0
System.out.println(weakReference.get());
System.gc();
//null
System.out.println(weakReference.get());
}
WeakHashMap
JDK中有一個WeakHashMap,使用與Map相同,只不過節點為弱引用
當key的引用不存在引用的情況下,發生GC時,WeakHashMap中該鍵值對就會被刪除
public static void test2() {
WeakHashMap<String, String> weakHashMap = new WeakHashMap<>();
HashMap<String, String> hashMap = new HashMap<>();
String s1 = new String("3.jpg");
String s2 = new String("4.jpg");
hashMap.put(s1, "圖片1");
hashMap.put(s2, "圖片2");
weakHashMap.put(s1, "圖片1");
weakHashMap.put(s2, "圖片2");
//只將s1賦值為空時,堆中的3.jpg字串還會存在強引用,所以要remove
hashMap.remove(s1);
s1=null;
s2=null;
System.gc();
//4.jpg=圖片2
test2Iteration(hashMap);
//4.jpg=圖片2
test2Iteration(weakHashMap);
}
private static void test2Iteration(Map<String, String> map){
Iterator iterator = map.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry entry = (Map.Entry) iterator.next();
System.out.println(entry);
}
}
未顯示刪除weakHashMap中的該key,當這個key沒有其他地方引用時就刪除該鍵值對
軟引用,弱引用適用的場景
資料量很大佔用記憶體過多可能造成記憶體溢位的場景
比如需要載入大量資料,全部載入到記憶體中可能造成記憶體溢位,就可以使用軟引用、弱引用來充當快取,當記憶體不足時,JVM對這些資料進行回收
使用軟引用時,可以自定義Map進行儲存Map<String,SoftReference<XXX>> cache
使用弱引用時,則可以直接使用WeakHashMap
軟引用與弱引用的區別則是GC回收的時機不同,軟引用存活可能更久,Full GC下才回收;而弱引用存活可能更短,發生GC就會回收
虛引用
使用PhantomReference
建立虛引用,需要搭配引用佇列ReferenceQueue
使用
無法透過虛引用得到該物件例項(其他引用都可以得到例項)
虛引用只是為了能在這個物件被收集器回收時收到一個通知
引用佇列搭配虛引用使用
public class PhantomReferenceTest {
private static PhantomReferenceTest reference;
private static ReferenceQueue queue;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("呼叫finalize方法");
//搭上引用鏈
reference = this;
}
public static void main(String[] args) {
reference = new PhantomReferenceTest();
//引用佇列
queue = new ReferenceQueue<>();
//虛引用
PhantomReference<PhantomReferenceTest> phantomReference = new PhantomReference<>(reference, queue);
Thread thread = new Thread(() -> {
PhantomReference<PhantomReferenceTest> r = null;
while (true) {
if (queue != null) {
r = (PhantomReference<PhantomReferenceTest>) queue.poll();
//說明被回收了,得到通知
if (r != null) {
System.out.println("例項被回收");
}
}
}
});
thread.setDaemon(true);
thread.start();
//null (獲取不到虛引用)
System.out.println(phantomReference.get());
try {
System.out.println("第一次gc 物件可以復活");
reference = null;
//第一次GC 引用不可達 守護執行緒執行finalize方法 重新變為可達物件
System.gc();
TimeUnit.SECONDS.sleep(1);
if (reference == null) {
System.out.println("object is dead");
} else {
System.out.println("object is alive");
}
reference = null;
System.out.println("第二次gc 物件死了");
//第二次GC 不會執行finalize方法 不能再變為可達物件
System.gc();
TimeUnit.SECONDS.sleep(1);
if (reference == null) {
System.out.println("object is dead");
} else {
System.out.println("object is alive");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
結果:
/*
null
第一次gc 物件可以復活
呼叫finalize方法
object is alive
第二次gc 物件死了
例項被回收
object is dead
*/
第一次GC時,守護執行緒執行finalize方法讓虛引用重新可達,所以沒死
第二次GC時,不再執行finalize方法,虛引用已死
虛引用回收後,引用佇列有資料,來通知告訴我們reference這個物件被回收了
使用場景
GC只能回收堆內記憶體,而直接記憶體GC是無法回收的,直接記憶體代表的物件建立一個虛引用,加入引用佇列,當這個直接記憶體不使用,這個代表直接記憶體的物件為空時,這個虛記憶體就死了,然後引用佇列會產生通知,就可以通知JVM去回收堆外記憶體(直接記憶體)
總結
本篇文章圍繞引用深入淺出的解析記憶體溢位與洩漏、強引用、軟引用、弱引用、虛引用
當JVM沒有足夠的記憶體為新物件分配空間時就會發生記憶體溢位丟擲OOM
記憶體溢位有兩種情況,一種是分配的資源太少,不滿足必要物件的記憶體;另一種是發生記憶體洩漏,不合理的設定物件的生命週期、不關閉資源都會導致記憶體洩漏
使用最常見的就是強引用,強引用只有在可達性分析演算法中不可達時才會回收,強引用使用不當是造成記憶體洩漏的原因之一
使用SoftReference
軟引用時,只要記憶體不足觸發Full GC時就會對軟引用進行回收
使用WeakReference
弱引用時,只要發生GC就會對弱引用進行回收
軟、弱引用可以用來充當大資料情況下的快取,它們的區別就是軟引用可能活的更久Full GC才回收,使用弱引用時可以直接使用JDK中提供的WeakHashMap
虛引用無法在程式中獲取,與引用佇列搭配使用,當虛引用被回收時,能夠從引用佇列中取出(感知),可以在直接引用不使用時,發出訊息讓JVM進行回收
最後(一鍵三連求求拉~)
本篇文章將被收入JVM專欄,覺得不錯感興趣的同學可以收藏專欄喲~
本篇文章筆記以及案例被收入 gitee-StudyJava、 github-StudyJava 感興趣的同學可以stat下持續關注喔\~
有什麼問題可以在評論區交流,如果覺得菜菜寫的不錯,可以點贊、關注、收藏支援一下\~
關注菜菜,分享更多幹貨,公眾號:菜菜的後端私房菜
本文由部落格一文多發平臺 OpenWrite 釋出!