自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

小心點(diǎn),別被當(dāng)成垃圾回收了

開(kāi)發(fā) 前端
我們說(shuō)的不同的引用類型其實(shí)都是邏輯上的,而對(duì)于虛擬機(jī)來(lái)說(shuō),主要體現(xiàn)的是對(duì)象的不同的可達(dá)性(reachable) 狀態(tài)和對(duì)垃圾收集(garbage collector)的影響。

 我們說(shuō)的不同的引用類型其實(shí)都是邏輯上的,而對(duì)于虛擬機(jī)來(lái)說(shuō),主要體現(xiàn)的是對(duì)象的不同的可達(dá)性(reachable) 狀態(tài)和對(duì)垃圾收集(garbage collector)的影響。

[[323258]]

初識(shí)引用

對(duì)于剛接觸 Java 的 C++ 程序員而言,理解棧和堆的關(guān)系可能很不習(xí)慣。在 C++ 中,可以使用 new 操作符在堆上創(chuàng)建對(duì)象,或者使用自動(dòng)分配在棧上創(chuàng)建對(duì)象。下面的 C++ 語(yǔ)句是合法的,但是 Java 編譯器卻拒絕這么寫(xiě)代碼,會(huì)出現(xiàn) syntax error 編譯錯(cuò)誤。

  1. Integer foo = Integer(1); 

Java 和 C 不一樣,Java 中會(huì)把對(duì)象都放在堆上,需要 new 操作符來(lái)創(chuàng)建對(duì)象。本地變量存儲(chǔ)在棧中,它們持有一個(gè)指向堆中對(duì)象的引用(指針)。下面是一個(gè) Java 方法,該方法具有一個(gè) Integer 變量,該變量從 String 解析值

  1. public static void foo(String bar){ 
  2.     Integer baz = new Integer(bar); 

這段代碼我們使用堆棧分配圖可以看一下它們的關(guān)系

首先先來(lái)看一下 foo() 方法,這一行代碼分配了一個(gè)新的 Integer 對(duì)象,JVM 嘗試在堆空間中開(kāi)辟一塊內(nèi)存空間。如果允許分配的話,就會(huì)調(diào)用 Integer 的構(gòu)造方法把 String 字符串轉(zhuǎn)換為 Integer 對(duì)象。JVM 將指向該對(duì)象的指針存儲(chǔ)在變量 baz 中。

上面這種情況是我們樂(lè)意看到的情況,畢竟我們不想在編寫(xiě)代碼的時(shí)候遇到阻礙,但是這種情況是不可能出現(xiàn)的,當(dāng)堆空間無(wú)法為 bar 和 baz 開(kāi)辟內(nèi)存空間時(shí),就會(huì)出現(xiàn) OutOfMemoryError,然后就會(huì)調(diào)用垃圾收集器(garbage collector) 來(lái)嘗試騰出內(nèi)存空間。這中間涉及到一個(gè)問(wèn)題,垃圾收集器會(huì)回收哪些對(duì)象?

垃圾收集器

Java 給你提供了一個(gè) new 操作符來(lái)為堆中的對(duì)象開(kāi)辟內(nèi)存空間,但它沒(méi)有提供 delete 操作符來(lái)釋放對(duì)象空間。當(dāng) foo() 方法返回時(shí),如果變量 baz 超過(guò)最大內(nèi)存,但它所指向的對(duì)象仍然還在堆中。如果沒(méi)有垃圾回收器的話,那么程序就會(huì)拋出 OutOfMemoryError 錯(cuò)誤。然而 Java 不會(huì),它會(huì)提供垃圾收集器來(lái)釋放不再引用的對(duì)象。

當(dāng)程序嘗試創(chuàng)建新對(duì)象并且堆中沒(méi)有足夠的空間時(shí),垃圾收集器就開(kāi)始工作。當(dāng)收集器訪問(wèn)堆時(shí),請(qǐng)求線程被掛起,試圖查找程序不再主動(dòng)使用的對(duì)象,并回收它們的空間。如果垃圾收集器無(wú)法釋放足夠的內(nèi)存空間,并且JVM 無(wú)法擴(kuò)展堆,則會(huì)出現(xiàn) OutOfMemoryError,你的應(yīng)用程序通常在這之后崩潰。還有一種情況是 StackOverflowError ,它出現(xiàn)的原因是因?yàn)榫€程請(qǐng)求的棧深度要大于虛擬機(jī)所允許的深度時(shí)出現(xiàn)的錯(cuò)誤。

