欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺談JVM垃圾回收之哪些對(duì)象可以被回收

 更新時(shí)間:2021年03月19日 11:57:40   作者:一個(gè)上進(jìn)的螺絲釘  
這篇文章主要介紹了JVM垃圾回收之哪些對(duì)象可以被回收,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

1.背景

Java語言相比于C和C++,一個(gè)最大的特點(diǎn)就是不需要程序員自己手動(dòng)去申請(qǐng)和釋放內(nèi)存,這一切交由JVM來完成。在Java中,運(yùn)行時(shí)的數(shù)據(jù)區(qū)域分為程序計(jì)數(shù)器、Java虛擬機(jī)棧、本地方法棧、方法區(qū)和堆。其中,程序計(jì)數(shù)器、虛擬機(jī)棧和本地方法棧是線程私有的,線程銷毀后自動(dòng)釋放。垃圾回收的行為發(fā)生在堆和方法區(qū),主要是堆,而堆中存儲(chǔ)的主要是對(duì)象。那么自然而然地就會(huì)有這么幾個(gè)問題,哪些對(duì)象可以被回收?通過什么方式回收?本文主要探討第一個(gè)問題,以及JVM對(duì)Java中幾種引用的回收策略。

2.如何判斷一個(gè)對(duì)象是否可以被回收

2.1 引用計(jì)數(shù)法

主要思想是:給對(duì)象添加一個(gè)引用計(jì)數(shù)器,這個(gè)對(duì)象被引用一次,計(jì)數(shù)器就加1;不再引用了,計(jì)數(shù)器就減1。如果一個(gè)對(duì)象的引用計(jì)數(shù)器為0,說明沒有人使用這個(gè)對(duì)象,那么這個(gè)對(duì)象就可以被回收了。這種方法實(shí)現(xiàn)起來比較簡(jiǎn)單,效率也比較高,大多數(shù)情況下都是有效的。但是,這種方法有一個(gè)漏洞。比如A.property = B,B.property = A,A和B兩個(gè)對(duì)象互相引用,并且沒有其他對(duì)象引用A和B。按照引用計(jì)數(shù)法的思想,A和B對(duì)象的引用計(jì)數(shù)器都不為0,都不能被釋放,但實(shí)際情況是A和B已經(jīng)沒人使用他們了,這就造成了內(nèi)存泄漏。所以,引用計(jì)數(shù)法雖然實(shí)現(xiàn)簡(jiǎn)單,但并不是一個(gè)完美的解決方案,實(shí)際中的Java也沒有采用它。

2.2 可達(dá)性分析算法

主要思想是:首先確定確定一系列肯定不能被回收的對(duì)象,即GC Roots。然后,從這些GC Roots出發(fā),向下搜索,去尋找它直接和間接引用的對(duì)象。最后,如果一個(gè)對(duì)象沒有被GC Roots直接或間接地引用,那么這個(gè)對(duì)象就可以被回收了。這種方法可以有效解決循環(huán)引用的問題,實(shí)際中Java也是采用這種判斷方法。那么問題來了,哪些對(duì)象可以作為GC Roots呢?這里可以使用MAT工具進(jìn)行觀察。運(yùn)行下面的demo:

import java.util.concurrent.TimeUnit;
 
public class GCRootsTest {
 public static void main(String[] args) throws InterruptedException {
  Object o = new Object();
  TimeUnit.SECONDS.sleep(100);
 }
}

主線程sleep的時(shí)候,在terminal窗口執(zhí)行jmap -dump:format=b,live,file=heapdump.bin 2872命令,生成堆轉(zhuǎn)儲(chǔ)快照dump文件,其中2872是進(jìn)程id,可以使用jps命令查看。然后使用MAT工具打開dump文件,可以很明顯地看到一共有四類對(duì)象可以作為GC Roots,下面詳細(xì)介紹下。

第一類,系統(tǒng)類對(duì)象(System Class)。比如,java.lang.String的Class對(duì)象,這個(gè)也很好理解,如果這些核心的系統(tǒng)類對(duì)象被回收了,程序就沒辦法運(yùn)行了。

第二類,native方法引用的對(duì)象。

第三類,活動(dòng)線程中正在引用的對(duì)象??梢钥闯?,代碼中變量o指向的Object對(duì)象可以被當(dāng)作GC Roots。

