国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

JVM(Java虛擬機)-史上最全、最詳細JVM筆記

這篇具有很好參考價值的文章主要介紹了JVM(Java虛擬機)-史上最全、最詳細JVM筆記。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

目錄

一、JVM概述

1、1為什么要學習JVM

1、2虛擬機

1、3JVM作用

1、4JVM整體組成部分?

二、JVM結(jié)構(gòu)--類加載器

2、1類加載子系統(tǒng)

2、2類加載過程

2、2、1加載

2、2、2鏈接

2、2、3初始化

2、3類加載器分類

2.3.1 引導類加載器(啟動類加載器 BootStrap ClassLoader)

2.3.2 擴展類加載器(Extension ClassLoader)

2.3.3 應(yīng)用程序類加載器(系統(tǒng)類加載器 Application ClassLoader)

2.4 雙親委派機制

2.5 如何打破雙親委派機制

三、JVM運行時數(shù)據(jù)區(qū)

3、1運行時數(shù)據(jù)區(qū)組成概述

3.1.1 程序計數(shù)器(Program Counter Register)

3.1.2Java 虛擬機棧(Java Virtual Machine Stacks)

3.1.3 本地方法棧(Native Method Stack)

3.1.4Java 堆(Java Heap)

3.1.5 方法區(qū)(Methed Area)

3.2.程序計數(shù)器(Program Counter Register)

3.3.Java 虛擬機棧(Java Virtual Machine Stacks)

3.4.本地方法棧(Native Method Stack)

3.5.Java 堆內(nèi)存

3.5.1Java堆內(nèi)存概述

3.5.2堆內(nèi)存區(qū)域劃分

3.5.3 為什么分區(qū)(代)?

3.5.4 對象創(chuàng)建內(nèi)存分配過程

3.5.5 新生區(qū)與老年區(qū)配置比例

?3.5.6 分代收集思想 Minor GC、Major GC、Full GC

3.5.7 堆空間的參數(shù)設(shè)置

3.5.8 字符串常量池

3.6方法區(qū)

3.6.1 方法區(qū)的基本理解

3.6.2 方法區(qū)大小設(shè)置

3.6.3?方法區(qū)的內(nèi)部結(jié)構(gòu)

3.6.4?方法區(qū)的垃圾回收

四、本地方法接口

?4.1 什么是本地方法

4.2 為什么要使用 Native Method

五、執(zhí)行引擎

5.1 概述

5.2 什么是解釋器?什么是 JIT 編譯器?

5.3 為什么 Java 是半編譯半解釋型語言?

六、垃圾回收

6.1 垃圾回收概述

6.1.1 概述

6.1.2 什么是垃圾?

6.1.3 為什么需要 GC?

6.1.4 早期垃圾回收

6.1.5Java 垃圾回收機制

6.2 垃圾回收相關(guān)算法

6.2.1 垃圾標記階段算法

6.2.2 垃圾回收階段算法

6.3 垃圾回收相關(guān)概念

6.3.1System.gc() 的理解

6.3.2 內(nèi)存溢出與內(nèi)存泄漏

6.3.3Stop the World

6.4 垃圾回收器

6.4.1 垃圾回收器概述

6.4.2 垃圾回收器分類

6.4.3GC 性能指標

6.4.4HotSpot 垃圾收集器

6. 4.5CMS 回收器

6.4.6G1(Garbage First)回收器

6.4.7 查看 JVM 垃圾回收器設(shè)置垃圾回收器



一、JVM概述

1、1為什么要學習JVM

中高程序員必備技能:

項目管理、性能調(diào)優(yōu)

1、2虛擬機

  • 虛擬機(Virtual Machine),虛擬計算機。他是一款軟件,用來執(zhí)行一系列虛擬計算機指令。大體上,虛擬機可以分為系統(tǒng)虛擬機和程序虛擬機。
  • VMware屬于系統(tǒng)虛擬機,完全對物理計算機的仿真,提供一個可運行完整操作系統(tǒng)的平臺。程序虛擬機的典型代表是java虛擬機,專門為執(zhí)行某個計算機程序而設(shè)計。
    在java虛擬機中執(zhí)行的指令稱為java字節(jié)碼指令。
  • Java虛擬機是一種執(zhí)行java字節(jié)碼文件的虛擬機,它擁有獨立的運行機制。
  • Java技術(shù)的核心就是Java虛擬機,因為所有的java程序都要在java虛擬機內(nèi)部運行。

1、3JVM作用

? ? ? ?Java虛擬機負責裝載字節(jié)碼到其內(nèi)部,解釋/編譯為對應(yīng)平臺上的機器碼指令執(zhí)行,每一條java指令,java虛擬機中都有詳細定義,如怎么取操作數(shù),怎么處理操作數(shù),處理結(jié)果放在哪。


特點:

  1. 一次編譯到處運行;
  2. 自動內(nèi)存管理;
  3. 自動垃圾回收功能;

? ? ? ?現(xiàn)在的JVM不僅可以執(zhí)行java字節(jié)碼文件,還可以執(zhí)行其他語言編譯后的字節(jié)碼文件,是一個跨語言平臺。

jvm,Java進階,jvm,java,開發(fā)語言

1、4JVM整體組成部分?

  1. 類加載器(ClassLoader)
  2. 運行時數(shù)據(jù)區(qū)(Runtime Data Area)
  3. 執(zhí)行引擎(Execution Engine)
  4. 本地庫接口(Native Interface)

jvm,Java進階,jvm,java,開發(fā)語言

jvm,Java進階,jvm,java,開發(fā)語言

? ? ? ??程序在執(zhí)行之前先要把 java 代碼轉(zhuǎn)換成字節(jié)碼(class 文件),jvm 首先需要把字節(jié)碼通過一定的方式 類加載器(ClassLoader) 把文件加載到內(nèi)存中的運行時數(shù)據(jù)區(qū)(Runtime Data Area) ,而字節(jié)碼文件是 jvm 的一套指令集規(guī)范,并不能直接交個底層操作系統(tǒng)去執(zhí)行,因此需要特定的命令解析器 執(zhí)行引擎(Execution Engine) 將字節(jié)碼翻譯成底層系統(tǒng)指令再交由CPU 去執(zhí)行,而這個過程中需要調(diào)用其他語言的接口 本地庫接口(Native

Interface) 來實現(xiàn)整個程序的功能,這就是這 4 個主要組成部分的職責與功能。
? ? ? ?而我們通常所說的 JVM 組成指的是 運行時數(shù)據(jù)區(qū)(Runtime Data? Area) ,因為通常需要程序員調(diào)試分析的區(qū)域就是“運行時數(shù)據(jù)區(qū)”,或者更具體的來說就是“運行時數(shù)據(jù)區(qū)”里面的 Heap(堆)模塊。

二、JVM結(jié)構(gòu)--類加載器

2、1類加載子系統(tǒng)

jvm,Java進階,jvm,java,開發(fā)語言

? ? ? ? 類加載子系統(tǒng)負責從文件系統(tǒng)或者網(wǎng)絡(luò)中加載class文件。classLoader只負責class文件的加載,至于它是否可以裕興,則由Execution Engine決定。

? ? ? ?加載的類信息存放于一塊稱為方法區(qū)的內(nèi)存空間。

jvm,Java進階,jvm,java,開發(fā)語言

? ? ? ? class file 存在于硬盤上,可以理解為設(shè)計師畫在紙上的模板,而最終這個模板在執(zhí)行的時候是要加載 JVM 當中來,根據(jù)這個模板實例化出 n 個實例.
? ? ? ? class file 加載到 JVM 中,被稱為 DNA 元數(shù)據(jù)模板. 此過程就要有一個運輸工具(類加載器 Class Loader),扮演一個快遞員的角色

2、2類加載過程

jvm,Java進階,jvm,java,開發(fā)語言

2、2、1加載

  1. ?通過類名(地址)獲取此類的二進制字節(jié)流。
  2. 將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)(元空間)的運行時結(jié)構(gòu)。
  3. 在內(nèi)存中生成一個代表這個類的java.lang.class對象,作為這個類的各種數(shù)據(jù)的訪問入口。

2、2、2鏈接

  1. 驗證:檢驗被加載的類是否有正確的內(nèi)部結(jié)構(gòu),并和其他類協(xié)調(diào)一致;
    ? ? ? ? ? ?驗證文件格式是否一致: class 文件在文件開頭有特定的文件標識(字節(jié)碼文件都以 CA FE BA BE 標識開頭);主,次版本號是否在當前 java 虛擬機接收范圍內(nèi).
    ? ? ? ? ? ?元數(shù)據(jù)驗證:對字節(jié)碼描述的信息進行語義分析,以保證其描述的信息符合java 語言規(guī)范的要求,例如這個類是否有父類;是否繼承瀏覽不允許被繼承的類(final 修飾的類).....
  2. 準備:準備階段則負責為類的靜態(tài)屬性分配內(nèi)存,并設(shè)置默認初始值;
    ? ? ? ? ? ?
    不包含用 final 修飾的 static 常量,在編譯時進行初始化.
    ? ? ? ? ? ?
    例如: public static int value = 123;value 在準備階段后的初始值是 0,而不是 123.
  3. 解析:將類的二進制數(shù)據(jù)中的符號引用替換成直接引用(符號引用是 Class 文件的邏輯符號,直接引用指向的方法區(qū)中某一個地址)

2、2、3初始化

? ? ? ?初始化,為類的靜態(tài)變量賦予正確的初始值,JVM 負責對類進行初始化,主要對類變量進行初始化。初始化階段就是執(zhí)行底層類構(gòu)造器方法<clinit>()的過程。此方法不需要定義,是 javac 編譯器自動收集類中的所有類變量的賦值動作和靜態(tài)代碼塊中的語句合并而來的。
類什么時候初始化:
? ? ? ?JVM規(guī)定:每個類或者接口被首次主動使用時才對其進行初始化。
  • 通過 new 關(guān)鍵字創(chuàng)建對象
  • 訪問類的靜態(tài)變量,包括讀取和更新
  • 訪問類的靜態(tài)方法
  • 對某個類進行反射操作
  • 初始化子類會導致父類的的初始化
  • 執(zhí)行該類的 main 函數(shù)

除了以上幾種主動使用,以下情況被動使用,不會加載類:

  1. 引用該類的靜態(tài)常量,注意是常量,不會導致初始化,但是也有意外,這里的常量是指已經(jīng)指定字面量的常量,對于那些需要一些計算才能得出結(jié)果的常量就會導致類加載,比如:
    public final static int NUMBER = 5 ; //不會導致類初始化,被動使用
    public final static int RANDOM = new Random().nextInt() ; //會導致類加載
  2. 構(gòu)造某個類的數(shù)組時不會導致該類的初始化,比如:
    Student[] students = new Student[10] ;

類的初始化順序

對static修飾的變量或語句塊進行賦值
? ? ? ?如果同時包含多個靜態(tài)變量和靜態(tài)代碼塊,則按照自上而下的順序依次執(zhí)行。
? ? ? ?如果初始化一個類的時候,其父類尚未初始化,則優(yōu)先初始化其父類。
? ? ? ?順序是:父類 static –> 子類 static

public class ClassInit{
    static{
        num = 20;
    }
    static int num = 10;
    public static void main (String[] args) {
        //num從準備到初始化值變化過程 num=0 -> num=20 -> num=10
        System.out.println(num);//10
    }
}


