Java虛擬機內存管理知識總結

0、Java 對內存的劃分:

Java虛擬機規範將物理內存(主內存和CPU中的緩存、寄存器)劃分爲 程序計數器 、 Java 虛擬機棧 、 本地方法棧 、 Java 堆 、 方法區 五個區域,但並無規定這些區域的具體實現,在其餘地方聽到的一些名詞(如永久代、元空間等,這些都是方法區的具體實現)可能都是這些區域具體的實現,這點要特別注意,別被這些概念搞暈。算法

各個區域的特色以下表:數組

區域 線程關係 內存異常 垃圾回收 做用
程序計數器 線程私有 記錄Java虛擬機正在指向的字節碼指令
Java 虛擬機棧 線程私有 StackOverflowError、OutOfMemoryError 描述 Java 方法執行時的內存模型,棧中棧幀存儲局部變量表、操做數棧、動態連接、方法返回地址等信息。
本地方法棧 線程私有 StackOverflowError、OutOfMemoryError 描述本地方法(非 Java 代碼編寫)執行時的內存模型
方法區 線程共享 OutOfMemoryError 存儲虛擬機加載過的類信息、常量(常量池)、靜態變量、即時編譯器(JIT)生成的代碼
Java 堆 線程共享 OutOfMemoryError 存放 Java對象(實例)

一、類加載器:

類加載器分爲 Bootstrap 、 Extension ClassLoader (Java9 中是 Platform ClassLoader)、 Application ClassLoader ,級別也是從低到高。緩存

能夠調用類加載器對象的 getParent() 方法查找該級加載器的上一級加載器,也成爲父類加載器。安全

類加載器 描述 是否爲 Java 實現
Bootstrap JVM啓動時建立,一般由操做系統相關的本地代碼實現,是最根基的類加載器,負責裝載的是最核心的 Java 類,如 Object 類、System 類、String 類等
Extension ClassLoader 加載一些擴展的系統類,如 XML、加密、壓縮相關功能的類
Application ClassLoader 加載用戶定義的 CLASSPATH 路徑下的類

此處不翻譯了,翻譯後就變味了,尤爲是下面的 Parents Delegation Model 翻譯爲雙親委派模型很不恰當。性能優化

字節碼文件加載到內存中,才能夠實例化出類,而類加載器就是負責加載 Java 類的。低級別的類加載器在加載一個類時會先詢問上一級的類加載器,直到詢問到頂級的類加載器(Bootstrap),若是頂級的類加載器能夠加載就加載該類,不然向下嘗試是否能夠加載該類,也便是若是上一級類加載器能加載的就用上一級加載(複用上一級的類加載器),用不了再用自身的類加載器加載,這也就是口口相傳倒是翻譯很不恰當的雙親委派模型。這樣作可使類加載更加安全,避免加載和標準 Java 類同包同名的類破壞虛擬機。數據結構

能夠根據須要繼承 Application ClassLoader 實現自定義類加載器,隔離加載器、修改類的加載方式、擴展加載源、防止源碼泄露。多線程

二、類加載的過程:

類加載是將字節碼文件實例化成 Class 對象並進行相關初始化的過程。類加載包括類的 加載(Load)、類的 連接 (Link)、類的 初始化 (init)三個步驟。架構

類的加載是將字節碼文件以二進制流的方式讀取到內存中並轉化爲特定的數據結構,檢查 cafe baby 這個魔法數(是否是Java文件的標誌),是否有父類等,建立類對應的 Class 對象。併發

類的連接又分爲 驗證 、 準備 、 解析 三個階段,驗證階段是進行更加詳細的校驗,如類型是否正確,靜態變量是否合理等;準備階段是爲類的靜態變量分配內存空間,並設定默認值;解析階段是保證類和類之間相互引用的正確性,完成類在內存中的結構佈局。分佈式

類的初始化並非初始化對象,而是根據代碼中的值初始化類的靜態變量值,類的靜態變量的初始化方式也有直接在聲明時指定值和在靜態代碼塊中指定值兩種方式。

三、訪問對象的兩種方式:

Java虛擬機棧中的局部變量表存放的數據除了基本的數據類型外,還有對象的引用類型(reference),這關係到如何訪問一個對象。

在不一樣的虛擬機中,對象的訪問方式也是不一樣的,主流的訪問方式有 使用句柄 和 直接指針 兩種。

  • 使用句柄:

使用句柄是在 Java 堆中劃分出一塊區域做爲句柄池,句柄池中存放對象的實例數據和類型數據(類相關的信息),reference 中存放的是對象在句柄中的地址,這是一種間接訪問對象方式。

  • 直接指針:

直接指針是reference中直接存放對象的地址,但 Java 堆須要考慮如何存放訪問對象類型的指針。

兩種方式其實各有優劣,以下表:

方式 優點 特色
使用句柄 reference 中存放的是穩定的句柄地址,對象在移動時只改變句柄池中對象的地址,而reference中的地址不須要改變。 間接訪問
直接指針 節省了一次指針定位的時間開銷,訪問速度相對更快。 直接訪問

四、判斷對象是否能夠回收的算法:

垃圾回收以前須要判斷對象是否能夠回收,常見的判斷算法有引用計數算法和可達性分析算法。