第四類,正在加鎖的對(duì)象。

3.Java中的幾種引用

在可達(dá)性分析算法中,判斷一個(gè)對(duì)象是不是可以被回收,主要看從GC Roots出發(fā)是否可以找到一個(gè)引用指向該對(duì)象。java中的引用一共有四種,按照引用的強(qiáng)弱依次為強(qiáng)引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)。這樣就可以對(duì)不同引用指向的對(duì)象采取不同的回收策略。比如一個(gè)強(qiáng)引用指向一個(gè)對(duì)象,那么這個(gè)對(duì)象肯定不會(huì)被回收,哪怕發(fā)生OOM。而對(duì)于弱引用指向的對(duì)象,只要發(fā)生垃圾回收,該對(duì)象就會(huì)被回收。下面詳細(xì)介紹下不同引用的用法。

3.1強(qiáng)引用

所謂強(qiáng)引用,就是平時(shí)使用最多的,類似于Object obj = new Object()的引用。垃圾回收器永遠(yuǎn)不會(huì)回收被強(qiáng)引用指向的對(duì)象。

3.2軟引用

軟引用,在Java中使用SoftReference類來實(shí)現(xiàn)軟引用。在下面的代碼中,softReference作為軟用指向一個(gè)Object對(duì)象,而otherObject變量可以通過軟引用的get方法間接引用到Object對(duì)象。

 public static void main(String[] args) {
  // 軟引用
  SoftReference<Object> softReference = new SoftReference<>(new Object());
  Object otherObject = softReference.get();
 }

對(duì)于軟引用指向的對(duì)象,當(dāng)內(nèi)存不夠用時(shí),該對(duì)象就會(huì)被回收。為演示這個(gè)現(xiàn)象,將JVM的堆內(nèi)存設(shè)置為10M(-Xms10M -Xmx10M)。以下代碼的主要邏輯是:向一個(gè)List集合中添加5個(gè)SoftReference對(duì)象,其中每個(gè)SoftReference對(duì)象都指向了一個(gè)大小為2M的byte數(shù)組,添加完成之后遍歷List,并打印List中每一個(gè)軟引用指向的對(duì)象。

public class ReferenceTest {
 
 private static final int _2M = 2 * 1024 * 1024;
 
 public static void main(String[] args) {
  List<SoftReference<Object>> list = new ArrayList<>();
  for (int i = 0; i < 5; i++) {
   SoftReference<Object> softReference = new SoftReference<>(new byte[_2M]);
   list.add(softReference);
  }
 
  System.out.println("List集合中的軟引用:");
  for (int i = 0; i < 5; i++) {
   System.out.println(list.get(i));
  }
 
  System.out.println("--------------------------");
  System.out.println("List集合中的軟引用指向的對(duì)象:");
  for (int i = 0; i < 5; i++) {
   System.out.println(list.get(i).get());
  }
 }
}

上述代碼在堆內(nèi)存為10M的情況下運(yùn)行的結(jié)果如下圖??梢钥吹角叭齻€(gè)軟引用指向的對(duì)象已經(jīng)被垃圾回收器回收掉了,原因就是堆內(nèi)存不夠用了,軟引用指向的對(duì)象就被回收了。

通常情況下,軟引用指向的對(duì)象被回收了,那么這個(gè)軟引用也就沒有存在的意義了,應(yīng)該被垃圾回收器回收掉。為了實(shí)現(xiàn)這個(gè)效果,通常軟引用要配合引用隊(duì)列使用。用法如下面的代碼所示,將軟引用和引用隊(duì)列關(guān)聯(lián),這樣當(dāng)軟引用指向的對(duì)象被回收時(shí),該軟引用會(huì)自動(dòng)加入到引用隊(duì)列,這時(shí)候可以采用一定的策略將這些軟引用對(duì)象回收。

public class ReferenceTest {
 
 private static final int _2M = 2 * 1024 * 1024;
 
