天天看點

GC垃圾回收面試看這篇就夠了

GC

你知道哪些垃圾回收算法?

垃圾回收從理論上非常容易了解,具體的方法有以下幾種:

  1. 标記-清除
  2. 标記-複制
  3. 标記-整理
  4. 分代回收

如何判斷一個對象是否應該被回收

這就是所謂的對象存活性判斷,常用的方法有兩種:1.引用計數法; 2.對象可達性分析。由

于引用計數法存在互相引用導緻無法進行 GC 的問題,是以目前 JVM

虛拟機多使用對象可達 性分析算法。

簡單的解釋一下垃圾回收

Java 垃圾回收機制最基本的做法是分代回收。記憶體中的區域被劃分成不同的世代,對象根

據其存活的時間被儲存在對應世代的區域中。一般的實作是劃分成 3

個世代:年輕、年老和

永久。記憶體的配置設定是發生在年輕世代中的。當一個對象存活時間足夠長的時候,它就會被複

制到年老世代中。對于不同的世代可以使用不同的垃圾回收算法。進行世代劃分的出發點是

對應用中對象存活時間進行研究之後得出的統計規律。一般來說,一個應用中的大部分對象

的存活時間都很短。比如局部變量的存活時間就隻在方法的執行過程中。基于這一點,對于

年輕世代的垃圾回收算法就可以很有針對性。

java 當中的四種引用

強引用,軟引用,弱引用,虛引用。不同的引用類型主要展現在 GC 上:

強引用:如果一個對象具有強引用,它就不會被垃圾回收器回收。即使目前記憶體空

間不足,JVM 也不會回收它,而是抛出 OutOfMemoryError 錯誤,使程式異常終止。

如果想中斷強引用和某個對象之間的關聯,可以顯式地将引用指派為 null,這樣一

來的話,JVM 在合适的時間就會回收該對象。

軟引用:在使用軟引用時,如果記憶體的空間足夠,軟引用就能繼續被使用,而不會

被垃圾回收器回收,隻有在記憶體不足時,軟引用才會被垃圾回收器回收。

虛引用:顧名思義,就是形同虛設,如果一個對象僅持有虛引用,那麼它相當于沒

有引用,在任何時候都可能被垃圾回收器回收。

調用 System.gc()會發生什麼?

通知 GC 開始工作,但是 GC 真正開始的時間不确定。

GC是什麼,為什麼要使用它?【阿斯拓】

GC是垃圾收集的意思(GabageCollection),記憶體處理是程式設計人員容易出現問題的地方,忘記或者錯誤的記憶體回收會導緻程式或系統的不穩定甚至崩潰,Java提供的GC功能**可以自動監測對象是否超過作用域,進而達到自動回收記憶體的目的,**Java語言沒有提供釋放已配置設定記憶體的顯示操作方法。

說一下垃圾回收的原理,可以直接從記憶體中回收嗎?

Java語言中一個顯著的特點就是引入了垃圾回收機制,使c++程式員最頭疼的記憶體管理的問題迎刃而解,它使得Java程式員在編寫程式的時候不再需要考慮記憶體管理。垃圾回收可以有效的防止記憶體洩露,有效的使用可以使用的記憶體。垃圾回收器通常是作為一個單獨的低級别的線程運作,不可預知的情況下對記憶體堆中已經死亡的或者長時間沒有使用的對象進行清除和回收,程式員不能實時的調用垃圾回收器對某個對象或所有對象進行垃圾回收,因為Java語言規範并不保證GC一定會執行。回收機制有分代複制垃圾回收和标記垃圾回收,增量垃圾回收。

垃圾回收機制概述

Java語言中一個顯著的特點就是引入了垃圾回收機制,使c++程式員最頭疼的記憶體管理的問題迎刃而解,它使得Java程式員在編寫程式的時候不再需要考慮記憶體管理。由于有個垃圾回收機制,Java中的對象不再有“作用域”的概念,隻有對象的引用才有“作用域”。垃圾回收可以有效的防止記憶體洩露,有效的使用空閑的記憶體。

ps:記憶體洩露是指該記憶體空間使用完畢之後未回收,在不涉及複雜資料結構的一般情況下,Java

