Java內存管理 -JVM 垃圾回收

一.概述

相比起C和C++的本身回收內存,JAVA要方便得多,由於JVM會爲咱們自動分配內存以及回收內存。html

在以前的JVM 以內存管理 中,咱們介紹了JVM內存管理的幾個區域,其中程序計數器以及虛擬機棧是線程私有的,隨線程而滅,故而它是不用考慮垃圾回收的,由於線程結束其內存空間即釋放。java

而JAVA堆和方法區則不同,JAVA堆和方法區時存放的是對象的實例信息以及對象的其餘信息,這部分是垃圾回收的主要地點。算法

二.JAVA堆垃圾回收

垃圾回收主要考慮的問題有兩個:一個是效率問題,一個是空間碎片問題。線程

而Java堆中的垃圾回收能夠分爲兩個區域,一個是新生代,一個是老年代。其中新生代又分爲一塊比較大的Eden空間和兩塊較小的Survivor空間。由於新生代和老年代所存儲的對象羣體是不同的,爲了在效率和空間碎片問題中取得平衡,新生代和老年代所使用的垃圾回收算法是不同。htm

新生代 -複製算法對象

從名字上就知道,新生代主要存放的是比較新的對象,回收屢次以後仍然存活的對象,就會被送到老年代中區。由此可知新生代的垃圾回收是比較頻繁的,因此爲解決效率問題,新生代使用了複製算法。複製算法能夠將內存分爲大小相等的兩塊,每次分配時使用其中一塊,當這一塊用完時,就將還存活的對象複製到另外一塊內存上面區。此時已使用過的這一塊內存就能夠一次清理掉,這樣也不用擔憂內存碎片的問題。固然這種算法的一個缺點就是內存使用率比較低,只有一半(每次只能一半用來分配出去)。blog

而IBM公司的研究代表,新生代中的對象98%都是」照生夕死「,因此不須要按照1:1劃分,故而會將內存分爲一塊較大的Eden空間和兩塊小的Survivor空間。內存

那麼爲何會有兩塊Survivor呢,複製算法不是隻須要一塊Eden和一塊Survivor就夠了嗎?字符串

其實這主要仍是爲了解決碎片化的問題。假設只有一個Survivor區,當Eden區滿的時候,進行Gc,存活對象被分配到了Survivor區,清空Eden區。當再一次Gc完成後,存活的對象繼續放在Survivor區,這樣不是很美好嗎,不會有內存碎片啊!可是別忘了,第一次存到Survivor區的對象極可能在第二次Gc的時候就失活了,清理掉Survivor失活對象不就會產生內存碎片了嗎?get

因此Java堆使用了兩個Survivor區,一個from Survivro和一個toSurvivor,第一次Eden滿的時候,複製算法將存活對象放到from Survivor區,清空Eden。第二次,Eden滿時,將Eden和from Survivor區存活的對象放到to Survivor區,清空Eden和from Survivor,而後重要的一步,將from Survivor和to Survivor角色互換!這樣就解決了內存碎片化的問題。

老年代 -標記/整理算法

首先要明白老年代存放的都是會存活得比較久的對象,因此若是老年代也使用複製算法的話,那麼複製對象的開銷時比較大的,由於老年代的對象基本上都會存活。

標記/整理算法很好理解,主要也就是」標記「,」整理「兩個步驟,先將要回收的對象標記,而後讓存活對象向着一端移動,最後將邊界之外的內存,而後Gc完成。

三.方法區垃圾回收

在某些地方的解釋中,方法區也會被叫作「永久代」,與JAVA堆不一樣,這裏存放的是類的信息以及一些常量信息,故而這個區域中被分配的內存通常比較難以被回收,因此纔有有」永久代「之名。

雖然方法區中垃圾回收效率較低,但被分配的內存卻也並不是真的就永不被回收,其主要回收的有兩部份內容:廢棄常量和無用的類。廢棄常量的回收與JAVA堆中類實例回收相似,當常量池中一個常量沒有被引用時,就有可能被回收。好比常量池中有一個字符串常量「abc」,當沒有任何一個String對象值爲"abc"時,那麼下一次垃圾回收"abc"常量就有可能會被回收。

而對於無用的類的回收,首先須要判斷什麼樣的類纔是」無用的類「:

  • 該類全部的實例都已被回收,即JAVA堆中沒有該類的實例。
  • 加載類的ClassLoader已經被回收。
  • 該類對應的java.lang.Class對象沒有在任何地方被引用,沒法在任何地方經過反射訪問該類的方法。

虛擬機可能會堆知足這三個條件的」無用的類「進行回收,僅僅是可能,並不是必然。

相關文章
相關標籤/搜索