public class ClassInit{
    static int num = 10;
    static{
        num = 20;
    }
    public static void main (String[] args) {
        //num從準備到初始化值變化過程 num=0 -> num=10 -> num=20
        System.out.println(num);//20
    }
}


2、3類加載器分類

? ? ? ?站在JVM的角度看,類加載器可以分為兩種:

  • 引導類加載器(啟動類加載器 Bootstrap ClassLoader).
  • 其他所有類加載器,這些類加載器由 java 語言實現(xiàn),獨立存在于虛擬機外部,并 且全部繼承自抽象類 java.lang.ClassLoader.
? ? ? ?站在 java 開發(fā)人員的角度來看,類加載器就應(yīng)當劃分得更細致一些.自 JDK1.2 以來 java 一直保持者三層類加載器

jvm,Java進階,jvm,java,開發(fā)語言

2.3.1 引導類加載器(啟動類加載器 BootStrap ClassLoader)

? ? ? ?這個類加載器使用 C/C++語言實現(xiàn),嵌套在 JVM 內(nèi)部.它用來加載 java 核心類庫.并不繼承于 java.lang.ClassLoader 沒有父加載器.
? ? ? ?負責加載擴展類加載器和應(yīng)用類加載器,并為他們指定父類加載器.
? ? ? ?出于安全考慮,引用類加載器只加載存放在<JAVA_HOME>\lib 目錄,或者被-Xbootclasspath 參數(shù)鎖指定的路徑中存儲放的類.

2.3.2 擴展類加載器(Extension ClassLoader)

? ? ? ?Java 語言編寫的,由 sun.misc.Launcher$ExtClassLoader 實現(xiàn).
? ? ? ?派生于 ClassLoader 類.
? ? ? ?從 java.ext.dirs 系統(tǒng)屬性所指定的目錄中加載類庫,或從 JDK 系統(tǒng)安裝目錄的jre/lib/ext 子目錄(擴展目錄)下加載類庫.如果用戶創(chuàng)建的 jar 放在此目錄下,也會自動由擴展類加載器加載

2.3.3 應(yīng)用程序類加載器(系統(tǒng)類加載器 Application ClassLoader)

? ? ? ?Java 語言編寫的,由 sun.misc.Launcher$AppClassLoader 實現(xiàn).
? ? ? ?派生于 ClassLoader 類.
? ? ? ?加載我們自己定義的類,用于加載用戶類路徑(classpath)上所有的類.
? ? ? ?該類加載器是程序中默認的類加載器.
? ? ? ?ClassLoader 類 , 它 是 一 個 抽 象 類 , 其 后 所 有 的 類 加 載 器 都 繼 承 自 ClassLoader(不包括啟動類加載器)

2.4 雙親委派機制

? ? ? ?Java 虛擬機對 class 文件采用的是按需加載的方式,也就是說當需要該類時才會將它的 class 文件加載到內(nèi)存中生成 class 對象.而且加載某個類的 class 文件時,Java 虛擬機采用的是雙親委派模式,即把請求交由父類處理,它是一種任務(wù)委派模式

jvm,Java進階,jvm,java,開發(fā)語言

?工作原理:

  1. 如果一個類加載器收到了類加載請求,它并不會自己先去加載,而是把這個請 求委托給父類的加載器去執(zhí)行.
  2. 如果父類加載器還存在其父類加載器,則進一步向上委托,依次遞歸,請求最終 將到達頂層的啟動類加載器.
  3. 如果父類加載器可以完成類的加載任務(wù),就成功返回,倘若父類加載器無法完 成加載任務(wù),子加載器才會嘗試自己去加載,這就是雙親委派機制.
  4. 如果均加載失敗,就會拋出 ClassNotFoundException 異常。jvm,Java進階,jvm,java,開發(fā)語言

?雙親委派優(yōu)點:

  1. 安全,可避免用戶自己編寫的類替換 Java 的核心類,如 java.lang.String.
  2. 避免類重復加載,當父親已經(jīng)加載了該類時,就沒有必要子 ClassLoader 再加載一次

2.5 如何打破雙親委派機制

? ? ? ?Java 虛擬機的類加載器本身可以滿足加載的要求,但是也允許開發(fā)者自定義類加載器。
? ? ? ?在 ClassLoader 類中涉及類加載的方法有兩個,loadClass(String name), findClass(String name),這兩個方法并沒有被 final 修飾,也就表示其他子類可以重寫.
? ? ? ?重寫 loadClass 方法(是實現(xiàn)雙親委派邏輯的地方,修改他會破壞雙親委派機制, 不推薦)
重寫 findClass 方法 (推薦)
? ? ? ?我們可以通過自定義類加載重寫方法打破雙親委派機制, 再例如 tomcat 等都有自己定義的類加載器.

三、JVM運行時數(shù)據(jù)區(qū)

3、1運行時數(shù)據(jù)區(qū)組成概述

JVM 的運行時數(shù)據(jù)區(qū),不同虛擬機實現(xiàn)可能略微有所不同,但都會遵從 Java 虛擬機規(guī)范,
Java 8 虛擬機規(guī)范規(guī)定,Java 虛擬機所管理的內(nèi)存將會包括以下幾個運行時數(shù)據(jù)區(qū)域:

3.1.1 程序計數(shù)器(Program Counter Register)

? ? ? ?程序計數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間,它可以看作是當前線程所執(zhí)行的字節(jié)碼的行號指示器。

3.1.2Java 虛擬機棧(Java Virtual Machine Stacks)

? ? ? ?描述的是 Java 方法執(zhí)行的內(nèi)存模型,每個方法在執(zhí)行的同時都會創(chuàng)建一個線幀(Stack Frame)用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息,每個方法從調(diào)用直至執(zhí)行完成的過程,都對應(yīng)著一個線幀在虛擬機棧中入棧到出棧的過程。

3.1.3 本地方法棧(Native Method Stack)

? ? ? ?與虛擬機棧的作用是一樣的,只不過虛擬機棧是服務(wù) Java 方法的,而本地方法棧是為虛擬機調(diào)用 Native 方法服務(wù)的。

3.1.4Java 堆(Java Heap)

? ? ? 是 Java 虛擬機中內(nèi)存最大的一塊,是被所有線程共享的,在虛擬機啟動時候創(chuàng)
建,Java 堆唯一的目的就是存放對象實例,幾乎所有的對象實例都在這里分配 內(nèi)存.

3.1.5 方法區(qū)(Methed Area)

? ? ? ?用于存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯后的代碼等數(shù)據(jù)。方法區(qū)是很重要的系統(tǒng)資源,是硬盤和 CPU 的中間橋梁,承載著操作系統(tǒng)和應(yīng)用程序的實時運行.
? ? ? ?JVM 內(nèi)存布局規(guī)定了 Java 在運行過程中內(nèi)存申請,分配,管理的策略,保證了 JVM的高效穩(wěn)定運行.不同的 JVM 對于內(nèi)存的劃分方式和管理機制存在著部分差異, 以最為流行的 HotSpot 虛擬機為例:

jvm,Java進階,jvm,java,開發(fā)語言

? ? ? ?Java 虛擬機定義了程序運行期間會使用到的運行數(shù)據(jù)區(qū),其中有一些會隨著虛擬機啟動而創(chuàng)建,隨著虛擬機退出而銷毀.另外一些則是與線程一一對應(yīng)的. 這些與線程對應(yīng)的區(qū)域會隨著線程開始和結(jié)束而創(chuàng)建銷毀.
? ? ? ?如圖: 紅色的為多個線程共享,灰色的為單個線程私有的,即 線程間共享:堆,方法區(qū). 線程私有:程序計數(shù)器,棧,本地方法棧.

jvm,Java進階,jvm,java,開發(fā)語言

3.2.程序計數(shù)器(Program Counter Register)

JVM 中的程序計數(shù)寄存器(Program Counter Register)這里翻譯為程序計數(shù)器更容易理解.
程序計數(shù)器用來存儲下一條指令的地址,也即將要執(zhí)行的指令代碼.由執(zhí)行引擎讀取下一條指令.

jvm,Java進階,jvm,java,開發(fā)語言

  • 它是一塊很小的內(nèi)存空間,幾乎可以忽略不計,也是運行速度最快的存儲區(qū)域.
  • 在 JVM 規(guī)范中,每個線程都有它自己的程序計數(shù)器,是線程私有的,生命周期與線程生命周期保持一致.
  • 程序計數(shù)器會存儲當前線程正在執(zhí)行的 Java 方法的 JVM 指令地址.
  • 它是程序控制流的指示器,分支,循環(huán),跳轉(zhuǎn),異常處理,線程恢復等基礎(chǔ)功能都需要依賴這個計數(shù)器來完成.
  • 它是唯一一個在java虛擬機規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域

jvm,Java進階,jvm,java,開發(fā)語言

jvm,Java進階,jvm,java,開發(fā)語言

3.3.Java 虛擬機棧(Java Virtual Machine Stacks)

棧的基本概念
? ? ? 棧是運行時的單位,即棧解決程序的運行問題,即程序如何執(zhí)行,或者說如何處 理數(shù)據(jù). Java 虛擬機棧(Java Virtual Machine Stack),早期也叫 Java 棧.每個線程在創(chuàng)建時都會創(chuàng)建一個虛擬機棧,其內(nèi)部保存一個個棧幀,對應(yīng)著一次方法的調(diào)用.Java 虛擬機棧是線程私有的.主管 Java 程序的運行,它保存方法的局部變量(8種基本數(shù)據(jù)類型,對象的引用地址),部分結(jié)果,并參與方法的調(diào)用和返回.

jvm,Java進階,jvm,java,開發(fā)語言

棧的特點
棧是一種快速有效的分配存儲方式,訪問速度僅次于程序計數(shù)器.
JVM 直接對 java 棧的操作只有兩個:調(diào)用方法 入棧 .執(zhí)行結(jié)束后 出棧 .
對于棧來說不存在垃圾回收問題.

jvm,Java進階,jvm,java,開發(fā)語言

棧中會出現(xiàn)異常,當線程請求的棧深度大于虛擬機所允許的深度時 , 會出現(xiàn)StackOverflowError.

棧的運行原理
  • JVM 直接對 java 棧的操作只有兩個,就是對棧幀的入棧和出棧,遵循先進后出/后進先出的原則.
  • 在一條活動的線程中,一個時間點上,只會有一個活動棧.即只有當前在執(zhí)行的方法的棧幀(棧頂)是有效地,這個棧幀被稱為當前棧(Current Frame),與當前棧幀對應(yīng)的方法稱為當前方法(CurrentMethod),定義這個方法的類稱為當前類(Current Class).
  • 執(zhí)行引擎運行的所有字節(jié)碼指令只針對當前棧幀進行操作.
  • 如果在該方法中調(diào)用了其他方法,對應(yīng)的新的棧幀就會被創(chuàng)建出來,放在棧的頂端,成為新的當前棧幀.

jvm,Java進階,jvm,java,開發(fā)語言

?? ? ? ?不同線程中所包含的棧幀(方法)是不允許存在相互引用的,即不可能在一個棧中引用另一個線程的棧幀(方法).
? ? ? ?如果當前方法調(diào)用了其他方法,方法返回之際,當前棧幀會傳回此方法的執(zhí)行結(jié)果給前一個棧幀,接著虛擬機會丟棄當前棧幀,使得前一個棧幀重新成為當前棧幀.
? ? ? ?Java 方法有兩種返回的方式,一種是正常的函數(shù)返回,使用 return 指令,另一種是拋出異常.不管哪種方式,都會導致棧幀被彈出.