的記憶體洩露表現為一個記憶體對象的生命周期超出了程式需要它的時間長度,我們有時也将其稱為“對象遊離”。

垃圾回收簡要過程

這裡必須點出一個很重要的誤區:不可達的對象并不會馬上就會被直接回收,而是至少要經過兩次标記的過程。** **

**        第一次被标記過的對象,會檢查該對象是否重寫了finalize()方法。如果重寫了該方法,則将其放入一個F-Query隊列中,否則,直接将對象加入“即将回收”集合。在第二次标記之前,F-Query隊列中的所有對象會逐個執行finalize()方法,但是不保證該隊列中所有對象的finalize()方法都能被執行,這是因為JVM建立一個低優先級的線程去運作此隊列中的方法,很可能在沒有周遊完之前,就已經被剝奪了運作的權利。那麼運作finalize()方法的意義何在呢?這是對象避免自己被清理的最後手段:如果在執行finalize()方法的過程中,使得此對象重新與GC

Roots引用鍊相連,則會在第二次标記過程中将此對象從F-Query隊列中清除,避免在這次回收中被清除,恢複成了一個“正常”的對象。但顯然這種好事不能無限的發生,對于曾經執行過一次finalize()的對象來說,之後如果再被标記,則不會再執行finalize()方法,隻能等待被清除的命運。 **

**        **之後,GC将對F-Queue中的對象進行第二次小規模的标記,将隊列中重新與GC

Roots引用鍊恢複連接配接的對象清除出“即将回收”集合。所有此集合中的内容将被回收。

手動GC回收

public class JVMDemo05 { public static void main(String[] args) { JVMDemo05 jvmDemo05 = new JVMDemo05(); //jvmDemo05 = null; System.gc(); } protected void finalize() throws Throwable { System.out.println(“gc在回收對象…”); } }

finalize作用

Java技術使用finalize()方法在垃圾收集器将對象從記憶體中清除出去前,做必要的清理工作。這個方法是由垃圾收集器在确定這個對象沒有被引用時對這個對象調用的。它是在Object類中定義的,是以所有的類都繼承了它。子類覆寫finalize()方法以整理系統資源或者執行其他清理工作。finalize()方法是在垃圾收集器删除對象之前對這個對象調用的。

垃圾回收機制算法

引用計數法

概述

給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器都為0的對象就是不再被使用的,垃圾收集器将回收該對象使用的記憶體。

優缺點

優點:

引用計數收集器可以很快的執行,交織在程式運作中。對程式需要不被長時間打斷的實時環境比較有利。

缺點:

無法檢測出循環引用。如父對象有一個對子對象的引用,子對象反過來引用父對象。這樣,他們的引用計數永遠不可能為0.而且每次加減非常浪費記憶體。

标記清除算法

标記-清除(Mark-Sweep)算法顧名思義,主要就是兩個動作,一個是标記,另一個就是清除。

标記就是根據特定的算法(如:引用計數算法,可達性分析算法等)标出記憶體中哪些對象可以回收,哪些對象還要繼續用。

标記訓示回收,那就直接收掉;标記訓示對象還能用,那就原地不動留下。

GC垃圾回收面試看這篇就夠了

缺點