標(biāo)記 - 清除算法

Java 能永久不衰的一個(gè)原因就是因?yàn)槔占鳌TS多人認(rèn)為 JVM 會(huì)為每個(gè)對(duì)象保留一個(gè)引用計(jì)數(shù),當(dāng)每次引用對(duì)象的時(shí)候,引用計(jì)數(shù)器的值就 + 1,當(dāng)引用失效的時(shí)候,引用計(jì)數(shù)器的值就 - 1。而垃圾收集器只會(huì)回收引用計(jì)數(shù)器的值為 0 的情況。這其實(shí)是 引用計(jì)數(shù)法(Reference Counting) 的收集方式。但是這種方式無(wú)法解決對(duì)象之間相互引用的問(wèn)題,如下

  1. class A{ 
  2.   public B b; 
  3.  
  4. class B{ 
  5.   public A a; 
  6. public class Main{ 
  7.     public static void main(String[] args){ 
  8.     A a = new A(); 
  9.     B b = new B(); 
  10.     a.b=b; 
  11.     b.a=a; 
  12.     } 

然而實(shí)際上,JVM 使用一種叫做 標(biāo)記-清除(Mark-Sweep)的算法,標(biāo)記清除垃圾回收背后的想法很簡(jiǎn)單:程序無(wú)法到達(dá)的每個(gè)對(duì)象都是垃圾,可以進(jìn)行回收。

標(biāo)記-清除收集具有如下幾個(gè)階段

  • 階段一:標(biāo)記

垃圾收集器會(huì)從 根(root) 引用開(kāi)始,標(biāo)記它到達(dá)的所有對(duì)象。如果用老師給學(xué)生判斷卷子來(lái)比喻,這就相當(dāng)于是給試卷上的全部答案判斷正確還是錯(cuò)誤的過(guò)程。

  • 階段二:清理

在第一階段中所有可回收的的內(nèi)容都能夠被垃圾收集器進(jìn)行回收。如果一個(gè)對(duì)象被判定為是可以回收的對(duì)象,那么這個(gè)對(duì)象就被放在一個(gè) finalization queue(回收隊(duì)列)中,并在稍后會(huì)由一個(gè)虛擬機(jī)自動(dòng)建立的、低優(yōu)先級(jí)的 finalizer 線程去執(zhí)行它。

  • 階段三:整理(可選)

一些收集器有第三個(gè)步驟,整理。在這個(gè)步驟中,GC 將對(duì)象移動(dòng)到垃圾收集器回收完對(duì)象后所留下的自由空間中。這么做可以防止堆碎片化,防止大對(duì)象在堆中由于堆空間的不連續(xù)性而無(wú)法分配的情況。

所以上面的過(guò)程中就涉及到一個(gè)根節(jié)點(diǎn)(GC Roots) 來(lái)判斷是否存在需要回收的對(duì)象。這個(gè)算法的基本思想就是通過(guò)一系列的 GC Roots 作為起始點(diǎn),從這些節(jié)點(diǎn)向下搜索,搜索所走過(guò)的路徑稱為 引用鏈(Reference Chain),當(dāng)一個(gè)對(duì)象到 GC Roots 之間沒(méi)有任何引用鏈相連的話,則證明此對(duì)象不可用。引用鏈上的任何一個(gè)能夠被訪問(wèn)的對(duì)象都是強(qiáng)引用 對(duì)象,垃圾收集器不會(huì)回收強(qiáng)引用對(duì)象。

因此,返回到 foo() 方法中,僅在執(zhí)行方法時(shí),參數(shù) bar 和局部變量 baz 才是強(qiáng)引用。一旦方法執(zhí)行完成,它們都超過(guò)了作用域的時(shí)候,它們引用的對(duì)象都會(huì)進(jìn)行垃圾回收。

下面來(lái)考慮一個(gè)例子

  1. LinkedList foo = new LinkedList(); 
  2. foo.add(new Integer(111)); 

變量 foo 是一個(gè)強(qiáng)引用,它指向一個(gè) LinkedList 對(duì)象。LinkedList(JDK.18) 是一個(gè)鏈表的數(shù)據(jù)結(jié)構(gòu),每一個(gè)元素都會(huì)指向前驅(qū)元素,每個(gè)元素都有其后繼元素。

當(dāng)我們調(diào)用add() 方法時(shí),我們會(huì)增加一個(gè)新的鏈表元素,并且該鏈表元素指向值為 111 的 Integer 實(shí)例。這是一連串的強(qiáng)引用,也就是說(shuō),這個(gè) Integer 的實(shí)例不符合垃圾收集條件。一旦 foo 對(duì)象超出了程序運(yùn)行的作用域,LinkedList 和其中的引用內(nèi)容都可以進(jìn)行收集,收集的前提是沒(méi)有強(qiáng)引用關(guān)系。

Finalizers

C++ 允許對(duì)象定義析構(gòu)函數(shù)方法:當(dāng)對(duì)象超出作用范圍或被明確刪除時(shí),會(huì)調(diào)用析構(gòu)函數(shù)來(lái)清理使用的資源。對(duì)于大多數(shù)對(duì)象來(lái)說(shuō),析構(gòu)函數(shù)能夠釋放使用 new 或者 malloc 函數(shù)分配的內(nèi)存。在Java中,垃圾收集器會(huì)為你自動(dòng)清除對(duì)象,分配內(nèi)存,因此不需要顯式析構(gòu)函數(shù)即可執(zhí)行此操作。這也是 Java 和 C++ 的一大區(qū)別。

然而,內(nèi)存并不是唯一需要被釋放的資源??紤] FileOutputStream:當(dāng)你創(chuàng)建此對(duì)象的實(shí)例時(shí),它從操作系統(tǒng)分配文件句柄。如果你讓流的引用在關(guān)閉前超過(guò)了其作用范圍,該文件句柄會(huì)怎么樣?實(shí)際上,每個(gè)流都會(huì)有一個(gè) finalizer 方法,這個(gè)方法是垃圾回收器在回收之前由 JVM 調(diào)用的方法。對(duì)于 FileOutputStream 來(lái)說(shuō),finalizer 方法會(huì)關(guān)閉流,釋放文件句柄給操作系統(tǒng),然后清除緩沖區(qū),確保數(shù)據(jù)能夠?qū)懭氪疟P(pán)。

任何對(duì)象都具有 finalizer 方法,你要做的就是聲明 finalize() 方法。如下

  1. protected void finalize() throws Throwable 
  2.     // 清除對(duì)象 

雖然 finalizers 的 finalize() 方法是一種好的清除方式,但是這種方法產(chǎn)生的負(fù)面影響非常大,你不應(yīng)該依靠這個(gè)方法來(lái)做任何垃圾回收工作。因?yàn)?finalize 方法的運(yùn)行開(kāi)銷比較大,不確定性強(qiáng),無(wú)法保證各個(gè)對(duì)象的調(diào)用順序。finalize 能做的任何事情,可以使用 try-finally 或者其他方式來(lái)做,甚至做的更好。

