作者:高效碼農來(lái)源:頭條科技
轉自:http://news.51cto.com/art/201802/566081.htm
一、Java引用的四種狀態(tài):
強引用:
用的最廣。我們平時(shí)寫(xiě)代碼時(shí),new一個(gè)Object存放在堆內存,然后用一個(gè)引用指向它,這就是強引用。
如果一個(gè)對象具有強引用,那垃圾回收器絕不會(huì )回收它。當內存空間不足,Java虛擬機寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不會(huì )靠隨意回收具有強引用的對象來(lái)解決內存不足的問(wèn)題。
軟引用:
如果一個(gè)對象只具有軟引用,則內存空間足夠時(shí),垃圾回收器就不會(huì )回收它;如果內存空間不足了,就會(huì )回收這些對象的內存。(備注:如果內存不足,隨時(shí)有可能被回收。)
只要垃圾回收器沒(méi)有回收它,該對象就可以被程序使用。軟引用可用來(lái)實(shí)現內存敏感的高速緩存。
弱引用:
弱引用與軟引用的區別在于:只具有弱引用的對象擁有更短暫的生命周期。
每次執行GC的時(shí)候,一旦發(fā)現了只具有弱引用的對象,不管當前內存空間足夠與否,都會(huì )回收它的內存。不過(guò),由于垃圾回收器是一個(gè)優(yōu)先級很低的線(xiàn)程,因此不一定會(huì )很快發(fā)現那些只具有弱引用的對象。
虛引用:
“虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用并不會(huì )決定對象的生命周期。如果一個(gè)對象僅持有虛引用,那么它就和沒(méi)有任何引用一樣,在任何時(shí)候都可能被垃圾回收器回收。
虛引用主要用來(lái)跟蹤對象被垃圾回收器回收的活動(dòng)。
二、Java中的內存劃分:
Java程序在運行時(shí),需要在內存中的分配空間。為了提高運算效率,就對數據進(jìn)行了不同空間的劃分,因為每一片區域都有特定的處理數據方式和內存管理方式。