  1. 标記與清除效率低;
  2. 清除之後記憶體會産生大量碎片;

是以碎片這個問題還得處理,怎麼處理,看标記-整理算法。

複制算法

S0和s1将可用記憶體按容量分成大小相等的兩塊,每次隻使用其中一塊,當這塊記憶體使用完了,就将還存活的對象複制到另一塊記憶體上去,然後把使用過的記憶體空間一次清理掉。這樣使得每次都是對其中一塊記憶體進行回收,記憶體配置設定時不用考慮記憶體碎片等複雜情況,隻需要移動堆頂指針,按順序配置設定記憶體即可,實作簡單,運作高效。

複制算法的缺點顯而易見,可使用的記憶體降為原來一半。

複制算法用于在新生代垃圾回收

标記-壓縮算法

标記壓縮法在标記清除基礎之上做了優化,把存活的對象壓縮到記憶體一端,而後進行垃圾清理。(java中老年代使用的就是标記壓縮法)

分代收集算法

根據記憶體中對象的存活周期不同,将記憶體劃分為幾塊,java的虛拟機中一般把記憶體劃分為新生代和年老代,當新建立對象時一般在新生代中配置設定記憶體空間,當新生代垃圾收集器回收幾次之後仍然存活的對象會被移動到年老代記憶體中,當大對象在新生代中無法找到足夠的連續記憶體時也直接在年老代中建立。

對于新生代和老年代來說,新生代回收頻率很高,但是每次回收耗時很短,而老年代回收頻率較低,但是耗時會相對較長,是以應該盡量減少老年代的GC.

為什麼老年代使用标記壓縮、新生代使用複制算法。
垃圾回收時的停頓現象

垃圾回收的任務是識别和回收垃圾對象進行記憶體清理,為了讓垃圾回收器可以更高效的執行,大部分情況下,會要求系統進如一個停頓的狀态。停頓的目的是為了終止所有的應用線程,隻有這樣的系統才不會有新垃圾的産生。同時停頓保證了系統狀态在某一個瞬間的一緻性,也有利于更好的标記垃圾對象。是以在垃圾回收時,都會産生應用程式的停頓。

垃圾收集器

什麼是Java垃圾回收器

Java垃圾回收器是Java虛拟機(JVM)的三個重要子產品(另外兩個是解釋器和多線程機制)之一,為應用程式提供記憶體的自動配置設定(Memory

Allocation)、自動回收(Garbage

Collect)功能,這兩個操作都發生在Java堆上(一段記憶體快)。某一個時點,一個對象如果有一個以上的引用(Rreference)指向它,那麼該對象就為活着的(Live),否則死亡(Dead),視為垃圾,可被垃圾回收器回收再利用。垃圾回收操作需要消耗CPU、線程、時間等資源,是以容易了解的是垃圾回收操作不是實時的發生(對象死亡馬上釋放),當記憶體消耗完或者是達到某一個名額(Threshold,使用記憶體占總記憶體的比列,比如0.75)時,觸發垃圾回收操作。有一個對象死亡的例外,java.lang.Thread類型的對象即使沒有引用,隻要線程還在運作,就不會被回收。

串行回收器(Serial Collector)

單線程執行回收操作,回收期間暫停所有應用線程的執行,client模式下的預設回收器,通過-XX:+UseSerialGC指令行可選項強制指定。參數可以設定使用新生代串行和老年代串行回收器

年輕代的回收算法(Minor Collection)

把Eden區的存活對象移到To區,To區裝不下直接移到年老代,把From區的移到To區,To區裝不下直接移到年老代,From區裡面年齡很大的更新到年老代。

回收結束之後,Eden和From區都為空,此時把From和To的功能互換,From變To,To變From,每一輪回收之前To都是空的。設計的選型為複制。

年老代的回收算法(Full Collection)

年老代的回收分為三個步驟,标記(Mark)、清除(Sweep)、合并(Compact)。标記階段把所有存活的對象标記出來,清除階段釋放所有死亡的對象,合并階段

把所有活着的對象合并到年老代的前部分,把空閑的片段都留到後面。設計的選型為合并,減少記憶體的碎片。

并行回收

并行回收器(ParNew回收器)

并行回收器在串行回收器基礎上做了改進,他可以使用多個線程同時進行垃

圾回收,對于計算能力強的計算機而言,可以有效的縮短垃圾回收所需的尖

際時間。

ParNew回收器是一個工作在新生代的垃圾收集器,他隻是簡單的将串行回收

器多線程快他的回收政策和算法和串行回收器一樣。

使用XX:+UseParNewGC 新生代ParNew回收器,老年代則使用市行回收器

ParNew回收器工作時的線程數量可以使用XX:ParaleiGCThreads參數指

定,一般最好和計算機的CPU相當,避免過多的栽程影響性能。

并行回收集器(ParallelGC)

老年代ParallelOldGC回收器也是一種多線程的回收器,和新生代的

ParallelGC回收器一樣,也是一種關往吞吐量的回收器,他使用了标記壓縮

算法進行實作。

-XX:+UseParallelOldGC 進行設定

-XX:+ParallelCThread也可以設定垃圾收集時的線程教量。

串行并行回收差別

串行單核,并行多核,後者效率高。

并CMS(并發GC)收集器

CMS(Concurrent Mark

Sweep)收集器是一種以擷取最短回收停頓時間為目标的收集器。CMS收集器是基于“标記-清除”算法實作的,整個收集過程大緻分為4個步驟:

①.初始标記(CMS initial mark)

②.并發标記(CMS concurrenr mark)

③.重新标記(CMS remark)

④.并發清除(CMS concurrent sweep)

其中初始标記、重新标記這兩個步驟任然需要停頓其他使用者線程。初始标記僅僅隻是标記出GC

ROOTS能直接關聯到的對象,速度很快,并發标記階段是進行GC ROOTS

根搜尋算法階段,會判定對象是否存活。而重新标記階段則是為了修正并發标記期間,因使用者程式繼續運作而導緻标記産生變動的那一部分對象的标記記錄,這個階段的停頓時間會被初始标記階段稍長,但比并發标記階段要短。

由于整個過程中耗時最長的并發标記和并發清除過程中,收集器線程都可以與使用者線程一起工作,是以整體來說,CMS收集器的記憶體回收過程是與使用者線程一起并發執行的。

CMS收集器的優點:并發收集、低停頓,但是CMS還遠遠達不到完美,器主要有三個顯著缺點:

CMS收集器對CPU資源非常敏感。在并發階段,雖然不會導緻使用者線程停頓,但是會占用CPU資源而導緻引用程式變慢,總吞吐量下降。CMS預設啟動的回收線程數是:(CPU數量+3)

/ 4。

CMS收集器無法處理浮動垃圾,可能出現“Concurrent Mode

Failure“,失敗後而導緻另一次Full