 public static void main(String[] args) {
  List<SoftReference<Object>> list = new ArrayList<>();
  // 引用隊(duì)列
  ReferenceQueue<Object> queue = new ReferenceQueue<>();
  for (int i = 0; i < 5; i++) {
   // 同時(shí)將軟引用關(guān)聯(lián)引用隊(duì)列,當(dāng)軟引用指向的對(duì)象被回收時(shí),該軟引用會(huì)加入到隊(duì)列
   SoftReference<Object> softReference = new SoftReference<>(new byte[_2M], queue);
   list.add(softReference);
  }
 
  // 移除List中,指向?qū)ο笠呀?jīng)被回收的軟引用
  Reference<?> poll = queue.poll();
  while (null != poll) {
   list.remove(poll);
   poll = queue.poll();
  }
 
  System.out.println("List集合中的軟引用:");
  for (SoftReference<Object> reference : list) {
   System.out.println(reference);
  }
 
  System.out.println("-------------------------------------");
  System.out.println("List集合中的軟引用指向的對(duì)象:");
  for (SoftReference<Object> reference : list) {
   System.out.println(reference.get());
  }
 }
}

執(zhí)行結(jié)果如下:

3.3弱引用

弱引用,相比于軟引用,它的引用程度更弱。只要發(fā)生垃圾回收,弱引用指向的對(duì)象都會(huì)被回收。話不多說,直接上代碼。跟軟引用的demo差不多,唯一不同的是每個(gè)byte的數(shù)組的大小變成了2K,這樣堆肯定放的下,也不會(huì)發(fā)生垃圾回收。

public class WeakReferenceTest {
 private static final int _2K = 2 * 1024;
 
 public static void main(String[] args) {
  List<WeakReference<byte[]>> list = new ArrayList<>();
  for (int i = 0; i < 5; i++) {
   WeakReference<byte[]> reference = new WeakReference<>(new byte[_2K]);
   list.add(reference);
  }
 
  System.out.println("List集合中的軟引用:");
  for (WeakReference<byte[]> reference : list) {
   System.out.println(reference);
  }
 
  System.out.println("-------------------------------------");
  System.out.println("List集合中的軟引用指向的對(duì)象:");
  for (WeakReference<byte[]> reference: list) {
   System.out.println(reference.get());
  }
 }
}

運(yùn)行。可以看到弱引用指向的對(duì)象并沒有被回收。

在上述代碼的基礎(chǔ)上,人為的進(jìn)行一次垃圾回收,代碼如下。

public class WeakReferenceTest {
 private static final int _2K = 2 * 1024;
 
 public static void main(String[] args) {
  List<WeakReference<byte[]>> list = new ArrayList<>();
  for (int i = 0; i < 5; i++) {
   WeakReference<byte[]> reference = new WeakReference<>(new byte[_2K]);
   list.add(reference);
  }
 
  System.gc(); // 手動(dòng)垃圾回收
  System.out.println("List集合中的弱引用:");
  for (WeakReference<byte[]> reference : list) {
   System.out.println(reference);
  }
 
  System.out.println("-------------------------------------");
  System.out.println("List集合中的弱引用指向的對(duì)象:");
  for (WeakReference<byte[]> reference: list) {
   System.out.println(reference.get());
  }
 }
}

運(yùn)行。發(fā)現(xiàn)此時(shí)弱引用指向的對(duì)象都被回收掉了。和軟引用一樣,弱引用也可以結(jié)合引用隊(duì)列使用,這里不再贅述。

3.4虛引用

與軟引用和虛引用不同,虛引用必須配合引用隊(duì)列使用,而且不能通過虛引用獲取到虛引用指向的對(duì)象。在Java中虛引用使用PhantomReference類來表示,從PhantomReference的源碼可以看出調(diào)用虛引用的get方法始終返回的是null,而且PhantomReference只提供了包含引用隊(duì)列的有參構(gòu)造器,這也就是說虛引用必須結(jié)合引用隊(duì)列使用。

public class PhantomReference<T> extends Reference<T> {
 
 public T get() {
  return null;
 }
 
 public PhantomReference(T referent, ReferenceQueue<? super T> q) {
  super(referent, q);
 }
 
}