棧幀的內(nèi)部結(jié)構(gòu)
每個棧幀中存儲著:
  • 局部變量表(Local Variables)
    ? ? ? ?局部變量表是一組變量值存儲空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量。對于基本數(shù)據(jù)類型的變量,則直接存儲它的值,對于引用類型的變量,則存的是指向?qū)ο蟮囊谩?
  • 操作數(shù)棧(Operand Stack)(或表達式棧)
    ? ? ? ?棧最典型的一個應(yīng)用就是用來對表達式求值。在一個線程執(zhí)行方法的過程中,實際上就是不斷執(zhí)行語句的過程,而歸根到底就是進行計算的過程。因此可以這么說,程序中的所有計算過程都是在借助于操作數(shù)棧來完成的。
  • 動態(tài)鏈接(Dynamic Linking) (或指向運行時常量池的方法引用)
    ? ? ? ?因為在方法執(zhí)行的過程中有可能需要用到類中的常量,所以必須要有一個引用指向運行時常量。
  • 方法返回地址(Retuen Address)(或方法正常退出或者異常退出的定義)
    ? ? ? ?當一個方法執(zhí)行完畢之后,要返回之前調(diào)用它的地方,因此在棧幀中必須保存一個方法返回地址。

jvm,Java進階,jvm,java,開發(fā)語言

3.4.本地方法棧(Native Method Stack)

  • Java 虛擬機棧管理 java 方法的調(diào)用,而本地方法棧用于管理本地方法的調(diào)用.
  • 本地方法棧也是線程私有的.
  • 允許被實現(xiàn)成固定或者是可動態(tài)擴展的內(nèi)存大小.內(nèi)存溢出方面也是相同的.
  • 如果線程請求分配的棧容量超過本地方法棧允許的最大容量拋出 StackOverflowError.?本地方法是用 C 語言寫的.
  • 它的具體做法是在 Native Method Stack 中登記 native 方法,在 Execution Engine 執(zhí)行時加載本地方法庫.

3.5.Java 堆內(nèi)存

3.5.1Java堆內(nèi)存概述

jvm,Java進階,jvm,java,開發(fā)語言

  • 一個 JVM 實例只存在一個堆內(nèi)存,堆也是 Java 內(nèi)存管理的核心區(qū)域.
  • Java 堆區(qū)在 JVM 啟動時的時候即被創(chuàng)建,其空間大小也就確定了,是 JVM 管理的最大一塊內(nèi)存空間.
  • 堆內(nèi)存的大小是可以調(diào)節(jié).
    ? ? ? ?例如: -Xms:10m(堆起始大小) -Xmx:30m(堆最大內(nèi)存大小)
    ? ? ? ?一般情況可以將起始值和最大值設(shè)置為一致,這樣會減少垃圾回收之后堆內(nèi)存重新分配大小的次數(shù),提高效率.
  • Java 虛擬機規(guī)范》規(guī)定,堆可以處于物理上不連續(xù)的內(nèi)存空間中,但邏輯上它應(yīng)該被視為連續(xù)的.
  • 所有的線程共享 Java 堆,在這里還可以劃分線程私有的緩沖區(qū).
  • Java 虛擬機規(guī)范》中對 Java 堆的描述是:所有的對象實例都應(yīng)當在運行時分配在堆上.
  • 在方法結(jié)束后,堆中的對象不會馬上被移除,僅僅在垃圾收集的時候才會被移除.
  • 堆是 GC(Garbage Collection,垃圾收集器)執(zhí)行垃圾回收的重點區(qū)域.

3.5.2堆內(nèi)存區(qū)域劃分

Java8 及之后堆內(nèi)存分為 :新生區(qū)(新生代)+老年區(qū)(老年代)
新生區(qū)分為 Eden(伊甸園)區(qū)和 Survivor(幸存者)區(qū)
jvm,Java進階,jvm,java,開發(fā)語言

3.5.3 為什么分區(qū)(代)?

? ? ? ?將對象根據(jù)存活概率進行分類,對存活時間長的對象,放到固定區(qū),從而減少掃描垃圾時間及 GC 頻率。
? ? ? ?針對分類進行不同的垃圾回收算法,對算法揚長避短。

jvm,Java進階,jvm,java,開發(fā)語言

3.5.4 對象創(chuàng)建內(nèi)存分配過程

? ? ? ?為新對象分配內(nèi)存是一件非常嚴謹和復雜的任務(wù),JVM 的設(shè)計者們不僅需要考慮內(nèi)存如何分配,在哪分配等問題,并且由于內(nèi)存分配算法與內(nèi)存回收算法密切相關(guān),所以還需要考慮 GC 執(zhí)行完內(nèi)存回收后是否會在內(nèi)存空間中產(chǎn)生內(nèi)存碎片.
  1. new 的新對象先放到伊甸園區(qū),此區(qū)大小有限制.
  2. 當伊甸園的空間填滿時,程序又需要創(chuàng)建對象時,JVM 的垃圾回收器將對伊甸園區(qū)進行垃圾回收(Minor GC),將伊甸園區(qū)中的不再被引用的對象進行銷毀.再加載新的對象放到伊甸園區(qū).
  3. 然后將伊甸園區(qū)中的剩余對象移動到幸存者 0 區(qū).
  4. 如果再次出發(fā)垃圾回收,此時上次幸存下來存放到幸存者 0 區(qū)的對象,如果沒有回收,就會被放到幸存者 1 區(qū),每次會保證有一個幸存者區(qū)是空的.
  5. 如果再次經(jīng)歷垃圾回收,此時會重新放回幸存者 0 區(qū),接著再去幸存者 1 區(qū).
  6. 什么時候去養(yǎng)老區(qū)呢?默認是 15 次,也可以設(shè)置參數(shù),最大值為 15
    ? ? ? ?-XX:MaxTenuringThreshold=<N>
    ? ? ? ?在對象頭中,它是由 4 位數(shù)據(jù)來對 GC 年齡進行保存的,所以最大值為 1111,即為15。所以在對象的 GC 年齡達到 15 時,就會從新生代轉(zhuǎn)到老年代。
  7. 在老年區(qū),相對悠閑,當養(yǎng)老區(qū)內(nèi)存不足時,再次觸發(fā) Major GC,進行養(yǎng)老區(qū)的內(nèi)存清理.
  8. 若養(yǎng)老區(qū)執(zhí)行了 Major GC 之后發(fā)現(xiàn)依然無法進行對象保存,就會產(chǎn)生 OOM 異常. Java.lang.OutOfMemoryError:Java heap space

例如:

public static void main(String[] args) {
    List<Integer> list = new ArrayList();
    while(true){
        list.add(new Random().nextInt());
    }
}

jvm,Java進階,jvm,java,開發(fā)語言

3.5.5 新生區(qū)與老年區(qū)配置比例

配置新生代與老年代在堆結(jié)構(gòu)的占比(一般不會調(diào))
  1. 默認**-XX:NewRatio**=2,表示新生代占 1,老年代占 2,新生代占整個堆的 1/3
  2. 可以修改**-XX:NewRatio**=4,表示新生代占 1,老年代占 4,新生代占整個堆的 1/5
  3. 當發(fā)現(xiàn)在整個項目中,生命周期長的對象偏多,那么就可以通過調(diào)整老年代的大小,來進行調(diào)優(yōu) jvm,Java進階,jvm,java,開發(fā)語言
  4. 在 HotSpot 中,Eden 空間和另外兩個 survivor 空間缺省所占的比例是 8 : 1 :1,當然開發(fā)人員可以通過選項**-XX:SurvivorRatio**調(diào)整這個空間比例。比如-XX:SurvivorRatio=8,新生區(qū)的對象默認生命周期超過 15 ,就會去養(yǎng)老區(qū)養(yǎng)老

?3.5.6 分代收集思想 Minor GC、Major GC、Full GC

? ? ? ?JVM 在進行 GC 時,并非每次都新生區(qū)和老年區(qū)一起回收的,大部分時候回收的都是指新生區(qū).針
對 HotSpot VM 的實現(xiàn),它里面的 GC 按照回收區(qū)域又分為兩大類型:一種是部分收集,一種是整堆收集.
? ? ? ?部分收集:不是完整收集整個 java 堆的垃圾收集.其中又分為:
? ? ? ? ? ? ? ?新生區(qū)收集(Minor GC/Yong GC):只是新生區(qū)(Eden,S0,S1)的垃圾收集.
? ? ? ? ? ? ? ?老年區(qū)收集(Major GC / Old GC):只是老年區(qū)的垃圾收集.
? ? ? ?整堆收集(Full GC):收集整個 java 堆和方法區(qū)的垃圾收集.
? ? ? ? ? ? ? ?整堆收集出現(xiàn)的情況:
? ? ? ? ? ? ? ? ? ? ? ? ? System.gc();時
? ? ? ? ? ? ? ? ? ? ? ? ? 老年區(qū)空間不足
? ? ? ? ? ? ? ? ? ? ? ? ? 方法區(qū)空間不足
? ? ? ? ? ? ? ? ? ? ? ? ? 開發(fā)期間盡量避免整堆收集

3.5.7 堆空間的參數(shù)設(shè)置

官網(wǎng)地址
-XX:+PrintFlagsInitial
查看所有參數(shù)的默認初始值
-XX:+PrintFlagsFinal
查看所有參數(shù)的最終值(修改后的值)
-Xms
初始堆空間內(nèi)存(默認為物理內(nèi)存的 1/64)
-Xmx
最大堆空間內(nèi)存(默認為物理內(nèi)存的 1/4)
-Xmn
設(shè)置新生代的大小(初始值及最大值)
-XX:NewRatio
配置新生代與老年代在堆結(jié)構(gòu)的占比
-XX:SurvivorRatio
設(shè)置新生代中 Eden 和 S0/S1 空間比例
-XX:MaxTenuringTreshold
設(shè)置新生代垃圾的最大年齡
XX:+PrintGCDetails
輸出詳細的 GC 處理日志

3.5.8 字符串常量池

字符串常量池為什么要調(diào)整位置?
? ? ? ?JDK7 及以后的版本中將字符串常量池放到了堆空間中。因為方法區(qū)的回收效率很低,在 Full GC 的時候才會執(zhí)行永久代的垃圾回收,而 Full GC 是老年代的空間不足、方法區(qū)不足時才會觸發(fā)。
? ? ? ?這就導致字符串常量池回收效率不高,而我們開發(fā)中會有大量的字符串被創(chuàng)建,回收效率低,導致永久代內(nèi)存不足。放到堆里,能及時回收內(nèi)存。
public static void main(String[] args) {
    String temp = "world";
    for (int i = 0; i < Integer.MAX_VALUE; i++) {
        String str = temp + temp;
        temp = str;
        str.intern();//將字符串存儲到字符串常量池中
    }
}

3.6方法區(qū)

3.6.1 方法區(qū)的基本理解

? ? ? ?方法區(qū),是一個被線程共享的內(nèi)存區(qū)域。其中主要存儲加載的類字節(jié)碼、class/method/field 等元數(shù)據(jù)、static final 常量、static 變量、即時編譯器編譯后的代碼等數(shù)據(jù)。另外,方法區(qū)包含了一個特殊的區(qū)域“運行時常量池”。