1、程序計數器:(線(xiàn)程私有)
每個(gè)線(xiàn)程擁有一個(gè)程序計數器,在線(xiàn)程創(chuàng )建時(shí)創(chuàng )建,
指向下一條指令的地址
執行本地方法時(shí),其值為undefined
2、虛擬機棧:(線(xiàn)程私有)
每個(gè)方法被調用的時(shí)候都會(huì )創(chuàng )建一個(gè)棧幀,用于存儲局部變量表、操作棧、動(dòng)態(tài)鏈接、方法出口等信息。局部變量表存放的是:編譯期可知的基本數據類(lèi)型、對象引用類(lèi)型。
每個(gè)方法被調用直到執行完成的過(guò)程,就對應著(zhù)一個(gè)棧幀在虛擬機中從入棧到出棧的過(guò)程。
在Java虛擬機規范中,對這個(gè)區域規定了兩種異常情況:
(1)如果線(xiàn)程請求的棧深度太深,超出了虛擬機所允許的深度,就會(huì )出現StackOverFlowError(比如無(wú)限遞歸。因為每一層棧幀都占用一定空間,而 Xss 規定了棧的最大空間,超出這個(gè)值就會(huì )報錯)
(2)虛擬機??梢詣?dòng)態(tài)擴展,如果擴展到無(wú)法申請足夠的內存空間,會(huì )出現OOM
3、本地方法棧:
(1)本地方法棧與java虛擬機棧作用非常類(lèi)似,其區別是:java虛擬機棧是為虛擬機執行java方法服務(wù)的,而本地方法棧則為虛擬機執使用到的Native方法服務(wù)。
(2)Java虛擬機沒(méi)有對本地方法棧的使用和數據結構做強制規定,Sun HotSpot虛擬機就把java虛擬機棧和本地方法棧合二為一。
(3)本地方法棧也會(huì )拋出StackOverFlowError和OutOfMemoryError。
4、堆:即堆內存(線(xiàn)程共享)
(1)堆是java虛擬機所管理的內存區域中最大的一塊,java堆是被所有線(xiàn)程共享的內存區域,在java虛擬機啟動(dòng)時(shí)創(chuàng )建,堆內存的唯一目的就是存放對象實(shí)例幾乎所有的對象實(shí)例都在堆內存分配。
(2)堆是GC管理的主要區域,從垃圾回收的角度看,由于現在的垃圾收集器都是采用的分代收集算法,因此java堆還可以初步細分為新生代和老年代。
(3)Java虛擬機規定,堆可以處于物理上不連續的內存空間中,只要邏輯上連續的即可。在實(shí)現上既可以是固定的,也可以是可動(dòng)態(tài)擴展的。如果在堆內存沒(méi)有完成實(shí)例分配,并且堆大小也無(wú)法擴展,就會(huì )拋出OutOfMemoryError異常。
5、方法區:(線(xiàn)程共享)
(1)用于存儲已被虛擬機加載的類(lèi)信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數據。
(2)Sun HotSpot虛擬機把方法區叫做永久代(Permanent Generation),方法區中最終要的部分是運行時(shí)常量池。
三、Java對象在內存中的狀態(tài):
可達的/可觸及的:
Java對象被創(chuàng )建后,如果被一個(gè)或多個(gè)變量引用,那就是可達的。即從根節點(diǎn)可以觸及到這個(gè)對象。
其實(shí)就是從根節點(diǎn)掃描,只要這個(gè)對象在引用鏈中,那就是可觸及的。
可恢復的:
Java對象不再被任何變量引用就進(jìn)入了可恢復狀態(tài)。
在回收該對象之前,該對象的finalize()方法進(jìn)行資源清理。如果在finalize()方法中重新讓變量引用該對象,則該對象再次變?yōu)榭蛇_狀態(tài),否則該對象進(jìn)入不可達狀態(tài)
不可達的:
Java對象不被任何變量引用,且系統在調用對象的finalize()方法后依然沒(méi)有使該對象變成可達狀態(tài)(該對象依然沒(méi)有被變量引用),那么該對象將變成不可達狀態(tài)。
當Java對象處于不可達狀態(tài)時(shí),系統才會(huì )真正回收該對象所占有的資源。
四、判斷對象死亡的兩種常用算法:
1、引用計數算法:
給對象中添加一個(gè)引用計數器,每當有一個(gè)地方引用它時(shí),計數器值就加1;當引用失效時(shí),計數器值就減1;任何時(shí)刻計數器為0的對象就是不可能再被使用的。
但是,主流的java虛擬機并沒(méi)有選用引用計數算法來(lái)管理內存,其中最主要的原因是:它很難解決對象之間相互循環(huán)引用的問(wèn)題。
2、根搜索算法:(jvm采用的算法)
設立若干種根對象,當任何一個(gè)根對象(GC Root)到某一個(gè)對象均不可達時(shí),則認為這個(gè)對象是可以被回收的。
五、垃圾回收算法
1、標記-清除算法:
標記階段:先通過(guò)根節點(diǎn),標記所有從根節點(diǎn)開(kāi)始的可達對象。因此,未被標記的對象就是未被引用的垃圾對象;
清除階段:清除所有未被標記的對象。
2、復制算法:(新生代的GC)
將原有的內存空間分為兩塊,每次只使用其中一塊,在垃圾回收時(shí),將正在使用的內存中的存活對象復制到未使用的內存塊中,然后清除正在使用的內存塊中的所有對象。
3、標記-整理算法:(老年代的GC)
標記階段:先通過(guò)根節點(diǎn),標記所有從根節點(diǎn)開(kāi)始的可達對象。因此,未被標記的對象就是未被引用的垃圾對象
整理階段:將將所有的存活對象壓縮到內存的一端;之后,清理邊界外所有的空間
4、分代收集算法:
存活率低:少量對象存活,適合復制算法:在新生代中,每次GC時(shí)都發(fā)現有大批對象死去,只有少量存活(新生代中98%的對象都是“朝生夕死”),那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成GC。
存活率高:大量對象存活,適合用標記-清理/標記-整理:在老年代中,因為對象存活率高、沒(méi)有額外空間對他進(jìn)行分配擔保,就必須使用“標記-清理”/“標記-整理”算法進(jìn)行GC。
六、垃圾收集器
1、Serial收集器:(串行收集器)
這個(gè)收集器是一個(gè)單線(xiàn)程的收集器,但它的單線(xiàn)程的意義并不僅僅說(shuō)明它只會(huì )使用一個(gè)CPU或一條收集線(xiàn)程去完成垃圾收集工作,更重要的是在它進(jìn)行垃圾收集時(shí),必須暫停其他所有的工作線(xiàn)程(Stop-The-World:將用戶(hù)正常工作的線(xiàn)程全部暫停掉),直到它收集結束。
2、ParNew收集器:Serial收集器的多線(xiàn)程版本(使用多條線(xiàn)程進(jìn)行GC)
ParNew收集器是Serial收集器的多線(xiàn)程版本。
它是運行在server模式下的首選新生代收集器,除了Serial收集器外,目前只有它能與CMS收集器配合工作。CMS收集器是一個(gè)被認為具有劃時(shí)代意義的并發(fā)收集器,因此如果有一個(gè)垃圾收集器能和它一起搭配使用讓其更加完美,那這個(gè)收集器必然也是一個(gè)不可或缺的部分了。
3、ParNew Scanvenge收集器
類(lèi)似ParNew,但更加關(guān)注吞吐量。目標是:達到一個(gè)可控制吞吐量的收集器。
停頓時(shí)間和吞吐量不可能同時(shí)調優(yōu)。我們一方買(mǎi)希望停頓時(shí)間少,另外一方面希望吞吐量高,其實(shí)這是矛盾的。因為:在GC的時(shí)候,垃圾回收的工作總量是不變的,如果將停頓時(shí)間減少,那頻率就會(huì )提高;既然頻率提高了,說(shuō)明就會(huì )頻繁的進(jìn)行GC,那吞吐量就會(huì )減少,性能就會(huì )降低。
吞吐量:CPU用于用戶(hù)代碼的時(shí)間/CPU總消耗時(shí)間的比值,即=運行用戶(hù)代碼的時(shí)間/(運行用戶(hù)代碼時(shí)間+垃圾收集時(shí)間)。比如,虛擬機總共運行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。
4、G1收集器:
是當今收集器發(fā)展的最前言成果之一,直到j(luò )dk1.7,sun公司才認為它達到了足夠成熟的商用程度。
5、CMS收集器:(老年代收集器)
CMS收集器(Concurrent Mark Sweep:并發(fā)標記清除)是一種以獲取最短回收停頓時(shí)間為目標的收集器。適合應用在互聯(lián)網(wǎng)站或者B/S系統的服務(wù)器上,這類(lèi)應用尤其重視服務(wù)器的響應速度,希望系統停頓時(shí)間最短。
七、Java堆內存劃分:
Java 中的堆是 JVM 所管理的最大的一塊內存空間,主要用于存放各種類(lèi)的實(shí)例對象。
在 Java 中,堆被劃分成兩個(gè)不同的區域:年輕代 ( Young )、老年代 ( Tenured)。年輕代 ( Young ) 又被劃分為三個(gè)區域:Eden、From Survivor、To Survivor。 這樣劃分的目的是為了使 JVM 能夠更好的管理堆內存中的對象,包括內存的分配以及回收。
1.年輕代
年輕代用來(lái)存放新近創(chuàng )建的對象,尺寸隨堆大小的增大和減小而相應的變化,默認值是保持為堆大小的1/15,可以通過(guò) -Xmn 參數設置年輕代為固定大小,也可以通過(guò) -XX:NewRatio 來(lái)設置年輕代與年老代的大小比例,年青代的特點(diǎn)是對象更新速度快,在短時(shí)間內產(chǎn)生大量的“死亡對象”。
年輕代的特點(diǎn)是產(chǎn)生大量的死亡對象,并且要是產(chǎn)生連續可用的空間, 所以使用復制清除算法和并行收集器進(jìn)行垃圾回收.對年輕代的垃圾回收稱(chēng)作初級回收 (minor gc)。
2.老年代
Full GC 是發(fā)生在老年代的垃圾收集動(dòng)作,所采用的是標記-清除算法。
現實(shí)的生活中,老年代的人通常會(huì )比新生代的人 “早死”。堆內存中的老年代(Old)不同于這個(gè),老年代里面的對象幾乎個(gè)個(gè)都是在 Survivor 區域中熬過(guò)來(lái)的,它們是不會(huì )那么容易就 “死掉” 了的。因此,Full GC 發(fā)生的次數不會(huì )有 Minor GC 那么頻繁,并且做一次 Full GC 要比進(jìn)行一次 Minor GC 的時(shí)間更長(cháng)。 另外,標記-清除算法收集垃圾的時(shí)候會(huì )產(chǎn)生許多的內存碎片 ( 即不連續的內存空間 ),此后需要為較大的對象分配內存空間時(shí),若無(wú)法找到足夠的連續的內存空間,就會(huì )提前觸發(fā)一次 GC 的收集動(dòng)作。
3.永久代
永久代是Hotspot虛擬機特有的概念,是方法區的一種實(shí)現,別的JVM都沒(méi)有這個(gè)東西。在Java 8中,永久代被徹底移除,取而代之的是另一塊與堆不相連的本地內存——元空間。
永久代或者“Perm Gen”包含了JVM需要的應用元數據,這些元數據描述了在應用里使用的類(lèi)和方法。注意,永久代不是Java堆內存的一部分。永久代存放JVM運行時(shí)使用的類(lèi)。永久代同樣包含了Java SE庫的類(lèi)和方法。永久代的對象在full GC時(shí)進(jìn)行垃圾收集。
八、類(lèi)加載機制:

虛擬機把描述類(lèi)的數據從Class文件加載到內存,并對數據進(jìn)行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類(lèi)型,這就是虛擬機的類(lèi)加載機制。
對應常見(jiàn)筆試題
注意:子類(lèi)初始化問(wèn)題:滿(mǎn)足主動(dòng)調用,即父類(lèi)訪(fǎng)問(wèn)子類(lèi)中的靜態(tài)變量、方法,子類(lèi)才會(huì )初始化;否則僅父類(lèi)初始化。




注意:訪(fǎng)問(wèn)類(lèi)或接口的靜態(tài)變量(特例:如果是用static final修飾的常量,那就不會(huì )對類(lèi)進(jìn)行顯式初始化。static final 修改的變量則會(huì )做顯式初始化)



上面的運行效果顯示,由于c是final static修飾的靜態(tài)常量,所以根本就沒(méi)有調用靜態(tài)代碼塊里面的內容,也就是說(shuō),沒(méi)有對這個(gè)類(lèi)進(jìn)行顯式初始化。
聯(lián)系客服