既然不能通過虛引用獲取到它指向的對(duì)象,那么虛引用到底有什么用呢?實(shí)際上,為一個(gè)對(duì)象關(guān)聯(lián)虛引用的唯一目的就是:在​該對(duì)象被垃圾回收時(shí)收到一個(gè)系統(tǒng)通知。當(dāng)垃圾回收器準(zhǔn)備回收一個(gè)對(duì)象時(shí),如果發(fā)現(xiàn)還有虛引用與之關(guān)聯(lián),就會(huì)在垃圾回收后,將這個(gè)虛引用加入引用隊(duì)列,在其關(guān)聯(lián)的虛引用出隊(duì)前,不會(huì)徹底銷毀該對(duì)象。 上面的描述還是不夠通俗易懂,其實(shí)虛引用的一個(gè)經(jīng)典的使用場(chǎng)景就是和DirectByteBuffer類關(guān)聯(lián)使用。DirectByteBuffer類使用的是堆外內(nèi)存(服務(wù)器內(nèi)存中,除了JVM占用外的那部分),省去了數(shù)據(jù)到內(nèi)核的拷貝,因此效率比ByteBuffer要高很多(這里的重點(diǎn)是虛引用,想要了解DirectByteBuffer類的底層原理,可以在網(wǎng)上找下資源),它的內(nèi)存示意圖如下。

雖然DirectByteBuffer類的效率很高,但是由于堆外內(nèi)存JVM的垃圾回收器不能進(jìn)行回收,所以要謹(jǐn)慎處理DirectByteBuffer類使用的堆外內(nèi)存,否則極易造成服務(wù)器內(nèi)存泄漏。為了解決這個(gè)問題,虛引用就派上用場(chǎng)了。DirectByteBuffer類的創(chuàng)建和回收主要分為以下幾個(gè)步驟

創(chuàng)建DirecByteBuffer對(duì)象時(shí)會(huì)同時(shí)創(chuàng)建一個(gè)Cleaner虛引用對(duì)象,指向自己,同時(shí)傳一個(gè)Deallocator對(duì)象給Cleaner

 Cleaner類的父類是PhantomReference,爺爺類是Reference。Reference類在初始化的時(shí)候會(huì)啟動(dòng)一個(gè)ReferenceHandler線程

當(dāng)DirectByteBuffer對(duì)象被回收后,Cleaner對(duì)象會(huì)被加入引用隊(duì)列

這時(shí)ReferenceHandler線程會(huì)調(diào)用Cleaner對(duì)象的clean方法完成對(duì)堆外內(nèi)存的回收

clean方法會(huì)調(diào)用Deallocator的run方法,通過Unsafe類最終完成堆外內(nèi)存的回收

總結(jié)起來就是一句話,用虛引用關(guān)聯(lián)DirectByteBuffer對(duì)象,當(dāng)DirectByteBuffer被回收后,虛引用對(duì)象會(huì)被加入到引用隊(duì)列,進(jìn)而由該虛引用對(duì)象完成對(duì)堆外內(nèi)存的釋放。(感興趣的或伙伴可以跟以下DirectByteBuffer的源碼)

4.總結(jié)

  • JVM采用可達(dá)性分析算法來判斷堆中有哪些對(duì)象可以被回收。
  • 主要有四類對(duì)象可作為GC Roots:系統(tǒng)類對(duì)象、Native方法引用的對(duì)象、活動(dòng)線程引用的對(duì)象以及正在加鎖的對(duì)象。
  • Java中常用的引用主要有四種,強(qiáng)引用、軟引用、弱引用和虛引用,對(duì)不同引用指向的對(duì)象,JVM有不同的回收策略。
  • 對(duì)于強(qiáng)引用指向的對(duì)象,垃圾回收器不會(huì)將其回收,即使是發(fā)生OOM。
  • 對(duì)于軟引用指向的對(duì)象,當(dāng)內(nèi)存不夠時(shí),垃圾回收器會(huì)將其回收。這個(gè)特點(diǎn)可以用來實(shí)現(xiàn)緩存,當(dāng)內(nèi)存不足時(shí)JVM會(huì)自動(dòng)清理掉這些緩存。
  • 對(duì)于弱引用指向的對(duì)象,當(dāng)發(fā)生垃圾回收時(shí),垃圾回收器會(huì)將其回收。
  • 對(duì)于虛引用,必須配合引用隊(duì)列使用,而且不能通過虛引用獲取到虛引用指向的對(duì)象,為一個(gè)對(duì)象關(guān)聯(lián)虛引用的唯一目的就是在​該對(duì)象被垃圾回收時(shí)收到一個(gè)系統(tǒng)通知。