? ? ? ?Java 虛擬機規(guī)范中明確說明:”盡管所有的方法區(qū)在邏輯上是屬于堆的一部分,但對HotSpotJVM 而言,方法區(qū)還有一個別名叫做 Non-Heap(非堆),目的就是要和堆分開.
所以,方法區(qū)看做是一塊獨立于 java 堆的內(nèi)存空間.
jvm,Java進階,jvm,java,開發(fā)語言

??? ? ?方法區(qū)在 JVM 啟動時被創(chuàng)建,并且它的實際的物理內(nèi)存空間中和 Java 堆區(qū)一樣都可以是不連續(xù)的.
? ? ? ?方法區(qū)的大小,跟堆空間一樣,可以選擇固定大小或者可擴展.
? ? ? ?方法區(qū)的大小決定了系統(tǒng)可以保存多少個類,如果系統(tǒng)定義了太多的類,導致方法區(qū)溢出, 虛擬機同樣會拋出內(nèi)存溢出的錯誤
? ? ? ?關(guān)閉 JVM 就會釋放這個區(qū)域的內(nèi)存.

方法區(qū),棧,堆的交互關(guān)系

jvm,Java進階,jvm,java,開發(fā)語言

?

3.6.2 方法區(qū)大小設(shè)置

Java 方法區(qū)的大小不必是固定的,JVM 可以根據(jù)應(yīng)用的需要動態(tài)調(diào)整.

  1. 元數(shù)據(jù)區(qū)大小可以使用參數(shù)-XX:MetaspaceSize 和 -XX:MaxMataspaceSize 指定,替代上述原有的兩個參數(shù).
  2. 默認值依賴于平臺,windows 下,-XXMetaspaceSize 是 21MB,
  3. -XX:MaxMetaspaceSize 的值是-1,級沒有限制.
  4. 這個-XX:MetaspaceSize 初始值是 21M 也稱為高水位線 一旦觸及就會觸發(fā) Full GC.
  5. 因此為了減少 FullGC 那么這個-XX:MetaspaceSize 可以設(shè)置一個較高的值

3.6.3?方法區(qū)的內(nèi)部結(jié)構(gòu)

jvm,Java進階,jvm,java,開發(fā)語言

? ? ? ?方法區(qū)它用于存儲已被虛擬機加載的類型信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼緩存,運行常量池等。
? ? ? ?運行常量池就是一張表,虛擬機指令根據(jù)這張表,找到要執(zhí)行的類名、方法名、參數(shù)類型、字面量(常量)等信息,存放編譯期間生成的各種字面量(常量)和符號引用。
通過反編譯字節(jié)碼文件查看.
? ? ? ?反編譯字節(jié)碼文件,并輸出值文本文件中,便于查看。參數(shù) -p 確保能查看private 權(quán)限類型的字段或方法

?javap -v -p Demo.class > test.txt

jvm,Java進階,jvm,java,開發(fā)語言

?

3.6.4?方法區(qū)的垃圾回收

  1. 有些人認為方法區(qū)(如 Hotspot 虛擬機中的元空間或者永久代)是沒有垃圾收集行為的,其實不然。《Java 虛擬機規(guī)范》對方法區(qū)的約束是非常寬松的,提到過可以不要求虛擬機在方法區(qū)中實現(xiàn)垃圾收集。
  2. 一般來說這個區(qū)域的回收效果比較難令人滿意,尤其是類型的卸載,條件相當苛刻。但是這部分區(qū)域的回收有時又確實是必要的。

方法區(qū)的垃圾收集主要回收兩部分內(nèi)容:運行時常量池中廢棄的常量和不再使用的類型。

下面也稱作類卸載
? ? ? ?判定一個常量是否“廢棄”還是相對簡單,而要判定一個類型是否屬于“不再被使用的類”的條件就比較苛刻了。需要同時滿足下面三個條件:
  1. 該類所有的實例都已經(jīng)被回收,也就是 Java 堆中不存在該類及其任何派生子類的實例。
  2. 加載該類的類加載器已經(jīng)被回收,這個條件除非是經(jīng)過精心設(shè)計的可替換類加載器的場景,如 OSGi、JSP 的重加載等,否則通常是很難達成的。
  3. 該類對應(yīng)的 java.lang.Class 對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。

四、本地方法接口

jvm,Java進階,jvm,java,開發(fā)語言

?4.1 什么是本地方法

? ? ? ?簡單來講, 一個 Native Method 就是一個 java 調(diào)用非 java 代碼的接口 ,一個Native Method 是這樣一個 java 方法:該方法的底層實現(xiàn)由非 Java 語言實現(xiàn),比如 C。這個特征并非 java 特有,很多其他的編程語言都有這一機制在定義一個 native method 時,并不提供實現(xiàn)體(有些像定義一個 Java interface),因為其實現(xiàn)體是由非 java 語言在外面實現(xiàn)的。
? ? ? ?關(guān)鍵字 native 可以與其他所有的 java 標識符連用,但是 abstract 除外。

4.2 為什么要使用 Native Method

? ? ? Java 使用起來非常方便,然而有些層次的任務(wù)用 java 實現(xiàn)起來不容易,或者我們對程序的效率很在意時,問題就來了。
  1. 與 java 環(huán)境外交互。
    ? ? ? ?有時 java 應(yīng)用需要與 java 外面的環(huán)境交互,這是本地方法存在的主要原因。 你可以想想 java 需要與一些底層系統(tǒng),如某些硬件交換信息時的情況。本地方法正式這樣的一種交流機制:它為我們提供了一個非常簡潔的接口,而且我們無需去了解 java 應(yīng)用之外的繁瑣細節(jié)。
  2. Sun 的解釋器是用 C 實現(xiàn)的,這使得它能像一些普通的 C 一樣與外部交互。
    ? ? ? ?jre大部分是用 java 實現(xiàn)的,它也通過一些本地方法與外界交互。例如:類 java.lang.Thread 的 setPriority()方法是用 Java 實現(xiàn)的,但是它實現(xiàn)調(diào)用的是該類里的本地方法 setPriority0()。

五、執(zhí)行引擎

5.1 概述

  1. 執(zhí)行引擎是 Java 虛擬機核心的組成部分之一。
  2. JVM 的主要任務(wù)是負責裝載字節(jié)碼到其內(nèi)部,但字節(jié)碼并不能夠直接運行在操作系統(tǒng)之上,因為字節(jié)碼指令并非等價于本地機器指令,它內(nèi)部包含的僅僅只是一些能夠被 JVM 所識別的字節(jié)碼指令、符號表,以及其他輔助信息。
  3. 那么,如果想要讓一個 Java 程序運行起來,執(zhí)行引擎(Execution Engine)的任務(wù)就是將字節(jié)碼指令解釋/編譯為對應(yīng)平臺上的本地機器指令才可以。簡單來說,JVM 中的執(zhí)行引擎充當了將高級語言翻譯為機器語言的譯者。
注意區(qū)分概念:
  1. 前端編譯:從 Java 程序員-字節(jié)碼文件的這個過程叫前端編譯.
  2. 執(zhí)行引擎這里有兩種行為:一種是解釋執(zhí)行,一種是編譯執(zhí)行(這里的是后端編譯)。

5.2 什么是解釋器?什么是 JIT 編譯器?

解釋器: 當 Java 虛擬機啟動時會根據(jù)預定義的規(guī)范對字節(jié)碼采用逐行解釋的方式執(zhí)行,將每條字
節(jié)碼文件中的內(nèi)容“翻譯”為對應(yīng)平臺的本地機器指令執(zhí)行。
JIT(Just In Time Compiler)編譯器: 就是虛擬機將源代碼一次性直接編譯成和本地機器平臺相關(guān)的機器語言,但并不是馬上執(zhí)行。

5.3 為什么 Java 是半編譯半解釋型語言?

? ? ? ?起初將 Java 語言定位為“解釋執(zhí)行”還是比較準確的。再后來,Java 也發(fā)展出可以直接生成本地代碼的編譯器?,F(xiàn)在 JVM 在執(zhí)行 Java 代碼的時候,通常都會將解釋執(zhí)行與編譯執(zhí)行二者結(jié)合起來進行。
原因:
? ? ? ?JVM 設(shè)計者們的初衷僅僅只是單純地為了滿足 Java 程序?qū)崿F(xiàn)跨平臺特性,因此避免采用靜態(tài)編譯的方式由高級語言直接生成本地機器指令,從而誕生了實現(xiàn)解釋器在運行時采用逐行解釋字節(jié)碼執(zhí)行程序的想法。
? ? ? ?解釋器真正意義上所承擔的角色就是一個運行時“翻譯者”,將字節(jié)碼文件中的內(nèi)容“翻譯”為對應(yīng)平臺的本地機器指令執(zhí)行,執(zhí)行效率低。
? ? ? ?JIT 編譯器將字節(jié)碼翻譯成本地代碼后,就可以做一個緩存操作,存儲在方法區(qū)的 JIT 代碼緩存中(執(zhí)行效率更高了)。
? ? ? ?是否需要啟動 JIT 編譯器將字節(jié)碼直接編譯為對應(yīng)平臺的本地機器指令,則需要根據(jù)代碼被調(diào)用執(zhí)行的頻率而定。
? ? ? ?JIT 編譯器在運行時會針對那些頻繁被調(diào)用的“熱點代碼”做出深度優(yōu)化,將其直接編譯為對應(yīng)平臺的本地機器指令,以此提升 Java 程序的執(zhí)行性能。
? ? ? ?一個被多次調(diào)用的方法,或者是一-個方法體內(nèi)部循環(huán)次數(shù)較多的循環(huán)體都可以被稱之為“熱點代碼”。
? ? ? ?目前 HotSpot VM 所采用的熱點探測方式是基于計數(shù)器的熱點探測。
JIT 編譯器執(zhí)行效率高為什么還需要解釋器?
  1. 當程序啟動后,解釋器可以馬上發(fā)揮作用,響應(yīng)速度快,省去編譯的時間,立即執(zhí)行。
  2. 編譯器要想發(fā)揮作用,把代碼編譯成本地代碼,需要一定的執(zhí)行時間,但編譯為本地代碼后,執(zhí)行效率高。就需要采用解釋器與即時編譯器并存的架構(gòu)來換取一個平衡點。

六、垃圾回收

6.1 垃圾回收概述

6.1.1 概述

  1. Java 和 C++語言的區(qū)別,就在于垃圾收集技術(shù)和內(nèi)存動態(tài)分配上,C++語言沒有垃圾收集技術(shù),需要程序員手動的收集。
  2. 垃圾收集,不是 Java 語言的伴生產(chǎn)物。早在 1960 年,第一門開始使用內(nèi)存動態(tài)分配和垃圾收集技術(shù)的 Lisp 語言誕生。
  3. 關(guān)于垃圾收集有三個經(jīng)典問題:
    ? ? ? ?哪些內(nèi)存需要回收?
    ? ? ? ?什么時候回收?
    ? ? ? ?如何回收?
  4. 垃圾收集機制是 Java 的招牌能力,極大地提高了開發(fā)效率。如今,垃圾收集幾乎成為現(xiàn)代語言的標配,即使經(jīng)過如此長時間的發(fā)展,Java 的垃圾收集機制仍然在不斷的演進中,不同大小的設(shè)備、不同特征的應(yīng)用場景,對垃圾收集提出了新的挑戰(zhàn)。