 GC的産生。由于CMS并發清理階段使用者線程還在運作,伴随程式的運作自熱會有新的垃圾不斷産生,這一部分垃圾出現在标記過程之後,CMS無法在本次收集中處理它們,隻好留待下一次GC時将其清理掉。這一部分垃圾稱為“浮動垃圾”。也是由于在垃圾收集階段使用者線程還需要運作,

即需要預留足夠的記憶體空間給使用者線程使用,是以CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進行收集,需要預留一部分記憶體空間提供并發收集時的程式運作使用。在預設設定下,CMS收集器在老年代使用了68%的空間時就會被激活,也可以通過參數-XX:CMSInitiatingOccupancyFraction的值來提供觸發百分比,以降低記憶體回收次數提高性能。要是CMS運作期間預留的記憶體無法滿足程式其他線程需要,就會出現“Concurrent

Mode Failure”失敗,這時候虛拟機将啟動後備預案:臨時啟用Serial

Old收集器來重新進行老年代的垃圾收集,這樣停頓時間就很長了。是以說參數-XX:CMSInitiatingOccupancyFraction設定的過高将會很容易導緻“Concurrent

Mode Failure”失敗,性能反而降低。

最後一個缺點,CMS是基于“标記-清除”算法實作的收集器,使用“标記-清除”算法收集後,會産生大量碎片。空間碎片太多時,将會給對象配置設定帶來很多麻煩,比如說大對象,記憶體空間找不到連續的空間來配置設定不得不提前觸發一次Full

 GC。為了解決這個問題,CMS收集器提供了一個-XX:UseCMSCompactAtFullCollection開關參數,用于在Full

 GC之後增加一個碎片整理過程,還可通過-XX:CMSFullGCBeforeCompaction參數設定執行多少次不壓縮的Full

