JVM類加載,垃圾回收
類加載子系統(tǒng)
classLoader 只負責(zé)對字節(jié)碼文件的加載,至于是否可以運行,還要看執(zhí)行引擎。
- 加載的類信息存放于方法區(qū)的內(nèi)存空間,除了類信息之外,還會存放有運行時常量池的信息,還可能包含字符串字面量和數(shù)字常量。
loading加載:
通過一個類的全限定名獲得定義此類的二進制字節(jié)流。也就是根據(jù)完整的路徑找到對應(yīng)類的二進制字節(jié)流。
將這個字節(jié)流所代表的靜態(tài)的數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)運行時的數(shù)據(jù)結(jié)構(gòu)
- 在內(nèi)存中生成一個代表這個類的Java.lang.Class對象,作為方法區(qū)這個方法數(shù)據(jù)的入口。
鏈接:驗證、準備、解析
驗證:目的在于確保二進制字節(jié)流包含的信息符合虛擬機的要求,保證被加載類的正確性,不會危害虛擬機的安全。
驗證文件格式、字節(jié)碼、元數(shù)據(jù)、符號引用的驗證
準備:為類變量分配內(nèi)存并設(shè)置默認的初始值,即零值。
不包含被final修飾的類變量
解析:將常量池的符號轉(zhuǎn)化為直接引用的過程。
初始化:JVM將程序的執(zhí)行權(quán)交給程序。
雙親委派模型
雙親委派模型的原理:如果一個類加載器收到了類加載的請求的話,它首先不會自己去嘗試加載這個類,而是把這個請求委派給自己的父類加載器去完成,每一層的加載器都是如此,因此所有的加載請求都會傳送到最頂層的啟動類加載器中,只有當(dāng)父類加載器反饋自己無法加載這個請求的時候,即父類搜索不到這個類的時候,子類才會自己嘗試去加載。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
使用雙親委派模型來組織類加載之間的關(guān)系,一個顯而易見的好處就是Java中類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層級關(guān)系。因此Object類在程序中的各種類加載的環(huán)境中都能保證是同一個類。反之,如果沒有雙親委派模型的話,都由各個的類加載器去加載的話,如果用戶自定義了一個java.lang.Object的類,并放在ClassPath 中的話,就會出現(xiàn)多個Object類,Java中最基礎(chǔ)的體系也就會無法保證。
破壞雙親委派模型:
1.雙親委派模型被破壞的第一次就是剛引入雙親委派模型的時候,是為了兼容JDK1.2之前的代碼
2.雙親委派模型的第二次的破壞就是自生的缺陷導(dǎo)致的。但發(fā)生父類調(diào)用子類的時候。
3.第三次就是用戶對于程序動態(tài)性的追求而導(dǎo)致的:代碼熱的替換、模塊熱的部署。
垃圾回收
什么是垃圾?
GC 中的垃圾就是特指在內(nèi)存中的、不會在被使用的對象。
判斷對象已死
引用計數(shù)器法:
每個對象添加一個引用計數(shù)器,每被引用一次,計數(shù)器加1,失去引用,計數(shù)器減1,當(dāng)計數(shù)器在一段時間內(nèi)保持為0時,該對象就認為是可以被回收得了。(在JDK1.2之前,使用的是該算法)
缺點:當(dāng)兩個對象A、B相互引用的時候,當(dāng)其他所有的引用都消失之后,A和B還有一個相互引用,此時計數(shù)器各為1,而實際上這兩個對象都已經(jīng)沒有額外的引用了,已經(jīng)是垃圾了。但是卻不會被回收,引用計數(shù)器法不能解決循環(huán)引用的問題。
JVM垃圾回收采用的是可達性分析算法:
從GC set中的GC roots 作為起點,從這些節(jié)點向下搜索,搜索的路徑被稱為引用鏈,如果一個對象不存在引用鏈的話,那么說明這個對象已死。就會被GC回收器回收。
GCroots 是:
1.來自于JVM棧中引用的對象。比如各個線程中被調(diào)用的方法堆棧中使用到的參數(shù)、局部變量、臨時變量等。
2.方法區(qū)中靜態(tài)屬性引用的對象。比如Java中的引用類型靜態(tài)變量。
3.方法區(qū)中常量引用的對象。比如字符串常量池(String Table)中的引用。
4.本地方法棧中引用的對象。
5.Java虛擬機內(nèi)部的引用。比如基本數(shù)據(jù)類型對應(yīng)的Class對象,一些常駐的異常類等,還有系統(tǒng)的類加載器。
6.所有被同步鎖持有的對象。 …
并不是所有的引用對象一定就會被GC 的時候回收掉。
JDK1.2之后的四種引用類型:
1.強引用:
就是程序中一般使用的引用類型,即使發(fā)生OOM也不會回收的對象。
就是為剛被new出來的對象所加的引用,它的特點就是,永遠不會被GC,除非顯示的設(shè)置null,才會GC。
比如:
package Reference; /** * user:ypc; * date:2021-06-19; * time: 9:40; */ public class Test { public static void main(String[] args) { StringBuilder stringBuilder = new StringBuilder("I am FinalReference"); System.gc(); System.out.println(stringBuilder); //觸發(fā)GC byte[] bytes = new byte[1024 * 940 * 7]; System.gc(); System.out.println(stringBuilder); try { byte[] bytes2 = new byte[1024 * 1024 * 7]; } catch (Exception e) { } finally { System.out.println("發(fā)生了OOM:"); System.out.println(stringBuilder); } } }
2.軟引用:
就是用來描述一些還有用,但是非必須的對象,只被軟引用關(guān)聯(lián)著的對象,在系統(tǒng)將要發(fā)生OOM前會回收的對象。如果這次回收還沒有足夠的內(nèi)存,才會拋出OOM的異常。
package Reference; /** * user:ypc; * date:2021-06-19; * time: 9:46; */ public class SoftReference { public static class User { private int id; private String name; User(int id, String name) { this.id = id; this.name = name; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + '}'; } } public static void main(String[] args) { //user 是強引用 User user = new User(1001, "小明"); //softReference 是軟引用 java.lang.ref.SoftReference<User> softReference = new java.lang.ref.SoftReference<>(user); // 顯示的將強引用置為null user = null; System.out.println(softReference.get()); System.gc(); System.out.println("After GC: "); System.out.println(softReference.get()); //觸發(fā)GC byte[] bytes = new byte[1024 * 940 * 7]; System.gc(); System.out.println(softReference.get()); } }
3.弱引用:
弱引用也是用來描述那些非必要的對象,它的強度比軟引用還低一些。當(dāng)垃圾回收器開始工作的時候,無論內(nèi)存是否夠用,都會回收掉只被弱引用關(guān)聯(lián)著的對象。
public static void main(String[] args) { User user = new User(1002,"小明"); java.lang.ref.WeakReference<User> weakReference = new java.lang.ref.WeakReference<>(user); user = null; System.out.println(weakReference.get()); System.gc(); System.out.println("After GC"); System.out.println(weakReference.get()); }
4.虛引用:
它是最弱的一種引用關(guān)系。剛被創(chuàng)建就會被GC回收器回收。它的價值就是在GC 的時候觸發(fā)一次方法的回調(diào)。
虛擬機中的并行與并發(fā):
并行:
并行描述的是多條垃圾收集器線程之間的關(guān)系,說明同一時間有多條這樣的線程在同時的進行工作。
并發(fā):
并發(fā)描述的是垃圾收集器線程與用戶線程之間的關(guān)系,說明同一時間垃圾收集器的線程和用戶線程之家同時在運行。
常見的垃圾回收算法:
1.標記–清除算法:(Mark–Sweep)
將死亡的對象標記,然后進行GC。
執(zhí)行的效率不穩(wěn)定。如果堆中有大量的對象,其中有大部分都是要被回收的話 ,那么必需要進行大量的標記–清除的步驟,導(dǎo)致執(zhí)行效率的降低。
會造成內(nèi)存碎片的問題,使空間的利用率降低。標記–清除之后會產(chǎn)生大量的不連續(xù)的內(nèi)存碎片。
2.標記–復(fù)制算法:
將空間分為兩部分,一部分始終是未使用的狀態(tài)。當(dāng)進行垃圾回收的時候
將存活的對象復(fù)制到未使用的空間上,然后將另一半的區(qū)域進行全GC。
但是標記–復(fù)制算法在對象存活率比較高的時候就要進行多次的復(fù)制操作,效率會降低。而且每次只有50%的內(nèi)存空間被使用。
3.標記–整理算法:
將存活的對象進行移動,然后進行GC。
對象的移動需要STW
解決了垃圾碎片的問題
常見的垃圾回收器:
Serial/Serial Old
收集器 是最基本最古老的收集器,它是一個單線程收集器,并且在它進行垃圾收集時,必須暫停所有用戶線程。Serial收集器是針對新生代的收集器,采用的是Copying
算法,Serial Old
收集器是針對老年代的收集器,采用的是Mark-Compact
算法。它的優(yōu)點是實現(xiàn)簡單高效,但是缺點是會給用戶帶來停頓。ParNew
收集器 是Serial
收集器的多線程版本,使用多個線程進行垃圾收集。Parallel Scavenge
收集器 是一個新生代的多線程收集器(并行收集器),它在回收期間不需要暫停其他用戶線程,其采用的是Copying
算法,該收集器與前兩個收集器有所不同,它主要是為了達到一個可控的吞吐量。Parallel Old
收集器 是Parallel Scavenge
收集器的老年代版本(并行收集器),使用多線程和Mark-Compact
算法。- CMS(
Concurrent Mark Sweep
)收集器 是一種以獲取最短回收停頓時間為目標的收集器,它是一種并發(fā)收集器,采用的是Mark-Sweep
算法。 - G1收集器 是當(dāng)今收集器技術(shù)發(fā)展最前沿的成果,它是一款面向服務(wù)端應(yīng)用的收集器,它能充分利用多CPU、多核環(huán)境。因此它是一款并行與并發(fā)收集器,并且它能建立可預(yù)測的停頓時間模型。
新時代、老年代
Java 中的堆也是 GC 收集垃圾的主要區(qū)域。GC 分為兩種:Minor GC、FullGC ( 或稱為 Major GC )。 Minor GC 是發(fā)生在新生代中的垃圾收集動作,所采用的是復(fù)制算法。新生代幾乎是所有 Java 對象出生的地方,即 Java 對象申請的內(nèi)存以及存放都是在這個地方。Java 中的大部分對象通常不需長久存活,具有朝生夕滅的性質(zhì)。
當(dāng)一個對象被判定為 “死亡” 的時候,GC就有責(zé)任來回收掉這部分對象的內(nèi)存空間。新生代是GC收集垃圾的頻繁區(qū)域。當(dāng)對象在 Eden ( 包括一個 Survivor 區(qū)域,這里假設(shè)是 from 區(qū)域 ) 出生后,在經(jīng)過一次 Minor GC 后,如果對象還存活,并且能夠被另外一塊 Survivor 區(qū)域所容納( 上面已經(jīng)假設(shè)為 from 區(qū)域,這里應(yīng)為 to 區(qū)域,即 to 區(qū)域有足夠的內(nèi)存空間來存儲 Eden 和 from 區(qū)域中存活的對象 ),則使用復(fù)制算法將這些仍然還存活的對象復(fù)制到另外一塊 Survivor 區(qū)域 ( 即 to 區(qū)域 ) 中,然后清理所使用過的 Eden 以及 Survivor 區(qū)域 ( 即from 區(qū)域 ),并且將這些對象的年齡設(shè)置為1,以后對象在 Survivor 區(qū)每熬過一次 Minor GC,就將對象的年齡 + 1,當(dāng)對象的年齡達到某個值時 ( 默認是 15 也就是經(jīng)歷15次GC之后還存活的對象),這些對象就會被移動到老年代。
但這也不是一定的,對于一些較大的對象 ( 即需要分配一塊較大的連續(xù)內(nèi)存空間 ) 則是直接進入到老年代。Full GC 是發(fā)生在老年代的垃圾收集動作,所采用的是標記-清除算法。
另外,標記-清除算法收集垃圾的時候會產(chǎn)生許多的內(nèi)存碎片 ( 即不連續(xù)的內(nèi)存空間 ),此后需要為較大的對象分配內(nèi)存空間時,若無法找到足夠的連續(xù)的內(nèi)存空間,就會提前觸發(fā)一次 GC 的收集動作。
為什么大對象會直接存在老年代?
大對象的創(chuàng)建和銷毀隨需要消耗的時間比較多,所以性能也比較滿,如果存到新生代的話,那么有可能頻繁的創(chuàng)建和銷毀大對象,導(dǎo)致JVM對的運行的效率變低,所以直接存放在老年代。
新生代的各個區(qū)域的占比分別是:8:1:1 新生代與老年代的占比是:1:2
總結(jié)
本篇文章就到里了,希望能幫到你,也希望您能多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Java根據(jù)模板實現(xiàn)excel導(dǎo)出標準化
這篇文章主要為大家詳細介紹了Java如何根據(jù)模板實現(xiàn)excel導(dǎo)出標準化,文中的示例代碼講解詳細,具有一定的借鑒價值,有需要的小伙伴可以參考下2024-03-03解決springboot configuration processor對maven子模塊不起作用的問題
這篇文章主要介紹了解決springboot configuration processor對maven子模塊不起作用的問題,本文通過圖文實例代碼給大家講解的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-09-09Java實現(xiàn)的簡單字符串反轉(zhuǎn)操作示例
這篇文章主要介紹了Java實現(xiàn)的簡單字符串反轉(zhuǎn)操作,結(jié)合實例形式分別描述了java遍歷逆序輸出以及使用StringBuffer類的reverse()方法兩種字符串反轉(zhuǎn)操作技巧,需要的朋友可以參考下2018-08-08SpringCloud2020整合Nacos-Bootstrap配置不生效的解決
這篇文章主要介紹了SpringCloud2020整合Nacos-Bootstrap配置不生效的解決,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01SpringMVC @RequestBody Date類型的Json轉(zhuǎn)換方式
這篇文章主要介紹了SpringMVC @RequestBody Date類型的Json轉(zhuǎn)換方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10Spring Boot 通過 Mvc 擴展方便進行貨幣單位轉(zhuǎn)換的代碼詳解
這篇文章主要介紹了Spring Boot 通過 Mvc 擴展方便進行貨幣單位轉(zhuǎn)換,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12java實現(xiàn)HmacSHA256算法進行加密方式
這篇文章主要介紹了java實現(xiàn)HmacSHA256算法進行加密方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08