引用計數算法:

每一個對象都有對應的引用計數器,當有一個地方引用該對象時,就將引用計數器的值加1,當引用失效時,就將引用計數器的值減1,當計數器的值爲0時,表示對象沒有引用,能夠被回收了。

缺點:看起來簡單高效,可是有循環引用問題。若是兩個對象中包含對方的引用就會產生循環引用問題,致使垃圾收集器不能回收對象。

可達性分析算法:

若是對象與GC Roots 之間沒有直接或間接的應用關係,就能夠被回收了。常見的 GC Roots 對象包括虛擬機棧(棧幀本地變量表)中引用的對象、方法區中靜態屬性引用的對象、方法區常量引用的對象、本地方法棧中(Native 方法)引用的對象。GC Roots,是一個特殊的對象,且絕對不能被其餘對象引用,否則也會像引用計數算法那樣有循環引用的問題。

注:歡迎工做1到6年的Java工程師朋友們加入Java架構交流裙:834962734。羣內提供免費的Java架構學習資料(有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化等...)這些成爲架構師必備的知識體系,以及Java進階學習路線圖。

五、常見的垃圾回收算法:

  • 標記-清除算法

最基本的垃圾回收算法,後續的算法都是對它的改進。

首先標記出須要回收的對象,再將標記出的區域內容清除。

缺點是:標記時的查找效率,清除時產生內存碎片。

  • 標記-複製算法

將內存區域劃分爲兩塊,每次只使用一塊,垃圾回收時,標記正在使用的內存區域,將存活的對象複製到另外一塊內存區域,再將原來的那一塊內存區域一次性清除。避免了內存碎片的產生,但不適合存活時間長的對象。

缺點:浪費了一半的內存空間,當對象存活率高時,進行大量的複製操做,效率不高。

  • 標記-整理算法

標記過程和標記-除算法相同,垃圾回收時,是將存活的對象向同一端移動,再清除這以外的內存區域,這樣就使得對象佔用的內存區域連續,避免了內存碎片的產生。

  • 分代收集算法

根據對象存活時間的長短,將堆內存分爲新生代和老生代,存活時間短的對象放在新生代區域,存活時間長的大對象(如對象數組)放在老生代區域。新生代和老生代的比例是 1 : 2,新生代又分爲一個 Eden 區和兩個 Survivor 區。新生代使用標記-複製算法,老生代使用標記-清除算法或標記-整理算法,這樣最大發揮各自算法的優點。

六、常見的垃圾回收器:

  • Serial 回收器

Serial 採起 「複製算法」 實現,若是是在單 CPU 環境下,Serial 收集器沒有線程交互的開銷,理論上是能夠得到最高的單線程執行效率,STW 的時間也能夠控制在幾十到幾百毫秒內,這個時間是徹底能夠接受的。

  • Serial Old (PS MarkSweep)回收器

Serial Old 收集器 是 Serial 收集器的老年代版本,一樣也是一個單線程收集器,使用了 「標記-整理算法」。

  • ParNew 回收器

ParNew 收集器實際上就是 Serial 收集器的多線程版本,收集算法、STW、對象分配的規則、回收策略等都與 Serial 收集器徹底同樣,二者相同的代碼不少。ParNew 收集器雖然有多線程優點,但在單 CPU 和多 CPU 環境下,效果並不必定會比 Serial 好,至少在單 CPU 環境下是確定不如的 Serial 的。

  • Parallel Scavenge 回收器

Parallel Scavenge收集器和 ParNew 收集器很像,也是一個新生代收集器,也是使用複製算法,而且仍是並行的多線程的收集器。相比於 ParNew 收集器,Parallel Scavenge收集器能夠更加精準的控制 CPU 的吞吐量和 STW 的時間,對於交互很少的任務能夠更快地完成。

  • Parallel Old 回收器

Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本,使用多線程和 「標記-整理算法」。在 Parallel Old 收集器出現之間,選擇了 Parallel Scavenge 收集器做爲新生代的收集器,就只能選擇 Serial Old 收集器做爲老生代收集器,這樣確定就是對多 CPU 的浪費,因此 Parallel Scavenge收集器 + Parallel Old 收集器,對於多 CPU 環境吞吐量要求高的環境,算是強強聯合。

  • CMS 回收器

CMS (Concurrent Mark Sweep)收集器從英文名字上看就是基於 「標記-清除算法」 實現的,而且還有併發的特色,它是一種以縮短 STW 的時間爲目標的收集器,對於一些重視服務響應速度的網站,確定是 STW 越短,用戶體驗越好,可是缺點是會在垃圾收集結束後產生大量的空間碎片。

經過初始標記(Initial Mark)、併發標記(Concurrent Mark)、從新標記(Remark)、併發清除(Concurrent Sweep)四個步驟完成垃圾回收。

  • G1 回收器

G1 收集器是目前最早進的收集器,也是 JDK7 以後默認的垃圾回收器,它是基於 「標記-複製算法」 實現的,因此不會產生內存碎片,而且也能夠精準地控制 STW 的時間。G1 收集器對於新生代和老年代都是適用的,優先回收垃圾最多的區域。

相關文章
相關標籤/搜索