對(duì)象的生命周期

綜上所述,可以通過(guò)下面的流程來(lái)對(duì)對(duì)象的生命周期做一個(gè)總結(jié)

對(duì)象被創(chuàng)建并初始化,對(duì)象在運(yùn)行時(shí)被使用,然后離開(kāi)對(duì)象的作用域,對(duì)象會(huì)變成不可達(dá)并會(huì)被垃圾收集器回收。圖中用紅色標(biāo)明的區(qū)域表示對(duì)象處于強(qiáng)可達(dá)階段。

JDK1.2 介紹了 java.lang.ref 包,對(duì)象的生命周期有四個(gè)階段:􏲧強(qiáng)可達(dá)􏰛(Strongly Reachable􏰜)、軟可達(dá)(Soft Reachable􏰜)、弱可達(dá)(Weak Reachable􏰜)、 幻象可達(dá)(Phantom Reachable􏰜)。

如果只討論符合垃圾回收條件的對(duì)象,那么只有三種:軟可達(dá)、弱可達(dá)和幻象可達(dá)。

  • 軟可達(dá):軟可達(dá)就是􏱬我們只能通過(guò)軟引用􏳂才能訪問(wèn)的狀態(tài),軟可達(dá)的對(duì)象是由 SoftReference 引用的對(duì)象,并且沒(méi)有強(qiáng)引用的對(duì)象。軟引用是用來(lái)描述一些還有用但是非必須的對(duì)象。垃圾收集器會(huì)盡可能長(zhǎng)時(shí)間的保留軟引用的對(duì)象,但是會(huì)在發(fā)生 OutOfMemoryError 之前,回收軟引用的對(duì)象。如果回收完軟引用的對(duì)象,內(nèi)存還是不夠分配的話,就會(huì)直接拋出 OutOfMemoryError。
  • 弱可達(dá):弱可達(dá)的對(duì)象是 WeakReference 引用的對(duì)象。垃圾收集器可以隨時(shí)收集弱引用的對(duì)象,不會(huì)嘗試保留軟引用的對(duì)象。
  • 幻象可達(dá):幻象可達(dá)是由 PhantomReference 引用的對(duì)象,幻象可達(dá)就是沒(méi)有強(qiáng)、軟、弱引用進(jìn)行關(guān)聯(lián),并且已經(jīng)被 finalize 過(guò)了,只有幻象引用指向這個(gè)對(duì)象的時(shí)候。