6.1.2 什么是垃圾?

  • 垃圾是指在運行程序中沒有任何引用指向的對象,這個對象就是需要被回收的垃圾。
  • 如果不及時對內(nèi)存中的垃圾進行清理,那么,這些垃圾對象所占的內(nèi)存空間會一直保留到應(yīng)用程序結(jié)束,被保留的空間無法被其他對象使用。甚至可能導致內(nèi)存溢出。

6.1.3 為什么需要 GC?

想要學習 GC,首先需要理解為什么需要 GC?
  1. 對于高級語言來說,一個基本認知是如果不進行垃圾回收,內(nèi)存遲早都會被消耗完,因為不斷地分配內(nèi)存空間而不進行回收,就好像不停地生產(chǎn)生活垃圾而從來不打掃一樣。
  2. 除了釋放沒用的對象,垃圾回收也可以清除內(nèi)存里的記錄碎片。碎片整理將所占用的堆內(nèi)存移到堆的一端,以便 JVM 將整理出的內(nèi)存分配給新的對象。

6.1.4 早期垃圾回收

? ? ? ? 在早期的 C/C++時代,垃圾回收基本上是手工進行的。開發(fā)人員可以使用 new關(guān)鍵字進行內(nèi)存申請,并使用 delete 關(guān)鍵字進行內(nèi)存釋放。比如以下代碼:
MibBridge *pBridge= new cmBaseGroupBridge();
//如果注冊失敗,使用 Delete 釋放該對象所占內(nèi)存區(qū)域
if(pBridge->Register(kDestroy)!=NO ERROR)
delete pBridge;
? ? ? ?這種方式可以靈活控制內(nèi)存釋放的時間,但是會給開發(fā)人員帶來頻繁申請和釋放內(nèi)存的管理負擔。倘若有一處內(nèi)存區(qū)間由于程序員編碼的問題忘記被回收,那么就會產(chǎn)生內(nèi)存泄漏 ,垃圾對象永遠無法被清除,隨著系統(tǒng)運行時間的不斷增長,垃圾對象所耗內(nèi)存可能持續(xù)上升,直到出現(xiàn)內(nèi)存溢出并造成應(yīng)用程序崩潰。
? ? ? ?有了垃圾回收機制后,上述代碼極有可能變成這樣
MibBridge *pBridge=new cmBaseGroupBridge();
pBridge->Register(kDestroy);
? ? ? ?現(xiàn)在,除了 Java 以外,C#、Python、Ruby 等語言都使用了自動垃圾回收的思 想,也是未來發(fā)展趨勢,可以說這種自動化的內(nèi)存分配和來及回收方式已經(jīng)成為了現(xiàn)代開發(fā)語言必備的標準。

6.1.5Java 垃圾回收機制

6.1.5.1 自動內(nèi)存管理
自動內(nèi)存管理的優(yōu)點
? ? ? ? 自動內(nèi)存管理,無需開發(fā)人員手動參與內(nèi)存的分配與回收,這樣 降低內(nèi)存泄漏 內(nèi)存溢出的風險.
? ? ? ? 自動內(nèi)存管理機制,將程序員從繁重的內(nèi)存管理中釋放出來,可以 更專心地專注 于業(yè)務(wù)開發(fā).
6.1.5.2 關(guān)于自動內(nèi)存管理的擔憂
? ? ?1.對于 Java 開發(fā)人員而言,自動內(nèi)存管理就像是一個黑匣子,如果過度依賴于“自動”,那么這將會是一場災(zāi)難,最嚴重的就會弱化 Java 開發(fā)人員在程序出 現(xiàn)內(nèi)存溢出時定位問題和解決問題的能力。
? ? ?2.此時,了解 JVM 的自動內(nèi)存分配和內(nèi)存回收原理就顯得非常重要,只有在真正了解 JVM 是如何管理內(nèi)存后,我們才能夠在遇見 OutofMemoryError 時,快速地根據(jù)錯誤異常日志定位問題和解決問題。
? ? ?3.當需要排查各種內(nèi)存溢出、內(nèi)存泄漏問題時,當垃圾收集成為系統(tǒng)達到更高并發(fā)量的瓶頸時,我們就必須對這些“自動化”的技術(shù)實施必要的監(jiān)控和調(diào)節(jié) 。
6.1.5.3 應(yīng)該關(guān)心哪些區(qū)域的回收?

jvm,Java進階,jvm,java,開發(fā)語言

?

? ? ? ? 垃圾收集器可以對年輕代回收,也可以對老年代回收,甚至是全棧和方法區(qū)的回收,其中,
Java 堆是垃圾收集器的工作重點
? ? ? ? 從次數(shù)上講:
? ? ? ? ? ? ? ? 頻繁收集 Young 區(qū)
? ? ? ? ? ? ? ? 較少收集 Old 區(qū)
? ? ? ? ? ? ? ? 基本不收集元空間(方法區(qū))

6.2 垃圾回收相關(guān)算法

6.2.1 垃圾標記階段算法

6.2.1.1 標記階段的目的
垃圾標記階段:主要是為了判斷對象是否是垃圾對象
  1. 在堆里存放著幾乎所有的 Java 對象實例,在 GC 執(zhí)行垃圾回收之前,首先要區(qū)分出內(nèi)存中哪些是有用對象,哪些是垃圾對象。只有被標記為己經(jīng)是垃圾對,GC 才會在執(zhí)行垃圾回收時,釋放掉其所占用的內(nèi)存空間,因此這個過程我們可以稱為垃圾標記階段。
  2. 那么在 JVM 中究竟是如何標記一個垃圾對象呢?簡單來說,當一個對象已經(jīng)不再被任何引用指向時,就可以宣判為垃圾對象。
  3. 判斷對象是否為垃圾對象一般有兩種方式:引用計數(shù)算法和可達性分析算法
6.2.1.2 引用計數(shù)算法
  1. 引用計數(shù)算法(Reference Counting)比較簡單,對每個對象保存一個整型的引用計數(shù)器屬性。用于記錄對象被引用的情況。
  2. 對于一個對象 A,只要有任何一個引用指向了對象 A,則對象 A 的引用計數(shù)器就加 1;當引用失效時,引用計數(shù)器就減 1。只要對象 A 的引用計數(shù)器的值為 0,即表示對象 A 不可能再被使用,可進行回收。
  3. 優(yōu)點:實現(xiàn)簡單,垃圾對象便于辨識;判定效率高,回收沒有延遲性。
  4. 缺點:
    1.它需要單獨的字段存儲計數(shù)器,這樣的做法增加了存儲空間的開銷。
    2.每次賦值都需要更新計數(shù)器,伴隨著加法和減法操作,這增加了時間開銷。
    3.引用計數(shù)器有一個嚴重的問題,即無法處理循環(huán)引用的情況。這是一條致命缺陷,導致在.Java 的垃圾回收器中沒有使用這類算法。
jvm,Java進階,jvm,java,開發(fā)語言

?

6.2.1.3 可達性分析算法
可達性分析算法:也可以稱為根搜索算法、追蹤性垃圾收集
  1. 相對于引用計數(shù)算法而言,可達性分析算法不僅同樣具備實現(xiàn)簡單和執(zhí)行高效等特點,更重要的是該算法可以有效地解決在引用計數(shù)算法中循環(huán)引用的問題,防止內(nèi)存泄漏的發(fā)生。
  2. 相較于引用計數(shù)算法,這里的可達性分析就是 Java、C#選擇的。這種類型的垃圾收集通常也叫作追蹤性垃圾收集(Tracing Garbage Collection)
可達性分析實現(xiàn)思路
?
所謂"GCRoots”根就是一組必須活躍的引用
其基本思路如下:
? ? ? ?1.可達性分析算法是以根(GCRoots)為起始點,按照從上至下的方式搜索被根對象所連接的目標對象是否可達。
? ? ? ?2.使用可達性分析算法后,內(nèi)存中的存活對象都會被根直接或間接連接著,搜索所走過的路徑稱為引用鏈(Reference Chain)
? ? ? ?3.如果目標對象沒有任何引用鏈相連,則是不可達的,就意味著該對象己經(jīng)死亡,可以標記為垃圾對象。

jvm,Java進階,jvm,java,開發(fā)語言

?

GC Roots 可以是哪些元素?
  1. 虛擬機棧中引用的對象
    比如:各個線程被調(diào)用的方法中使用到的參數(shù)、局部變量等。
  2. 方法區(qū)中類靜態(tài)屬性引用的對象
    比如:
    Java 類的引用類型靜態(tài)變量
  3. 所有被同步鎖 synchronized 持有的對象
  4. Java 虛擬機內(nèi)部的引用。
    基 本 數(shù) 據(jù) 類 型 對 應(yīng) 的 Class 對 象 , 一 些 常 駐 的 異 常 對 象 ( 如 : NullPointerException、OutofMemoryError),系統(tǒng)類加載器。
6.2.1.4 對象的 finalization 機制
finalize() 方法機制:
? ? ? ? 對象銷毀前的回調(diào)方法:finalize();
? ? ? ?Java 語言提供了對象終止(finalization)機制來允許開發(fā)人員提供對象被銷毀之前的自定義處理邏輯。
? ? ? ?當垃圾回收器發(fā)現(xiàn)沒有引用指向一個對象,即:垃圾回收此對象之前,總會先調(diào)用這個對象的 finalize()方法,一個對象的 finalize()方法只被調(diào)用一次。
? ? ? ? finalize() 方法允許在子類中被重寫,用于在對象被回收時進行資源釋放。通常在這個方法中進行一些資源釋放和清理的工作,比如關(guān)閉文件、套接字和數(shù)據(jù)庫連接等。
Object 類中 finalize() 源碼
protected void finalize() throws Throwable { }
永遠不要主動調(diào)用某個對象的 finalize()方法,應(yīng)該交給垃圾回收機制調(diào)用。理由包括下面三點:
  1. 在 finalize()時可能會導致對象復活。
  2. finalize()方法的執(zhí)行時間是沒有保障的,它完全由 GC 線程決定,極端情況下,若不發(fā)生 GC,則 finalize()方法將沒有執(zhí)行機會。
  3. 一個糟糕的 finalize()會嚴重影響 GC 的性能。比如 finalize 是個死循環(huán)。

6.2.1.5 生存還是死亡?

由于 finalize()方法的存在,虛擬機中的對象一般處于三種可能的狀態(tài)。
?
? ? ? ? ?如果從所有的根節(jié)點都無法訪問到某個對象,說明對象己經(jīng)不再使用了。一般來說,此對象需要被回收。但事實上,也并非是“非死不可”的,這時候它們暫時處于“緩刑”階段。一個無法觸及的對象有可能在某一個條件下“復活”自己,如果這樣,那么對它立即進行回收就是不合理的。
? ? ?
為此,定義虛擬機中的對象可能的三種狀態(tài) 。如下:
  • 可觸及的:從根節(jié)點開始,可以到達這個對象。
  • 可復活的:對象的所有引用都被釋放,但是對象有可能在 finalize()中復活。
  • 不可觸及的:對象的 finalize()被調(diào)用,并且沒有復活,那么就會進入不可觸及狀態(tài)。
? ? ? ?以上 3 種狀態(tài)中,是由于 finalize()方法的存在,進行的區(qū)分。只有在對象不可觸及時才可以被回收。
具體過程