 GC之後,跟着來一次碎片整理過程。

G1回收器

G1回收器(Garbage-First)實在]dk1.7中提出的垃圾回收器,從長期目标來看是為了取

代CMS回收器,G1回收器擁有獨特的垃圾回收政策,G1屬于分代垃圾回收器,區分

新生代和老年代,依然有eden和from/to區,它并不要求整個eden區或者新生代、老

年代的空間都連續,它使用了分區算法。

并行性: G1回收期間可多線程同時工作。

井發性G1擁有與應用程式交替執行能力,部分工作可與應用程式同時執行,在整個

GC期間不會完全阻塞應用程式。

分代GC:G1依然是一個分代的收集器,但是它是非兩新生代和老年代一杯政的雜尊。

空間基理,G1在國收過程中,不會微CMS那樣在若千tacAy 要進行碎片整理。

G1

來用了有效複制對象的方式,減少空間碎片。

利得程,用于分區的原因,G可以貝造取都分區城進行回收,帽小了國收的格想,

提升了性能。

使用.XXX:+UseG1GC 應用G1收集器,

Mills指定最大停頓時間

使用-XX:MaxGCPausel

設定并行回收的線程數量

使用-XX:ParallelGCThreads

調優總結

初始堆值和最大堆記憶體記憶體越大,吞吐量就越高。

最好使用并行收集器,因為并行手機器速度比串行吞吐量高,速度快。

設定堆記憶體新生代的比例和老年代的比例最好為1:2或者1:3。

減少GC對老年代的回收。

JVM工作原理?

運作jvm字元碼的工作是由解釋器來完成的。解釋執行過程分三步進行:

代碼的裝入、代碼的校驗、和代碼的執行。

裝入代碼的工作由“類裝載器classloader”完成。類裝載器負責裝入運作一個程式需要的所有代碼,這也包括程式代碼中的類所繼承的類和被調用的類。當類裝載器裝入一個類時,該類被放在自己的名字空間中。除了通過符号引用自己名字空間以外的類,類之間沒有其他辦法可以影響其他類。在本台計算機的所有類都在同一位址空間中,而所有從外部引進的類,都有一個自己獨立的名字空間。這使得本地類通過共享相同的名字空間獲得較高的運作效率,同時又保證它們與從外部引進的類不會互相影響。當裝入了運作程式需要的所有類後,解釋器便可确定整個可執行程式的記憶體布局。解釋器為符号引用與特定的位址空間建立對應關系及查詢表。通過在這一階段确定代碼的内布局,java很好地解決了由超類改變而使子類

崩潰的問題,同時也防止了代碼的非法通路。随後,被裝入的代碼由位元組碼校驗器進行檢查。校驗器可以發現操作數棧益處、非法資料類型轉化等多種錯誤。通過校驗後,代碼便開始執行了。

Java位元組碼的執行有兩種方式:

1)即時編譯方式:解釋器先将位元組編譯成機器碼,然後再執行該機器碼。

2)解釋執行方式:解釋器通過每次解釋并執行一小段代碼來完成java位元組。

碼程式的所有操作。

JVM加載class檔案原理?

所謂裝載就是尋找一個類或是一個接口的二進制形式并用該二進制形式來構造代表這個類或是這個接口的class對象的過程.

在Java中,類裝載器把一個類裝入Java虛拟機中,要經過三個步驟來完成:裝載、連結和初始化,其中連結又可以分成校驗、準備、解析

裝載:查找和導入類或接口的二進制資料;

連結:執行下面的校驗、準備和解析步驟,其中解析步驟是可以選擇的;

校驗:檢查導入類或接口的二進制資料的正确性;

準備:給類的靜态變量配置設定并初始化存儲空間;

解析:将符号引用轉成直接引用;

初始化:激活類的靜态變量的初始化Java代碼和靜态Java代碼塊

JVM中類的裝載是由ClassLoader和它的子類來實作的,JavaClassLoader是一個重要的Java運作時系統元件。它負責在運作時查找和裝入類檔案的類

一個Java應用程式使用兩種類型的類裝載器:根裝載器(bootstrap)和使用者定義的裝載器(user-defined)。

根裝載器以某種預設的方式将類裝入,包括那些JavaAPI的類。在運作期間一個Java程式能安裝使用者自己定義的類裝載器。根裝載器是虛拟機固有的一部分,而使用者定義的類裝載器則不是,它是用Java語言寫的,被編譯成class檔案之後然後再被裝入到虛拟機,并像其它的任何對象一樣可以被執行個體化。Java類裝載器的體系結構如下所示:

Bootstrap(根裝載器)

|

Extension(擴充裝載器)

|

System

|

UserDefine1

/\

UserDefine2UserDefine3

|

UserDefine4