除此之外,還有強(qiáng)可達(dá)和不可達(dá)的兩種可達(dá)性判斷條件

  • 強(qiáng)可達(dá):就是一個(gè)對(duì)象剛被創(chuàng)建、初始化、使用中的對(duì)象都是處于強(qiáng)可達(dá)的狀態(tài)
  • 不可達(dá)(unreachable):處于不可達(dá)的對(duì)象就意味著對(duì)象可以被清除了。

下面是一個(gè)不同可達(dá)性狀態(tài)的轉(zhuǎn)換圖

判斷可達(dá)性條件,也是 JVM 垃圾收集器決定如何處理對(duì)象的一部分考慮因素。

所有的對(duì)象可達(dá)性引用都是 java.lang.ref.Reference 的子類,它里面有一個(gè)get() 方法,返回引用對(duì)象。如果已通過(guò)程序或垃圾收集器清除了此引用對(duì)象,則此方法返回 null 。也就是說(shuō),除了幻象引用外,軟引用和弱引用都是可以得到對(duì)象的。而且這些對(duì)象可以人為拯救,變?yōu)閺?qiáng)引用,例如把 this 關(guān)鍵字賦值給對(duì)象,只要重新和引用鏈上的任意一個(gè)對(duì)象建立關(guān)聯(lián)即可。

Reference

Queue引用隊(duì)列又稱為 ReferenceQueue,它位于 java.lang.ref 包下。我們?cè)?#1113107;建各種引用(軟引用,弱引用,幻象引用)并關(guān)聯(lián)到響應(yīng)對(duì)象􏰐時(shí),可以選擇是否需要關(guān)聯(lián)引用隊(duì)列。JVM 會(huì)在特定的時(shí)機(jī)將引用入隊(duì)到隊(duì)列中,程序可以通過(guò)判斷引用隊(duì)列中是否已經(jīng)加入引用,來(lái)了解被引用的對(duì)象是否被GC回收。

Reference

java.lang.ref.Reference 為軟(soft)引用、弱(weak)引用、虛(phantom)引用的父類。因?yàn)? Reference 對(duì)象和垃圾回收密切配合實(shí)現(xiàn),該類可能不能被直接子類化。

 

責(zé)任編輯:華軒 來(lái)源: Java建設(shè)者
相關(guān)推薦

2020-04-23 10:49:26

垃圾回收 C++Java

2021-01-04 10:08:07

垃圾回收Java虛擬機(jī)

2022-01-20 10:34:49

JVM垃圾回收算法

2017-08-04 10:53:30

回收算法JVM垃圾回收器

2022-03-21 11:33:11

JVM垃圾回收器垃圾回收算法

2021-11-05 15:23:20

JVM回收算法

2021-11-16 15:26:23

強(qiáng)化學(xué)習(xí)火箭人工智能

2023-12-19 21:52:51

Go垃圾回收開(kāi)發(fā)

2009-06-25 17:48:24

Java垃圾回收

2020-07-09 08:26:42

Kubernetes容器開(kāi)發(fā)

2021-03-03 08:13:57

模式垃圾回收

2017-10-12 11:48:09

iOS系統(tǒng)彈窗Apple ID

2010-12-13 11:14:04

Java垃圾回收算法

2023-08-08 10:29:55

JVM優(yōu)化垃圾回收

2022-06-22 09:54:45

JVM垃圾回收Java

2009-08-21 17:31:58

C#垃圾回收

2022-10-08 18:25:22

Python內(nèi)存管理GC

2009-07-06 17:34:22

Java垃圾回收

2009-06-23 14:15:00

Java垃圾回收

2010-09-25 15:33:19

JVM垃圾回收
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)