到此這篇關(guān)于JVM垃圾回收之哪些對(duì)象可以被回收的文章就介紹到這了,更多相關(guān)JVM垃圾回收之哪些對(duì)象可以被回收內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:

相關(guān)文章

  • 一種類似JAVA線程池的C++線程池實(shí)現(xiàn)方法

    一種類似JAVA線程池的C++線程池實(shí)現(xiàn)方法

    線程池(thread pool)是一種線程使用模式。線程過多或者頻繁創(chuàng)建和銷毀線程會(huì)帶來調(diào)度開銷,進(jìn)而影響緩存局部性和整體性能。這篇文章主要介紹了一種類似JAVA線程池的C++線程池實(shí)現(xiàn)方法,需要的朋友可以參考下
    2019-07-07
  • 解決Intellij IDEA 使用Spring-boot-devTools無效的問題

    解決Intellij IDEA 使用Spring-boot-devTools無效的問題

    下面小編就為大家?guī)硪黄鉀QIntellij IDEA 使用Spring-boot-devTools無效的問題。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-07-07
  • Spring?boot?security權(quán)限管理集成cas單點(diǎn)登錄功能的實(shí)現(xiàn)

    Spring?boot?security權(quán)限管理集成cas單點(diǎn)登錄功能的實(shí)現(xiàn)

    這篇文章主要介紹了Spring?boot?security權(quán)限管理集成cas單點(diǎn)登錄,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-03-03
  • spring boot 項(xiàng)目中使用thymeleaf模板的案例分析

    spring boot 項(xiàng)目中使用thymeleaf模板的案例分析

    這篇文章主要介紹了spring boot 項(xiàng)目中使用thymeleaf模板的案例分析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-09-09
  • Java Web開發(fā)中過濾器和監(jiān)聽器使用詳解

    Java Web開發(fā)中過濾器和監(jiān)聽器使用詳解

    這篇文章主要為大家詳細(xì)介紹了Java中的過濾器Filter和監(jiān)聽器Listener的使用以及二者的區(qū)別,文中的示例代碼講解詳細(xì),需要的可以參考一下
    2022-10-10
  • Eclipse?Jetty?server漏洞解決辦法

    Eclipse?Jetty?server漏洞解決辦法

    最近給?個(gè)客戶部署項(xiàng)?,但是客戶的安全稽核有點(diǎn)變態(tài),居然說 Eclipse Jetty Server?危漏洞,這篇文章主要給大家介紹了關(guān)于Eclipse?Jetty?server漏洞解決的相關(guān)資料,需要的朋友可以參考下
    2023-11-11
  • SpringBoot返回結(jié)果統(tǒng)一處理實(shí)例詳解

    SpringBoot返回結(jié)果統(tǒng)一處理實(shí)例詳解

    這篇文章主要為大家介紹了SpringBoot返回結(jié)果統(tǒng)一處理實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • java實(shí)現(xiàn)簡(jiǎn)單的爬蟲之今日頭條

    java實(shí)現(xiàn)簡(jiǎn)單的爬蟲之今日頭條

    最近在學(xué)習(xí)搜索方面的東西,需要了解網(wǎng)絡(luò)爬蟲方面的知識(shí),雖然有很多開源的強(qiáng)大的爬蟲,但本著學(xué)習(xí)的態(tài)度,想到之前在做資訊站的時(shí)候需要用到爬蟲來獲取一些文章,今天剛好有空就研究了一下.在網(wǎng)上看到了一個(gè)demo,使用的是Jsoup,我拿過來修改了一下,有需要的朋友可以參考
    2016-11-11
  • java排序去重示例分享

    java排序去重示例分享

    這篇文章主要介紹了java排序去重示例,對(duì)String strs = "ZZZ BBB AAA OOO ZZZ AAA ZZZ"計(jì)算出現(xiàn)個(gè)數(shù),排序去重,需要的朋友可以參考下
    2014-02-02
  • Mybatis中的config.xml配置文件詳細(xì)解析

    Mybatis中的config.xml配置文件詳細(xì)解析

    這篇文章主要介紹了詳解Mybatis-config.xml配置文件,需要的朋友可以參考下
    2017-12-12

最新評(píng)論