判定一個對象 objA 是否可回收,至少要經(jīng)歷兩次標記過程:
  1. 如果對象 objA 到 GC Roots 沒有引用鏈,則進行第一次標記。
  2. 進行篩選,判斷此對象是否有必要執(zhí)行 finalize()方法:
  • 如果對象 objA 沒有重寫 finalize()方法,或者 finalize()方法已經(jīng)被虛擬機調(diào)用過,則虛擬機視為“沒有必要執(zhí)行”,objA 被判定為不可觸及的。
  • 如果對象 objA 重寫了 finalize()方法,且還未執(zhí)行過,那么 objA 會被插入到隊列中,由一個虛擬機自動創(chuàng)建的、低優(yōu)先級的 Finalizer 線程觸發(fā)其finalize()方法執(zhí)行。
  • finalize()方法是對象逃脫死亡的最后機會,稍后 GC 會對隊列中的對象進行第二次標記。如果 objA 在 finalize()方法中與引用鏈上的任何一個對象建立了聯(lián)系,那么在第二次標記時,objA 會被移出“即將回收”集合。之后,對象會再次出現(xiàn)沒有引用存在的情況。在這個情況下,finalize()方法不會被再次調(diào)用,對象會直接變成不可觸及的狀態(tài)。
代碼演示 finalize() 方法可復活對象
第一次自救成功,但由于 finalize() 方法只會執(zhí)行一次,所以第二次自救失敗。

6.2.2 垃圾回收階段算法

? ? ? ? 當成功區(qū)分出內(nèi)存中存活對象和死亡對象后,GC 接下來的任務(wù)就是執(zhí)行垃圾回收,釋放掉無用對象所占用的內(nèi)存空間,以便有足夠的可用內(nèi)存空間為新對象分配內(nèi)存。目前在 JVM 中比較常見的三種垃圾收集算法是:

  • 標記-復制算法(Copying)
  • 標記-清除算法(Mark-Sweep)
  • 標記-壓縮算法(Mark-Compact)

6.2.2.1 標記-復制算法

? ? ? ? 它將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。在垃 圾回收時將正在使用的內(nèi)存中的存活對象復制到未被使用的內(nèi)存塊中,之后清除 正在使用的內(nèi)存塊中的所有對象,交換兩個內(nèi)存的角色,最后完成垃圾回收。

jvm,Java進階,jvm,java,開發(fā)語言

?

復制算法的優(yōu)缺點

優(yōu)點

  • 沒有標記和清除過程,實現(xiàn)簡單,運行高效
  • 復制過去以后保證空間的連續(xù)性,不會出現(xiàn)“碎片”問題。
缺點
  • 此算法的缺點也是很明顯的,就是需要兩倍的內(nèi)存空間。
  • 對于 G1 這種分拆成為大量 region 的 GC,復制而不是移動,意味著 GC 需要維護 region 之間對象引用關(guān)系,不管是內(nèi)存占用或者時間開銷也不小.

復制算法的應(yīng)用場景

  1. 如果系統(tǒng)中的垃圾對象很多,復制算法需要復制的存活對象數(shù)量并不會太大,效率較高
  1. 老年代大量的對象存活,那么復制的對象將會有很多,效率會很低
  2. 在新生代,對常規(guī)應(yīng)用的垃圾回收,一次通??梢曰厥?70% - 99% 的內(nèi)存空間。回收性價比很高。所以現(xiàn)在的商業(yè)虛擬機都是用這種收集算法回收新生代。

jvm,Java進階,jvm,java,開發(fā)語言

?

6.2.2.2 標記-清除算法


執(zhí)行過程
當堆中的有效內(nèi)存空間被耗盡的時候,然后進行這項工作.
? ? ? ?
清除:這里所謂的清除并不是真的置空,而是把需要清除的對象地址保存在空閑的地址列表里。下次有新對象需要加載時,判斷垃圾的位置空間是否夠,如果夠,就存放(也就是覆蓋原有的地址)

jvm,Java進階,jvm,java,開發(fā)語言

?

標記-清除算法的優(yōu)點:
? ? ? ?非?;A(chǔ)和常見的垃圾收集算法容易理解

標記-清除算法的缺點:
? ? ? ?標記清除算法的效率不算高
? ? ? ?這種方式清理出來的空閑內(nèi)存是不連續(xù)的,產(chǎn)生內(nèi)碎片。


?

6.2.2.3 標記-壓縮算法

背景

? ? ? ?復制算法的高效性是建立在存活對象少、垃圾對象多的前提下的。這種情況在新生代經(jīng)常發(fā)生,但是在老年代,更常見的情況是大部分對象都是存活對象。 如果依然使用復制算法,由于存活對象較多,復制的成本也將很高。因此,基于老年代垃圾回收的特性,需要使用其他的算法。

? ? ? ?標記-清除算法的確可以應(yīng)用在老年代中,但是該算法不僅執(zhí)行效率低下,而且在執(zhí)行完內(nèi)存回收后還會產(chǎn)生內(nèi)存碎片,所以 JVM 的設(shè)計者需要在此基礎(chǔ)之上進行改進
?

標記壓縮算法執(zhí)行過程

第一階段和標記清除算法一樣,從根節(jié)點開始標記所有被引用對象
第二階段將所有的存活對象壓縮到內(nèi)存的一端,按順序排放。之后,清理邊界外所有的空間。

jvm,Java進階,jvm,java,開發(fā)語言

標記-壓縮算法與標記-清除算法的比較

? ? ? ?標記-壓縮算法的最終效果等同于標記-清除算法執(zhí)行完成后,再進行一次內(nèi)存碎片整理,因此,也可以把它稱為標記-清除-壓縮(Mark-Sweep-Compact)算法。

? ? ? ?二者的本質(zhì)差異在于標記-清除算法是一種非移動式的回收算法(空閑列表記錄位置),標記-壓縮是移動式的。是否移動回收后的存活對象是一項優(yōu)缺點并存的風險決策。

? ? ? ?可以看到,標記的存活對象將會被整理,按照內(nèi)存地址依次排列,而未被標記的內(nèi)存會被清理掉。如此一來,當我們需要給新對象分配內(nèi)存時JVM 只需要持有一個內(nèi)存的起始地址即可,這比維護一個空閑列表顯然少了許多開銷。

?

標記-壓縮算法的優(yōu)缺點

優(yōu)點

  1. 消除了標記-清除算法當中,內(nèi)存區(qū)域分散的缺點,我們需要給新對象分配內(nèi)存時,JVM 只需要持有一個內(nèi)存的起始地址即可。
  2. 消除了復制算法當中,內(nèi)存減半的高額代價。
缺點
  1. 從效率上來說,標記-壓縮算法要低于復制算法。
  2. 移動對象的同時,如果對象被其他對象引用,則還需要調(diào)整引用的地址
  3. 移動過程中,需要全程暫停用戶應(yīng)用程序。即:STW

6.2.2.4 垃圾回收算法小結(jié)

? ? ? ? 效率上來說,復制算法是當之無愧的老大,但是卻浪費了太多內(nèi)存。而為了盡量兼顧上面提到的三個指標,標記-壓縮算法相對來說更平滑一些,但是效率上不盡如人意,它比復制算法多了一個標記的階段,比標記-清除多了一個整理內(nèi)存的階段。

標記清除 標記整理 復制
速率 中等 最慢 最快
空間開銷 少(會堆積碎片) 少(無堆積碎片) 通常需要活動對象的兩倍空間(無堆積碎片)
移動對象

6.2.2.5 分代收集

為什么要使用分代收集

? ? ? ?前面所有這些算法中,并沒有一種算法可以完全替代其他算法,它們都具有自己獨特的優(yōu)勢和特點。分代收集應(yīng)運而生。

? ? ? ?分代收集,是基于這樣一個事實:不同的對象的生命周期是不一樣的。因此,不同生命周期的對象可以采取不同的收集方式,以便提高回收效率。一般是把 Java堆分為新生代和老年代,這樣就可以根據(jù)各個年代的特點使用不同的回收算法,以提高垃圾回收的效率。

? ? ? ?在 Java 程序運行的過程中,會產(chǎn)生大量的對象,其中有些對象是與業(yè)務(wù)信息相關(guān): 比如 Http 請求中的 Session 對象、線程、Socket 連接,這類對象跟業(yè)務(wù)直接掛鉤,因此生命周期比較長。? ? ? ? ?但是還有一些對象,主要是程序運行過程中生成的臨時變量,這些對象生命周期會比較短,比如:String 對象,由于其不變類的特性,系統(tǒng)會產(chǎn)生大量的這些對象,有些對象甚至只用一次即可回收。

? ? ? ?目前幾乎所有的 GC 都采用分代手機算法執(zhí)行垃圾回收的在 HotSpot 中,基于分代的概念,GC 所使用的內(nèi)存回收算法必須結(jié)合年輕代和老年代各自的特點。

年輕代(Young Gen)

年輕代特點:區(qū)域相對老年代較小,對象生命周期短、存活率低,回收頻繁。

? ? ? ?這種情況復制算法的回收整理,速度是最快的。復制算法的效率只和當前存活對象大小有關(guān),因此很適用于年輕代的回收。而復制算法內(nèi)存利用率不高的問題,通過 hotspot 中的兩個 survivor 的設(shè)計得到緩解。

老年代(Tenured Gen)

老年代特點:區(qū)域較大,對象生命周期長、存活率高,回收不及年輕代頻繁。

? ? ? ?這種情況存在大量存活率高的對象,復制算法明顯變得不合適。一般是由標記- 清除或者是標記-清除與標記-壓縮的混合實現(xiàn)。

  1. Mark 階段的開銷與存活對象的數(shù)量成正比。
  2. Sweep 階段的開銷與所管理區(qū)域的大小成正相關(guān)。
  3. Compact 階段的開銷與存活對象的數(shù)據(jù)成正比。

分代的思想被現(xiàn)有的虛擬機廣泛使用。幾乎所有的垃圾回收器都區(qū)分新生代和老年代。

6.3 垃圾回收相關(guān)概念

6.3.1System.gc() 的理解

? ? ? ?在默認情況下,通過 System.gc()者 Runtime.getRuntime().gc() 的調(diào)用,會顯式觸發(fā) Full GC,同時對老年代和新生代進行回收,嘗試釋放被丟棄對象占用的內(nèi)存。
? ? ? ?然而 System.gc()調(diào)用附帶一個免責聲明,無法保證對垃圾收集器的調(diào)用(不能確保立即生效)。
? ? ? ?JVM 實現(xiàn)者可以通過 System.gc() 調(diào)用來決定 JVM 的 GC 行為。而一般情況下,垃圾回收應(yīng)該是自動進行的,無須手動觸發(fā),否則就太過于麻煩了。在一些特殊情況下,我們可以在運行之間調(diào)用 System.gc()。

6.3.2 內(nèi)存溢出與內(nèi)存泄漏

內(nèi)存溢出

? ? ? ?內(nèi)存溢出相對于內(nèi)存泄漏來說,盡管更容易被理解,但是同樣的,內(nèi)存溢出也是引發(fā)程序崩潰的罪魁禍首之一。
? ? ? ?由于 GC 一直在發(fā)展,所有一般情況下,除非應(yīng)用程序占用的內(nèi)存增長速度非???,造成垃圾回收已經(jīng)跟不上內(nèi)存消耗的速度,否則不太容易出現(xiàn) OOM 的情況。
? ? ? ?大多數(shù)情況下,GC 會進行各種年齡段的垃圾回收,實在不行了就放大招,來一次獨占式的 Full GC 操作,這時候會回收大量的內(nèi)存,供應(yīng)用程序繼續(xù)使用。
? ? ? ?Javadoc 中對 OutofMemoryError 的解釋是,沒有空閑內(nèi)存,并且垃圾收集器也無法提供更多內(nèi)存。