Java的類裝載模型是一種代理(delegation)模型。當JVM要求類裝載器CL(ClassLoader)裝載一個類時,CL首先将這個類裝載請求轉發給他的父裝載器。隻有當父裝載器沒有裝載并無法裝載這個類時,CL才獲得裝載這個類的機會。這樣,所有類裝載器的代理關系構成了一種樹狀的關系。樹的根是類的根裝載器(bootstrapClassLoader),在JVM中它以"null"表示。除根裝載器以外的類裝載器有且僅有一個父裝載器。在建立一個裝載器時,如果沒有顯式地給出父裝載器,那麼JVM将預設系統裝載器為其父裝載器

下面針對各種類裝載器分别進行詳細的說明:

根(Bootstrap)裝載器:該裝載器沒有父裝載器,它是JVM實作的一部分,從sun.boot.class.path裝載運作時庫的核心代碼。

擴充(Extension)裝載器:繼承的父裝載器為根裝載器,不像根裝載器可能與運作時的作業系統有關,這個類裝載器是用純Java代碼實作的,它從java.ext.dirs(擴充目錄)中裝載代碼。

系統(SystemorApplication)裝載器:裝載器為擴充裝載器,我們都知道在安裝JDK的時候要設定環境變量(CLASSPATH),這個類裝載器就是從java.class.path(CLASSPATH環境變量)中裝載代碼的,它也是用純Java代碼實作的,同時還是使用者自定義類裝載器的預設父裝載器。

小應用程式(Applet)裝載器:裝載器為系統裝載器,它從使用者指定的網絡上的特定目錄裝載小應用程式代碼。

說說Java中的記憶體配置設定?

Java把記憶體分成兩種,一種叫做棧記憶體,一種叫做堆記憶體

在函數中定義的一些基本類型的變量和對象的引用變量都是在函數的棧記憶體中配置設定。當在一段代碼塊中定義一個變量時,java就在棧中為這個變量配置設定記憶體空間,當超過變量的作用域後,java會自動釋放掉為該變量配置設定的記憶體空間,該記憶體空間可以立刻被另作它用。

堆記憶體用于存放由new建立的對象和數組。在堆中配置設定的記憶體,由java虛拟機自動垃圾回收器來管理。在堆中産生了一個數組或者對象後,還可以在棧中定義一個特殊的變量,這個變量的取值等于數組或者對象在堆記憶體中的首位址,在棧中的這個特殊的變量就變成了數組或者對象的引用變量,以後就可以在程式中使用棧記憶體中的引用變量來通路堆中的數組或者對象,引用變量相當于為數組或者對象起的一個别名,或者代号。

引用變量是普通變量,定義時在棧中配置設定記憶體,引用變量在程式運作到作用域外釋放。而數組&對象本身在堆中配置設定,即使程式運作到使用new産生數組和對象的語句所在地代碼塊之外,數組和對象本身占用的堆記憶體也不會被釋放,數組和對象在沒有引用變量指向它的時候,才變成垃圾,不能再被使用,但是仍然占着記憶體,在随後的一個不确定的時間被垃圾回收器釋放掉。這個也是java比較占記憶體的主要原因。但是在寫程式的時候,可以人為的控制。

GC經常回收好不好

不好,GC在回收線程的時候,其他線程全部等待。時間很短,速度快,看不到效果。

為什麼老年代使用标記壓縮、新生代使用複制算法。
說下你熟悉那些jvm參數調優

-XX:+UseSerialGC 設定使用新生代串行和老年代串行回收器

XX:+UseParNewGC新生代并行回收器

串行回收和并行回收的差別

串行是單線程回收,效率較慢

并行回收是多線程回收,效率快。

cms也是并行。

ClassLoader如何加載class。

jvm裡有多個類加載,每個類加載可以負責加載特定位置的類,例如,bootstrap類加載負責加載jre/lib/rt.jar中的類,我們平時用的jdk中的類都位于rt.jar中。extclassloader負責加載jar/lib/ext/*.jar中的類,appclassloader負責classpath指定的目錄或jar中的類。除了bootstrap之外,其他的類加載器本身也都是java類,它們的父類是ClassLoader。

繼續閱讀