內(nèi)存泄漏

? ? ? ?內(nèi)存泄漏也稱作“存儲滲漏”。嚴格來說,只有對象不會再被程序用到了,但是 GC 又不能回收他們的情況,才叫內(nèi)存泄漏。
? ? ? ?但實際情況很多時候一些不太好的實踐(或疏忽)會導致對象的生命周期變得很長甚至導致 OOM,也可以叫做寬泛意義上的“內(nèi)存泄漏”。
? ? ? ?盡管內(nèi)存泄漏并不會立刻引起程序崩潰,但是一旦發(fā)生內(nèi)存泄漏,程序中的可用內(nèi)存就會被逐步蠶食,直至耗盡所有內(nèi)存,最終出現(xiàn) OutofMemory 異常,導致程序崩潰。注意,這里的存儲空間并不是指物理內(nèi)存,而是指虛擬內(nèi)存大小,這個虛擬內(nèi)存大小取決于磁盤交換區(qū)設(shè)定的大小。

? ? ? ?注意,這里的存儲空間并不是指物理內(nèi)存,而是指虛擬內(nèi)存大小,這個虛擬內(nèi)存大小取決于磁盤交換區(qū)設(shè)定的大小。

常見例子

單例模式

? ? ? ?單例的生命周期和應(yīng)用程序是一樣長的,所以在單例程序中,如果持有對外部對象的引用的話,那么這個外部對象是不能被回收的,則會導致內(nèi)存泄漏的產(chǎn)生。

一些提供 close()的資源未關(guān)閉導致內(nèi)存泄漏
? ? ? ?數(shù)據(jù)庫連接 dataSourse.getConnection(),網(wǎng)絡(luò)連接 socket 和 io 連接必須手動 close,否則是不能被回收的。

6.3.3Stop the World

? ? ? ?Stop-the-World,簡稱 STW,指的是 GC 事件發(fā)生過程中,會產(chǎn)生應(yīng)用程序的停頓。停頓產(chǎn)生時整個應(yīng)用程序線程都會被暫停,沒有任何響應(yīng),有點像卡死的感覺,這個停頓稱為 STW。

可達性分析算法中枚舉根節(jié)點(GC Roots)會導致所有 Java 執(zhí)行線程停頓,為什么需要停頓所有 Java 執(zhí)行線程呢?

  1. 分析工作必須在一個能確保一致性的快照中進行
  2. 一致性指整個分析期間整個執(zhí)行系統(tǒng)看起來像被凍結(jié)在某個時間點上
  3. 如果出現(xiàn)分析過程中對象引用關(guān)系還在不斷變化,則分析結(jié)果的準確性無法保證,會出現(xiàn)漏標,錯標問題
  4. 被 STW 中斷的應(yīng)用程序線程會在完成 GC 之后恢復,頻繁中斷會讓用戶感覺像是網(wǎng)速不快造成電影卡帶一樣,所以我們需要減少 STW 的發(fā)生。
  5. 越優(yōu)秀,回收效率越來越高,盡可能地縮短了暫停時間。

? ? ? ?STW 是 JVM 在后臺自動發(fā)起和自動完成的。在用戶不可見的情況下,把用戶正常的工作線程全部停掉。

6.4 垃圾回收器

6.4.1 垃圾回收器概述

? ? ? ?如果說垃圾收集算法是內(nèi)存回收的方法論,那么收集器就是內(nèi)存回收的實踐者.
? ? ? ?垃圾收集器沒有在 java 虛擬機規(guī)范中進行過多的規(guī)定,可以由不同的廠商、不同版本的 JVM 來實現(xiàn)。
? ? ? ?由于 JDK 的版本處于高速迭代過程中,因此 Java 發(fā)展至今已經(jīng)衍生了眾多的垃圾回收器。從不同角度分析垃圾收集器,可以將 GC 分為不同的類型。
? ? ? ?實際使用時,可以根據(jù)實際的使用場景選擇不同的垃圾回收器,這也是 JVM 調(diào)優(yōu)的重要部

6.4.2 垃圾回收器分類

按線程數(shù)可以分為單線程(串行)垃圾回收器和多線程(并行)垃圾回收器

單線程垃圾回收器(Serial)

只有一個線程進行垃圾回收,使用于小型簡單的使用場景,垃圾回收時,其他用戶線程會暫停.

jvm,Java進階,jvm,java,開發(fā)語言

?

多線程垃圾回收器(Parallel)
? ? ? ?多線程垃圾回收器內(nèi)部提供多個線程進行垃圾回收,在多 cpu 情況下大大提升垃
圾回收效率,但同樣也是會暫停其他用戶線程.

jvm,Java進階,jvm,java,開發(fā)語言

?按照工作模式分,可以分為獨占式并發(fā)式垃圾回收器。

jvm,Java進階,jvm,java,開發(fā)語言

按工作的內(nèi)存區(qū)間分,又可分為年輕代垃圾回收器老年代垃圾回收器。?

6.4.3GC 性能指標

吞吐量:運行用戶代碼的時間占總運行時間的比例
(總運行時間:程序的運行時間+ 內(nèi)存回收的時間)
  • 垃圾收集開銷:垃圾收集所用時間與總運行時間的比例。
暫停時間:執(zhí)行垃圾收集時,程序的工作線程被暫停的時間。
  • 內(nèi)存占用:Java 堆區(qū)所占的內(nèi)存大小。
  • 快速:一個對象從誕生到被回收所經(jīng)歷的時間。

jvm,Java進階,jvm,java,開發(fā)語言

?

6.4.4HotSpot 垃圾收集器

? ? ? ? 圖中展示了 7 種作用于不同分代的收集器,如果兩個收集器之間存在連線,則說明它們可以搭配使用。虛擬機所處的區(qū)域則表示它是屬于新生代還是老年代收集器。
jvm,Java進階,jvm,java,開發(fā)語言

?

6. 4.5CMS 回收器

CMS 概述

CMS(Concurrent Mark Sweep,并發(fā)標記清除)收集器是以獲取最短回收停頓時間為目標的收集器(追求低停頓),它在垃圾收集時使得用戶線程和 GC 線程并發(fā)執(zhí)行,因此在垃圾收集過程中用戶也不會感到明顯的卡頓。

垃圾回收過程

初始標記:
? ? ? ?Stop The World,僅使用一條初始標記線程對所有與 GC Roots 直接關(guān)聯(lián)的對象進行標記。

并發(fā)標記:
? ? ? ?垃圾回收線程,與用戶線程并發(fā)執(zhí)行。此過程進行可達性分析,標記出所有廢棄對象。

重新標記:
? ? ? ?Stop The World,使用多條標記線程并發(fā)執(zhí)行,將剛才并發(fā)標記過程中新出現(xiàn)的廢棄對象標記出來。

并發(fā)清除:
? ? ? ?只使用一條 GC 線程,與用戶線程并發(fā)執(zhí)行,清除剛才標記的對象。 這個過程非常耗時。

? ? ? ?并發(fā)標記與并發(fā)清除過程耗時最長,且可以與用戶線程一起工作,因此,總體上 說,CMS 收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)執(zhí)行的。

jvm,Java進階,jvm,java,開發(fā)語言

CMS 的優(yōu)點:
? ? ? ??可以作到并發(fā)收集

CMS 的弊端:

  1. CMS 是基于標記-清除算法來實現(xiàn)的,會產(chǎn)生內(nèi)存碎片。
  2. CMS 在并發(fā)階段,它雖然不會導致用戶線程停頓,但是會因為占用了一部分線程而導致應(yīng)用程序變慢,總吞吐量會降低。
  3. CMS 收集器無法處理浮動垃圾(floating garbage)。

?三色標記算法

? ? ? ?為了提高 JVM 垃圾回收的性能,從 CMS 垃圾收集器開始,引入了并發(fā)標記的概念。引入并發(fā)標記的過程就會帶來一個問題,在業(yè)務(wù)執(zhí)行的過程中,會對現(xiàn)有的引用關(guān)系鏈出現(xiàn)改變。

三色標記法將對象的顏色分為了黑、灰、白,三種顏色。
  • 黑色:該對象已經(jīng)被標記過了,且該對象下的屬性也全部都被標記過了,例如GCRoots 對象。
  • 灰色:對象已經(jīng)被垃圾收集器掃描過了,但是對象中還存在沒有掃描的引用(GC需要從此對象中去尋找垃圾);
  • 白色:表示對象沒有被垃圾收集器訪問過,即表示不可達.

三色標記的過程:

為了解決并發(fā)的問題,引入中間狀態(tài)(灰色),當一個對象被標記的時候,會有下面幾個過程:

  1. 剛開始,確定為 GC Roots 的對象為黑色。
  2. 將 GC Roots 直接關(guān)聯(lián)的對象置為灰色。
  3. 遍歷灰色對象的所有引用,灰色對象本身置為黑色,其引用置為灰色。
  4. 重復步驟 3,直到?jīng)]有灰色對象為止。
  5. ?結(jié)束時,黑色對象存活,白色對象回收。

? ? ? ?這個過程正確執(zhí)行的前提是沒有其他線程改變對象間的引用關(guān)系,然而,并發(fā)標記的過程中,用戶線程仍在運行,因此就會產(chǎn)生漏標和錯標的情況。

漏標

假設(shè) GC 已經(jīng)在遍歷對象 B 了,而此時用戶線程執(zhí)行了 A.B=null 的操作,切斷了 A 到 B 的引用

jvm,Java進階,jvm,java,開發(fā)語言

本來執(zhí)行了 A.B=null 之后,B、D、E 都可以被回收了,但是由于 B 已經(jīng)變?yōu)榛疑?,它仍會被當做存活對象,繼續(xù)遍歷下去。最終的結(jié)果就是本輪 GC 不會回收 B、D、E,留到下次 GC 時回收,也算是浮動垃圾的一部分。
錯標
假設(shè) GC 線程已經(jīng)遍歷到 B 了,此時用戶線程執(zhí)行了以下操作:
? ? ? ?B.D=null;//B 到 D 的引用被切斷
? ? ? ?A.xx=D;//A 到 D 的引用被建立

jvm,Java進階,jvm,java,開發(fā)語言

?

? ? ? ?B 到 D 的引用被切斷,且 A 到 D 的引用被建立。
? ? ? ?此時 GC 線程繼續(xù)工作,由于 B 不再引用 D 了,盡管 A 又引用了 D,但是因為 A 已經(jīng)標記為黑色,GC 不會再遍歷 A 了,所以 D 會被標記為白色,最后被當做垃圾回收。
? ? ? ?可以看到錯標的結(jié)果比漏表嚴重的多,浮動垃圾可以下次 GC 清理,而把不該回收的對象回收掉,將會造成程序運行錯誤。
解決錯標的問題
錯標只有在滿足下面兩種情況下才會發(fā)生:
jvm,Java進階,jvm,java,開發(fā)語言

?只要打破任一條件,就可以解決錯標的問題。

原始快照和增量更新

原始快照打破的是第一個條件:當灰色對象指向白色對象的引用被斷開時,就將這條引用關(guān)系記錄下來。當掃描結(jié)束后,再以這些灰色對象為根,重新掃描一次。

增量更新打破的是第二個條件:當黑色指向白色的引用被建立時,就將這個新的引用關(guān)系記錄下來,等掃描結(jié)束后,再以這些記錄中的黑色對象為根,重新掃描一次。相當于黑色對象一旦建立了指向白色對象的引用,就會變?yōu)榛疑珜ο蟆?

總結(jié)

? ? ? ?CMS 為了讓 GC 線程和用戶線程一起工作,回收的算法和過程比以前舊的收集器要復雜很多。究其原因,就是因為 GC 標記對象的同時,用戶線程還在修改對象的引用關(guān)系。因此 CMS 引入了三色算法,將對象標記為黑、灰、白三種顏色的對象,將用戶線程修改的引用關(guān)系記錄下來,以便在「重新標記」階段可以修正對象的引用。

? ? ? ?雖然 CMS 從來沒有被 JDK 當做默認的垃圾收集器,存在很多的缺點,但是它開啟了「GC 并發(fā)收集」的先河,為后面的收集器提供了思路。

6.4.6G1(Garbage First)回收器

既然我們已經(jīng)有了前面幾個強大的 GC,為什么還要發(fā)布 Garbage First(G1)GC?

? ? ? ? ?原因就在于應(yīng)用程序所應(yīng)對的業(yè)務(wù)越來越龐大、復雜,用戶越來越多,沒有GC 就不能保證應(yīng)用程序正常進行,而經(jīng)常造成 STW 的 GC 又跟不上實際的需求,所以才會不斷地嘗試對 GC 進行優(yōu)化。G1(Garbage-First)垃圾回收器是在 Java7 update 4 之后引入的一個新的垃圾回收器,是當今收集器技術(shù)發(fā)展的最前沿成果之一.

? ? ? ?與此同時,為了適應(yīng)現(xiàn)在不斷擴大的內(nèi)存和不斷增加的處理器數(shù)量,進一步降低暫停時間(pause time),同時兼顧良好的吞吐量。

? ? ? ?官方給 G1 設(shè)定的目標是在延遲可控的情況下獲得盡可能高的吞吐量,所以才擔當起“全功能收集器”的重任與期望。

G1 是一款面向服務(wù)端應(yīng)用的垃圾收集器。

為什么名字叫做 Garbage First(G1)呢?

jvm,Java進階,jvm,java,開發(fā)語言

? ? ? ?因為 G1 是一個并行回收器,它把堆內(nèi)存分割為很多不相關(guān)的區(qū)域(Region)(物理上不連續(xù)的邏輯上連續(xù)的)。使用不同的 Region 來表示 Eden、幸存者0 區(qū),幸存者 1 區(qū),老年代等。

? ? ? ?G1 GC 有計劃地避免在整個 Java 堆中進行全區(qū)域的垃圾收集。G1 跟蹤各個 Region 里面的垃圾堆積的價值大?。ɑ厥账@得的空間大小以及回收所需時間的經(jīng)驗值),在后臺維護一個優(yōu)先列表,每次根據(jù)允許的收集時間,優(yōu)先回收價值最大的 Region.

? ? ? ?由于這種方式的側(cè)重點在于回收垃圾最大量的區(qū)間(Region),所以我們給 G1 一個名字:垃圾優(yōu)先(Garbage First)。

? ? ? ?G1(Garbage-First)是一款面向服務(wù)端應(yīng)用的垃圾收集器,主要針對配備多核 CPU 及大容量內(nèi)存的機器,以極高概率滿足 GC 停頓時間的同時,還兼具高吞吐量的性能特征。

? ? ? ? 如下圖所示,G1 收集器收集器收集過程有初始標記、并發(fā)標記、最終標記、篩選回收,和 CMS 收集器前幾步的收集過程很相似:

jvm,Java進階,jvm,java,開發(fā)語言

  1. 初始標記:標記出 GC Roots 直接關(guān)聯(lián)的對象,這個階段速度較快,需要停止用戶線程,單線程執(zhí)行。
  2. 并發(fā)標記:從 GC Root 開始對堆中的對象進行可達新分析,找出存活對象,這個階段耗時較長,但可以和用戶線程并發(fā)執(zhí)行。
  3. 最終標記:修正在并發(fā)標記階段引用戶程序執(zhí)行而產(chǎn)生變動的標記記錄。
  4. 篩選回收:篩選回收階段會對各個 Region 的回收價值和成本進行排序,根據(jù)用戶所期望的 GC 停頓時間來指定回收計劃(用最少的時間來回收包含垃圾最多的區(qū)域.這就是 Garbage First 的由來——第一時間清理垃圾最多的區(qū)塊),這里為了提高回收效率,并沒有采用和用戶線程并發(fā)執(zhí)行的方式,而是停頓用戶線程。
適用場景:要求盡可能可控 GC 停頓時間;內(nèi)存占用較大的應(yīng)用。

6.4.7 查看 JVM 垃圾回收器設(shè)置垃圾回收器

打印默認垃圾回收器

-XX:+PrintCommandLineFlags -version

JDK 8 默認的垃圾回收器
年輕代使用 Parallel Scavenge GC
老年代使用 Parallel Old GC

打印垃圾回收詳細信息

-XX:+PrintGCDetails -version

設(shè)置默認垃圾回收器

Serial 回收器

-XX:+UseSerialGC 年輕代使用 Serial GC, 老年代使用 Serial Old GC

ParNew 回收器

-XX:+UseParNewGC 年輕代使用 ParNew GC,不影響老年代。

CMS 回收器

-XX:+UseConcMarkSweepGC 老年代使用 CMS GC。

# G1 回收器 文章來源地址http://www.zghlxwxcb.cn/news/detail-770039.html

-XX:+UseG1GC 手動指定使用 G1 收集器執(zhí)行內(nèi)存回收任務(wù)。
-XX:G1HeapRegionSize 設(shè)置每個 Region 的大小。

到了這里,關(guān)于JVM(Java虛擬機)-史上最全、最詳細JVM筆記的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔相關(guān)法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • JVM——Java虛擬機詳解

    JVM——Java虛擬機詳解

    JVM——Java虛擬機,它是Java實現(xiàn)平臺無關(guān)性的基石。 Java程序運行的時候,編譯器將Java文件編譯成平臺無關(guān)的Java字節(jié)碼文件(.class),接下來對應(yīng)平臺JVM對字節(jié)碼文件進行解釋,翻譯成對應(yīng)平臺匹配的機器指令并運行。 同時JVM也是一個跨語言的平臺,和語言無關(guān),只和class的文

    2024年01月19日
    瀏覽(36)
  • Java虛擬機(JVM)框架

    見:GitHub - eHackyd/Java_JVM: Java虛擬機(JVM)框架的學習筆記

    2024年02月10日
    瀏覽(21)
  • Jvm --java虛擬機(上)

    Jvm --java虛擬機(上)

    為什么學習jvm 如果你這輩子只甘心做一個平庸的Java碼農(nóng),那么你可以利用閱讀本文的時間去學習其他新的技術(shù)知識,但是如果你想成為一個更更更更優(yōu)秀的中高級程序員!那么請繼續(xù)閱讀本文,希望這篇文章會對你有所幫助,那么學習jvm有啥好處嘞? 首先: 你能夠明白為什

    2024年02月03日
    瀏覽(30)
  • JVM(Java虛擬機)概述

    ? ? ?JVM(Java Virtual Machine)是一個能夠運行Java字節(jié)碼的虛擬計算機。它是Java平臺的核心組成部分,負責執(zhí)行編譯后的Java程序,提供跨平臺運行的能力。JVM使得Java程序可以在任何安裝了JVM的操作系統(tǒng)上運行,無需對代碼進行修改,實現(xiàn)了\\\"一次編寫,到處運行\(zhòng)\\"(Write Once, Ru

    2024年03月11日
    瀏覽(25)
  • Java虛擬機快速入門 | JVM引言、JVM內(nèi)存結(jié)構(gòu)、直接內(nèi)存

    Java虛擬機快速入門 | JVM引言、JVM內(nèi)存結(jié)構(gòu)、直接內(nèi)存

    目錄 一:JVM引言 1. 什么是 JVM ? 2. 常見的 JVM 3. 學習路線 二:JVM內(nèi)存結(jié)構(gòu) 1. 程 序 計 數(shù) 器(PC Register) 2. 虛 擬 機 棧(JVM Stacks) 3. 本 地 方 法 棧(Native Method Stacks) 4. 堆(Heap) 5. 方 法 區(qū)(Method Area) 三:直接內(nèi)存 tips: 首先給大家推薦兩款好用的免費軟件:動圖抓取軟

    2024年02月05日
    瀏覽(20)
  • 什么是Java中的JVM(Java虛擬機)?

    什么是Java中的JVM(Java虛擬機)?

    JVM(Java虛擬機)是Java平臺的核心組件之一,是一個用于執(zhí)行Java字節(jié)碼的虛擬計算機。Java源代碼經(jīng)過編譯器編譯,生成字節(jié)碼文件(.class文件),然后由JVM來解釋和執(zhí)行這些字節(jié)碼。JVM負責將字節(jié)碼翻譯成特定操作系統(tǒng)和硬件平臺的機器碼,從而實現(xiàn)跨平臺的能力。 ? JVM具

    2024年02月15日
    瀏覽(22)
  • Java虛擬機(JVM):堆溢出

    Java虛擬機(JVM):堆溢出

    Java堆溢出(Java Heap Overflow)是指在Java程序中,當創(chuàng)建對象時,無法分配足夠的內(nèi)存空間來存儲對象,導致堆內(nèi)存溢出的情況。 Java堆是Java虛擬機中用于存儲對象的一塊內(nèi)存區(qū)域。當程序創(chuàng)建對象時,會在堆中分配一塊連續(xù)的內(nèi)存空間來存儲對象的實例變量。如果堆中的剩余

    2024年02月12日
    瀏覽(26)
  • Java虛擬機(JVM):虛擬機棧溢出

    Java虛擬機(JVM):虛擬機棧溢出

    Java虛擬機棧溢出(Java Virtual Machine Stack Overflow)是指在Java程序中,當線程調(diào)用的方法層級過深,導致棧空間溢出的情況。 Java虛擬機棧是每個線程私有的,用于存儲方法的調(diào)用和局部變量的內(nèi)存空間。每當一個方法被調(diào)用時,會在棧中創(chuàng)建一個棧幀,用于存儲方法的參數(shù)、局

    2024年02月12日
    瀏覽(21)
  • JVM 虛擬機 ----> Java 類加載機制

    JVM 虛擬機 ----> Java 類加載機制

    一、概述 類是在運行期間第一次使用時,被類加載器動態(tài)加載至 JVM 。JVM不會一次性加載所有類。因為如果一次性加載,那么會占用很多的內(nèi)存 二、類的生命周期 類的生命周期包含以下 七 個階段: 加載(Loading) 驗證(Verification) 準備(Preparation) 解析(Resolution) 初始化

    2024年02月07日
    瀏覽(23)
  • Java虛擬機(JVM):引用計數(shù)算法

    Java虛擬機(JVM):引用計數(shù)算法

    我們學習了Java內(nèi)存運行時區(qū)域的各個部分,其中程序計數(shù)器、虛擬機棧、本地方法棧3個區(qū)域隨線程而生,隨線程而滅。棧中的棧幀隨著方法的進入和退出而有條不紊地執(zhí)行著出棧和入棧操作。每一個棧幀中分配多少內(nèi)存基本上是在類結(jié)構(gòu)確定下來就已知的,因此這幾個區(qū)域

    2024年02月12日
    瀏覽(21)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包