JDK (Java Development Kit) : java語言的軟件開發(fā)包。包括Java運(yùn)行時(shí)環(huán)境JRE。
JRE (Java Runtime Environment) :Java運(yùn)行時(shí)環(huán)境,包括JVM。
JVM (Java Virtual Machine) :一種用于計(jì)算機(jī)設(shè)備的規(guī)范。
Java語言在不同平臺(tái)上運(yùn)行時(shí)不需要重新編譯。Java語言使用Java虛擬機(jī)屏蔽了與具體平臺(tái)相關(guān)的信息,使得Java語言編譯程序只需生成在Java虛擬機(jī)上運(yùn)行的目標(biāo)代碼(字節(jié)碼),就可以在多種平臺(tái)上不加修改地運(yùn)行。
實(shí)現(xiàn)通過類的權(quán)限定名獲取該類的二進(jìn)制字節(jié)流的代碼塊叫做類加載器。
啟動(dòng)類加載器(Bootstrap ClassLoader)用來加載 java 核心類庫(kù),無法被 java 程序直接引用。
擴(kuò)展類加載器(extensions class loader):它用來加載 Java 的擴(kuò)展庫(kù)。Java虛擬機(jī)的實(shí)現(xiàn)會(huì)提供一個(gè)擴(kuò)展庫(kù)目錄。該類加載器在此目錄里面查找并加載 Java 類。
系統(tǒng)類加載器(system class loader):它根據(jù) Java 應(yīng)用的類路徑(CLASSPATH) 來加載 Java類。一般來說,Java應(yīng)用的類都是由它來完成加載的??梢酝ㄟ^ClassLoader.getSystemClassLoader()來獲取它。
用戶自定義類加載器,通過繼承 java.lang.ClassLoader 類的方式實(shí)現(xiàn)。
ClassLoader 顧名思義就是類加載器。Java源代碼首先被jvm編譯成.class文件。類從被加載到虛擬機(jī)內(nèi)存中開始,直到卸載出內(nèi)存為止,它的整個(gè)生命周期包括了:加載、驗(yàn)證、準(zhǔn)備、解析、初始化、使用和卸載這7個(gè)階段。其中,驗(yàn)證、準(zhǔn)備和解析這三個(gè)部分統(tǒng)稱為連接(linking)。
加載:通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流,將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),在內(nèi)存中生成一個(gè)代表這個(gè)類的Class對(duì)象,作為方法去這個(gè)類的各種數(shù)據(jù)的訪問入口;
驗(yàn)證:驗(yàn)證是連接階段的第一步,這一階段的目的是確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬自身的安全;
準(zhǔn)備:準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些變量所使用的內(nèi)存都將在方法去中進(jìn)行分配。這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量(static),而不包括實(shí)例變量,實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在Java堆中。
解析:解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)(Class文件內(nèi)的符號(hào))引用替換為直接引用(指針)的過程。
初始化:初始化階段是類加載過程的最后一步,開始執(zhí)行類中定義的Java程序代碼(字節(jié)碼)。
父類靜態(tài)域——》子類靜態(tài)域——》父類成員初始化——》父類構(gòu)造塊——》父類構(gòu)造方法——》子類成員初始化——》子類構(gòu)造塊——》子類構(gòu)造方法;
類加載檢查:虛擬機(jī)遇到一條 new 指令時(shí),首先檢查這個(gè)指令的參數(shù)是否能在常量池中定位到這個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類是否已被加載過、解析和初始化過。如果沒有,那必須先執(zhí)行相應(yīng)的類加載過程。
分配內(nèi)存:在類加載檢查通過后,接下來虛擬機(jī)將為新生對(duì)象分配內(nèi)存。對(duì)象所需的內(nèi)存大小在類加載完成后便可確定,為對(duì)象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從 Java 堆中劃分出來。分配方式有 “指針碰撞” 和 “空閑列表” 兩種,選擇那種分配方式由 Java 堆是否規(guī)整決定,而 Java 堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。
初始化零值:內(nèi)存分配完成后,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值(不包括對(duì)象頭),這一步操作保證了對(duì)象的實(shí)例字段在 Java 代碼中可以不賦初始值就直接使用,程序能訪問到這些字段的數(shù)據(jù)類型所對(duì)應(yīng)的零值。
設(shè)置對(duì)象頭:初始化零值完成之后,虛擬機(jī)要對(duì)對(duì)象進(jìn)行必要的設(shè)置,例如這個(gè)對(duì)象是哪個(gè)類的實(shí)例、如何才能找到類的元數(shù)據(jù)信息、對(duì)象的哈希碼、對(duì)象的 GC 分代年齡等信息。 這些信息存放在對(duì)象頭中。 另外,根據(jù)虛擬機(jī)當(dāng)前運(yùn)行狀態(tài)的不同,如是否啟用偏向鎖等,對(duì)象頭會(huì)有不同的設(shè)置方式。
執(zhí)行init方法:在上面工作都完成之后,從虛擬機(jī)的視角來看,一個(gè)新的對(duì)象已經(jīng)產(chǎn)生了,但從 Java 程序的視角來看,對(duì)象創(chuàng)建才剛開始,<init> 方法還沒有執(zhí)行,所有的字段都還為零。所以一般來說,執(zhí)行 new 指令之后會(huì)接著執(zhí)行 <init> 方法,把對(duì)象按照程序員的意愿進(jìn)行初始化,這樣一個(gè)真正可用的對(duì)象才算完全產(chǎn)生出來。
1.如果一個(gè)類加載器收到了類加載請(qǐng)求,它并不會(huì)自己先加載,而是把這個(gè)請(qǐng)求委托給父類的加載器去執(zhí)行;
2.如果父類加載器還存在其父類加載器,則進(jìn)一步向上委托,依次遞歸,請(qǐng)求最終將到達(dá)頂層的引導(dǎo)類加載器;
3.如果父類加載器可以完成類加載任務(wù),就成功返回,倘若父類加載器無法完成加載任務(wù),子加載器才會(huì)嘗試自己去加載,這就是雙親委派機(jī)制;
避免類的重復(fù)加載,保護(hù)程序安全,防止核心API被隨意篡改。
JNDI(Java Naming and Directory Interface,Java命名和目錄接口)便是最典型的例子。JND需要調(diào)用由獨(dú)立廠商實(shí)現(xiàn)并部署在應(yīng)用程序的ClassPath下的JNDI接口提供者(SPI,Service Provider Interface)的代碼,但啟動(dòng)類加載器不可能“認(rèn)識(shí)”這些代碼那該怎么辦?
為了解決這個(gè)問題,Java設(shè)計(jì)團(tuán)隊(duì)只好引入了一個(gè)不太優(yōu)雅的設(shè)計(jì):線程上下文類加載器(Thread Context ClassLoader)。這個(gè)類加載器可以通過java.lang.Thread類的 setContextClassLoaser()方法進(jìn)行設(shè)置。有了線程上下文類加載器,JNDI服務(wù)使用這個(gè)線程上下文類加載器去加載所需要的SPI代碼,也就是父類加載器請(qǐng)求子類加載器去完成類加載的動(dòng)作,這種行為實(shí)際上就是打通了雙親委派模型的層次結(jié)構(gòu)來逆向使用類加載器,實(shí)際上已經(jīng)違背了雙親委派模型的一般性原則。Java中所有涉及SPI的加載動(dòng)作基本上都采用這種方式,例如JNDI、JDBC、JCE、JAXB和JBI等。
例如JDBC在rt里面定義了這個(gè)SPI,那MySQL有MySQL的JDBC實(shí)現(xiàn),Oracle有Oracle的JDBC實(shí)現(xiàn),反正我java不管你內(nèi)部如何實(shí)現(xiàn)的,反正你們都得統(tǒng)一按我這個(gè)來,這樣我們java開發(fā)者才能容易的調(diào)用數(shù)據(jù)庫(kù)操作。所以因?yàn)檫@樣那就不得不違反這個(gè)約束啊,Bootstrap ClassLoader就得委托子類來加載數(shù)據(jù)庫(kù)廠商們提供的具體實(shí)現(xiàn)。因?yàn)樗氖种荒苊?lt;JAVA_HOME>\lib中,其他的它無能為力,這就違反了自下而上的委托機(jī)制了。
不可以。因?yàn)樵陬惣虞d中,會(huì)根據(jù)雙親委派機(jī)制去尋找當(dāng)前java.lang.String是否已被加載。由于啟動(dòng)類加載器已在啟動(dòng)時(shí)候加載了所以不會(huì)再次加載,因此使用的String是已在java核心類庫(kù)加載過的String,而不是新定義的String。
Java源程序.java通過編譯器編譯成字節(jié)碼.class文件,也就是計(jì)算機(jī)可以識(shí)別的二進(jìn)制文件。
根據(jù) Java 虛擬機(jī)規(guī)范的規(guī)定,class 文件格式采用一種類似于 C 語言的偽結(jié)構(gòu)來存儲(chǔ)數(shù)據(jù),這種偽結(jié)構(gòu)中只有兩種數(shù)據(jù)類型:無符號(hào)數(shù)和表。
無符號(hào)數(shù)屬于基礎(chǔ)數(shù)據(jù)類型,以 u1、u2、u4、u8 來分別代表 1 個(gè)字節(jié)、2 個(gè)字節(jié)、4 個(gè)字節(jié)和 8 個(gè)字節(jié)的無符號(hào)數(shù),無符號(hào)數(shù)可以用來描述數(shù)字、索引引用、數(shù)量值或者按照 UTF-8 編碼構(gòu)成的字符串值。
表是由多個(gè)無符號(hào)數(shù)或者其他表作為數(shù)據(jù)項(xiàng)構(gòu)成的復(fù)合數(shù)據(jù)結(jié)構(gòu),所有表都習(xí)慣性地以 _info 結(jié)尾。表用于描述有層次關(guān)系的復(fù)合結(jié)構(gòu)的數(shù)據(jù),整個(gè) class 文件本質(zhì)上就是一張表。
Java語言提供了對(duì)象終止(finalization)機(jī)制來允許開發(fā)人員提供對(duì)象被銷毀之前的自定義處理邏輯。當(dāng)垃圾回收器發(fā)現(xiàn)沒有引用指向一個(gè)對(duì)象,即:垃圾回收此對(duì)象之前,總會(huì)先調(diào)用這個(gè)對(duì)象的finalize()方法,finalize()只會(huì)被調(diào)用一次。finalize() 方法允許在子類中被重寫,用于在對(duì)象被回收時(shí)進(jìn)行資源釋放。通常在這個(gè)方法中進(jìn)行一些資源釋放和清理的工作,比如關(guān)閉文件、套接字和數(shù)據(jù)庫(kù)連接等。
在JVM中表示兩個(gè)class對(duì)象是否為同一個(gè)類存在兩個(gè)必要條件:
1)類的完整類名必須一致,包括包名。
2)加載這個(gè)類的ClassLoader(指ClassLoader實(shí)例對(duì)象)必須相同。
主動(dòng)使用,分為七種情況:
1)創(chuàng)建類的實(shí)例
2)訪問某個(gè)類或接口的靜態(tài)變量,或者對(duì)該靜態(tài)變量賦值
3)調(diào)用類的靜態(tài)方法I
4)反射(比如:Class.forName(“com.atguigu.Test”))
5)初始化一個(gè)類的子類
6)Java虛擬機(jī)啟動(dòng)時(shí)被標(biāo)明為啟動(dòng)類的類
7)JDK7開始提供的動(dòng)態(tài)語言支持:java.lang.invoke.MethodHandle實(shí)例的解析結(jié)果REF getStatic、REF putStatic、REF invokeStatic句柄對(duì)應(yīng)的類沒有初始化,則初始化
除了以上七種情況,其他使用Java類的方式都被看作是對(duì)類的被動(dòng)使用,都不會(huì)導(dǎo)致類的初始化。
淺拷貝:只是增加了一個(gè)指針指向已存在的內(nèi)存地址,
深拷貝:是增加了一個(gè)指針并且申請(qǐng)了一個(gè)新的內(nèi)存,使這個(gè)增加的指針指向這個(gè)新的內(nèi)存,使用深拷貝的情況下,釋放內(nèi)存的時(shí)候不會(huì)因?yàn)槌霈F(xiàn)淺拷貝時(shí)釋放同一個(gè)內(nèi)存的錯(cuò)誤。
淺復(fù)制:僅僅是指向被復(fù)制的內(nèi)存地址,如果原地址發(fā)生改變,那么淺復(fù)制出來的對(duì)象也會(huì)相應(yīng)的改變。
深復(fù)制:在計(jì)算機(jī)中開辟一塊新的內(nèi)存地址用于存放復(fù)制的對(duì)象。
java -XX:+TraceClassLoading 具體類
Java -verbose 具體類
JDK1.7相對(duì)于JDK1.6,主要的變化就是將永久代中的字符串常量池移到堆內(nèi)存中,交由堆管理。我們知道堆是JVM內(nèi)存管理的主要區(qū)域,那么將字符串常量池放到堆內(nèi)存中更方便高效的對(duì)字符串常量進(jìn)行管理和垃圾回收。
而JDK1.8相對(duì)于JDK1.7來說,主要區(qū)別有兩點(diǎn),一是將虛擬機(jī)棧和本地方法棧合二為一了,二是移除永久代,增加了元數(shù)據(jù)區(qū),元數(shù)據(jù)區(qū)使用本地內(nèi)存,只受計(jì)算機(jī)內(nèi)存大小的限制。而永久代使用的還是堆內(nèi)存空間,受堆內(nèi)存大小的限制。
記錄正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址。為了線程切換后能恢復(fù)到正確的位置,每個(gè)線程都需要一個(gè)獨(dú)立的程序計(jì)數(shù)器,各個(gè)線程之間互不影響,獨(dú)立存儲(chǔ),這也就是所謂的“線程私有區(qū)域”。
描述方法執(zhí)行的內(nèi)存模型,每個(gè)方法在執(zhí)行時(shí)都會(huì)創(chuàng)建一個(gè)棧幀,每個(gè)棧幀存放的是局部變量表,操作數(shù)棧,動(dòng)態(tài)鏈接,方法出口等信息,方法被調(diào)用到執(zhí)行完成對(duì)應(yīng)的是一個(gè)棧幀從入棧到出棧的過程。是線程私有的。
為虛擬機(jī)使用到的Native方法服務(wù)。注在HotSpot虛擬機(jī)中直接就把本地方法棧和虛擬機(jī)棧合二為一了。
存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。在jdk1.8中已經(jīng)去除了永久代,改用只受計(jì)算機(jī)本地內(nèi)存大小限制的元空間來實(shí)現(xiàn)方法區(qū),元空間參數(shù)(-XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=1024M)。
運(yùn)行時(shí)常量池是方法區(qū)的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池,用于存放編譯器生成的各種字面量和符號(hào)引用,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放。
1. 由于PermGen內(nèi)存經(jīng)常會(huì)溢出,引發(fā)惱人的 java.lang.OutOfMemoryError: PermGen,因此 JVM 的開發(fā)者希望這一塊內(nèi)存可以更靈活地被管理,不要再經(jīng)常出現(xiàn)這樣的 OOM。
2. 移除 PermGen 可以促進(jìn) HotSpot JVM 與 JRockit VM 的融合,因?yàn)?JRockit 沒有永久代。
i++:剛開始學(xué)java時(shí)候,i++先使用i,然后再自加1,為什么是這樣呢?
底層步驟:
1.從局部變量表取出 i 并壓入操作數(shù)棧。(入棧)
2.然后對(duì)局部變量表中的i自增1,將操作棧棧頂值取出使用。(自加1,出棧)
3.最后使用操作數(shù)棧的棧頂值更新局部變量表,如此線程從操作棧讀到的是自增之前的值。(更新)
++i:剛開始學(xué)java時(shí)候,++i先自加1然后再使用i,為什么是這樣呢?
----------------------------------------------------------
底層步驟:
1.先對(duì)局部變量表的 i 自增 1。(自加1)
2.然后取出并壓入操作數(shù)棧。(入棧)
3.再將操作棧棧頂值取出使用。(出棧)
4.最后使用棧頂值更新局部變量表,線程從操作棧讀到的是自增之后的值。(更新)
--------------------------------------------
之前之所以說 i++ 不是原子操作,即使使用 volatile 修飾也不是線程安全,就是因?yàn)榭赡?i 被從局部變量表取出,壓入操作數(shù)棧,操作數(shù)棧中自增,使用棧頂值更新局部變量表(寄存器更新寫入內(nèi)存),其中分為 3 步,volatile 保證可見性,保證每次從局部變量表讀取的都是最新的值,但可能這 3 步可能被另一個(gè)線程的 3 步打斷,產(chǎn)生數(shù)據(jù)互相覆蓋問題,從而導(dǎo)致 i 的值比預(yù)期的小。
如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,則拋出StackOverflowError。
堆內(nèi)存存儲(chǔ)對(duì)象實(shí)例。我們只要不斷地創(chuàng)建對(duì)象。并保證gc roots到對(duì)象之間有可達(dá)路徑來避免垃圾回收機(jī)制清除這些對(duì)象。就會(huì)在對(duì)象數(shù)量到達(dá)最大。堆容量限制后,產(chǎn)生內(nèi)存溢出異常。
public class Cat {
public static void main(String[] args) {
List list = new ArrayList();
while (true) {
list.add(new Cat());
}
}
}
1.棧是線程私有的,棧的生命周期和線程一樣,每個(gè)方法在執(zhí)行的時(shí)候就會(huì)創(chuàng)建一個(gè)棧幀,它包含局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息,局部變量表又包括基本數(shù)據(jù)類型和對(duì)象的引用;
2.當(dāng)線程請(qǐng)求的棧深度超過了虛擬機(jī)允許的最大深度時(shí),會(huì)拋出StackOverFlowError異常,方法遞歸調(diào)用肯可能會(huì)出現(xiàn)該問題;
3.調(diào)整參數(shù)-xss去調(diào)整jvm棧的大小;
在Java虛擬機(jī)中,對(duì)象是在Java堆中分配內(nèi)存的,這是一個(gè)普遍的常識(shí)。但是,有一種特殊情況,那就是如果經(jīng)過逃逸分析(Escape Analysis)后發(fā)現(xiàn),一個(gè)對(duì)象并沒有逃逸出方法的話,那么就可能被優(yōu)化成棧上分配。這樣就無需在堆上分配內(nèi)存,也無須進(jìn)行垃圾回收了。這也是最常見的堆外存儲(chǔ)技術(shù)。
1)當(dāng)一個(gè)對(duì)象在方法中被定義后,對(duì)象只在方法內(nèi)部使用,則認(rèn)為沒有發(fā)生逃逸。
2)當(dāng)一個(gè)對(duì)象在方法中被定義后,它被外部方法所引用,則認(rèn)為發(fā)生逃逸。例如作為調(diào)用參數(shù)傳遞到其他地方中。
運(yùn)行時(shí)數(shù)據(jù)區(qū) | 是否存在 | 是否存在 |
---|---|---|
程序計(jì)數(shù)器 | 否 | 否 |
虛擬機(jī)棧 | 是 | 否 |
本地方法棧 | 是 | 否 |
方法區(qū) | 是(OOM) | 是 |
堆 | 是 | 是 |
Java GC(Garbage Collection)垃圾回收機(jī)制,是Java與C++/C的主要區(qū)別。JVM通過GC來回收堆和方法區(qū)中的內(nèi)存,這個(gè)過程是自動(dòng)執(zhí)行的。因此作為Java開發(fā)者,不需要專門去編寫內(nèi)存回收和垃圾清理代碼,也不需要處理內(nèi)存泄露和溢出的問題。雖然java不需要開發(fā)人員顯示的分配和回收內(nèi)存,這對(duì)開發(fā)人員確實(shí)降低了不少編程難度,但也可能帶來一些副作用:
1.有可能不知不覺浪費(fèi)了很多內(nèi)存;
2.JVM花費(fèi)過多時(shí)間來進(jìn)行內(nèi)存回收;
3.內(nèi)存泄露;
Java GC機(jī)制主要完成3件事:確定哪些內(nèi)存需要回收;確定什么時(shí)候需要執(zhí)行GC;如何執(zhí)行GC。
1.引用計(jì)數(shù)法
所謂引用計(jì)數(shù)法就是給每一個(gè)對(duì)象設(shè)置一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用這個(gè)對(duì)象時(shí),就將計(jì)數(shù)器加一,引用失效時(shí),計(jì)數(shù)器就減一。當(dāng)一個(gè)對(duì)象的引用計(jì)數(shù)器為零時(shí),說明此對(duì)象沒有被引用,也就是“死對(duì)象”,將會(huì)被垃圾回收.
引用計(jì)數(shù)法有一個(gè)缺陷就是無法解決循環(huán)引用問題,也就是說當(dāng)對(duì)象 A 引用對(duì)象 B,對(duì)象B 又引用者對(duì)象 A,那么此時(shí) A,B 對(duì)象的引用計(jì)數(shù)器都不為零,也就造成無法完成垃圾回收,所以主流的虛擬機(jī)都沒有采用這種算法。
2.可達(dá)性算法(引用鏈法)
該算法的思想是:從一個(gè)被稱為 GC Roots的對(duì)象開始向下搜索,如果一個(gè)對(duì)象到 GCRoots 沒有任何引用鏈相連時(shí),則說明此對(duì)象不可用。
在 java 中可以作為 GC Roots 的對(duì)象有以下幾種:
(1)虛擬機(jī)棧中引用的對(duì)象方法區(qū)類靜態(tài)屬性引用的對(duì)象方法區(qū)常量池引用的對(duì)象本地方法棧 JNI 引用的對(duì)象
雖然這些算法可以判定一個(gè)對(duì)象是否能被回收,但是當(dāng)滿足上述條件時(shí),一個(gè)對(duì)象比不一定會(huì)被回收。當(dāng)一個(gè)對(duì)象不可達(dá) GC Root 時(shí),這個(gè)對(duì)象并不會(huì)立馬被回收,而是出于一個(gè)死緩的階段,若要被真正的回收需要經(jīng)歷兩次標(biāo)記
(2)如果對(duì)象在可達(dá)性分析中沒有與 GC Root 的引用鏈,那么此時(shí)就會(huì)被第一次標(biāo)記并且進(jìn)行一次篩選,篩選的條件是是否有必要執(zhí)行 finalize()方法。當(dāng)對(duì)象沒有覆蓋 finalize()方法或者已被虛擬機(jī)調(diào)用過,那么就認(rèn)為是沒必要的。
(3)如果該對(duì)象有必要執(zhí)行 finalize()方法,那么這個(gè)對(duì)象將會(huì)放在一個(gè)稱為 F-Queue 的對(duì)隊(duì)列中,虛擬機(jī)會(huì)觸發(fā)一個(gè) Finalize()線程去執(zhí)行,此線程是低優(yōu)先級(jí)的,并且虛擬機(jī)不會(huì)承諾一直等待它運(yùn)行完,這是因?yàn)槿绻?finalize()執(zhí)行緩慢或者發(fā)生了死鎖,那么就會(huì)造成 FQueue 隊(duì)列一直等待,造成了內(nèi)存回收系統(tǒng)的崩潰。GC 對(duì)處于 F-Queue 中的對(duì)象進(jìn)行第二次被標(biāo)記,這時(shí),該對(duì)象將被移除”即將回收”集合,等待回收。
GC按照回收區(qū)域又分為兩大種類型:部分收集和整堆收集。
部分收集(Partial GC):不是完整收集整個(gè)Java堆的垃圾收集。其中又分為:
1. 新生代收集(Minor GC/Young GC):只是新生代的垃圾收集
當(dāng)年輕代空間不足時(shí),就會(huì)觸發(fā)MinorGC,這里的年輕代滿指的是Eden代滿,Survivor滿不會(huì)引發(fā)GC。
2. 老年代收集(MajorGC/o1dGC):只是老年代的圾收集。
3. 混合收集(MixedGC):收集整個(gè)新生代以及部分老年代的垃圾收集。
整堆收集(FullGC):收集整個(gè)java堆和方法區(qū)的垃圾收集。
1.在初始階段,新創(chuàng)建的對(duì)象被分配到Eden區(qū),S0和S1的兩塊空間都為空。
2.當(dāng)Eden區(qū)滿了的時(shí)候,Minor GC 被觸發(fā) 。經(jīng)過掃描與標(biāo)記,不存活的對(duì)象被回收,存活的對(duì)象被復(fù)制到S0,并且存活的對(duì)象年齡都增大一歲。
3.當(dāng)Eden區(qū)又滿的時(shí)候,Minor GC再次被觸發(fā)。此時(shí)Eden區(qū) 和 S0區(qū)存活的對(duì)象要復(fù)制到S1。需要注意的是,此時(shí)Eden區(qū)和S0區(qū)被清空,S0中的對(duì)象復(fù)制到S1后其年齡要加1。
4.當(dāng)Eden區(qū)再次又滿的時(shí)候,MinorGC則重復(fù)上面過程,將Eden區(qū) 和 S1區(qū)存活的對(duì)象復(fù)制到S0。此時(shí)Eden區(qū)和S1區(qū)被清空,S0中的對(duì)象復(fù)制到S1后其年齡要加1。
5.經(jīng)過幾次Minor GC之后,當(dāng)存活對(duì)象的年齡達(dá)到一個(gè)閾值之后(-XX:MaxTenuringThreshold默認(rèn)是15),就會(huì)被從年輕代Promotion到老年代。
6.新生代 GC(Minor GC):指發(fā)生新生代的的垃圾收集動(dòng)作,Minor GC 非常頻繁,回收速度一般也比較快。
7.老年代 GC(Major GC/Full GC):指發(fā)生在老年代的 GC,出現(xiàn)了 Major GC 經(jīng)常會(huì)伴隨至少一次的 Minor GC(并非絕對(duì)),Major GC 的速度一般會(huì)比 Minor GC 的慢 10 倍以上。
JDK1.2 以前,一個(gè)對(duì)象只有被引用和沒有被引用兩種狀態(tài)。后來,Java 對(duì)引用的概念進(jìn)行了擴(kuò)充,將引用分為強(qiáng)引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)4 種,這 4 種引用強(qiáng)度依次逐漸減弱。
一共有 4 種:標(biāo)記-清除算法、復(fù)制算法、標(biāo)記整理算法、分代收集算法;
最基礎(chǔ)的收集算法是“標(biāo)記-清除”(Mark-Sweep)算法,分為“標(biāo)記”和“清除”兩個(gè)階段:首先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象。
● 效率問題,標(biāo)記和清除兩個(gè)過程的效率都不高;
● 空間問題,標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會(huì)導(dǎo)致以后在程序運(yùn)行過程中需要分配較大對(duì)象時(shí),無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動(dòng)作。
它將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對(duì)象復(fù)制到另外一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉。這樣使得每次都是對(duì)整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收,內(nèi)存分配時(shí)也就不用考慮內(nèi)存碎片等復(fù)雜情況,只要移動(dòng)堆頂指針,按順序分配內(nèi)存即可,實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效。只是這種算法的代價(jià)是將內(nèi)存縮小為了原來的一半。復(fù)制算法的執(zhí)行過程如下圖:
一般虛擬機(jī)都采用這種算法來回收新生代,因?yàn)樾律械膶?duì)象 98% 是“朝生夕死”的,所以并不需要按照 1:1 的比例來劃分內(nèi)存空間,而是將內(nèi)存分為一塊較大的 Eden 空間和兩塊較小的 Survivor 空間,每次使用 Eden 和其中一塊 Survivor 。當(dāng)回收時(shí),將 Eden 和 Survivor 中還存活著的對(duì)象一次性地復(fù)制到另外一塊 Survivor 空間上,最后清理掉 Eden 和剛才用過的 Survivor 空間。HotSpot 虛擬機(jī)默認(rèn) Eden:Survivor = 8:1,也就是每次新生代中可用內(nèi)存空間為整個(gè)新生代容量的 90%(其中一塊Survivor不可用),只有 10% 的內(nèi)存會(huì)被“浪費(fèi)”。當(dāng)然如果另外一塊 Survivor 空間沒有足夠空間存放上一次新生代收集下來的存活對(duì)象時(shí),這些對(duì)象將直接通過分配擔(dān)保機(jī)制進(jìn)入老年代。
復(fù)制算法適合年輕代,不適合老年代。因?yàn)樵趯?duì)象存活率較高時(shí)就要進(jìn)行較多的復(fù)制操作,效率將會(huì)變低。更關(guān)鍵的是復(fù)制算法需要有額外的空間進(jìn)行分配擔(dān)保,以應(yīng)對(duì)被使用的內(nèi)存中所有對(duì)象都 100% 存活的極端情況。
根據(jù)老年代的特點(diǎn),有人提出了另外一種“標(biāo)記-整理”(Mark-Compact)算法,標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣,但后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都向一端移動(dòng),然后直接清理掉端邊界以外的內(nèi)存。
"分代收集"(Generational Collection)算法是根據(jù)對(duì)象存活周期的不同將內(nèi)存劃分為幾塊并采用不同的垃圾收集算法。一般是把 Java 堆分為新生代和老年代,這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴?。在新生代中,每次垃圾收集時(shí)都發(fā)現(xiàn)有大批對(duì)象死去,只有少量存活,那就選用復(fù)制算法,只需要付出少量存活對(duì)象的復(fù)制成本就可以完成收集。而老年代中因?yàn)閷?duì)象存活率高、沒有額外空間對(duì)它進(jìn)行分配擔(dān)保,就必須使用“標(biāo)記—清理”或者“標(biāo)記—整理”算法來進(jìn)行回收。
進(jìn)行垃圾收集時(shí),必須暫停其他所有工作線程,Sun將這種事情叫做"Stop The World"。
如果新生代的垃圾收集器為Serial和ParNew,并且設(shè)置了-XX:PretenureSizeThreshold參數(shù),當(dāng)對(duì)象大于這個(gè)參數(shù)值時(shí),會(huì)被認(rèn)為是大對(duì)象,直接進(jìn)入老年代。
Young GC后,如果對(duì)象太大無法進(jìn)入Survivor區(qū),則會(huì)通過分配擔(dān)保機(jī)制進(jìn)入老年代。
對(duì)象每在Survivor區(qū)中“熬過”一次Young GC,年齡就增加1歲,當(dāng)它的年齡增加到一定程度(默認(rèn)為15歲,可以通過-XX:MaxTenuringThreshold設(shè)置),就將會(huì)被晉升到老年代中。
如果在Survivor區(qū)中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代,無須等到MaxTenuringThreshold中要求的年齡。
在HotSpot虛擬機(jī)中,Eden區(qū)和Survivor區(qū)的默認(rèn)比例為8:1:1,即-XX:SurvivorRatio=8,其中Survivor分為From Survivor和ToSurvivor,因此Eden此時(shí)占新生代空間的80%。
Minor GC是當(dāng)年輕代空間不足時(shí),就會(huì)觸發(fā)MinorGC,這里的年輕代滿指的是Eden代滿,Survivor滿不會(huì)引發(fā)GC(每次Minor GC會(huì)清理年輕代的內(nèi)存)。因?yàn)镴ava對(duì)象大多都具備朝生夕滅的特性,所以Minor GC非常頻繁,一般回收速度也比較快。這一定義既清晰又易于理解。Minor GC會(huì)引發(fā)STW,暫停其它用戶的線程,等垃圾回收結(jié)束,用戶線程才恢復(fù)運(yùn)行
Major GC指發(fā)生在老年代的GC,對(duì)象從老年代消失時(shí),我們說"Major Gc"或"Full GC"發(fā)生了。出現(xiàn)了MajorGc,經(jīng)常會(huì)伴隨至少一次的Minor GC(但非絕對(duì)的,在Paralle1 Scavenge收集器的收集策略里就有直接進(jìn)行MajorGC的策略選擇過程),也就是在老年代空間不足時(shí),會(huì)先嘗試觸發(fā)MinorGc。如果之后空間還不足,則觸發(fā)Major GC,Major GC的速度一般會(huì)比MinorGc慢1e倍以上,STW的時(shí)間更長(zhǎng),如果Major GC后,內(nèi)存還不足,就報(bào)OOM了;
Full GC是對(duì)年輕代和老年代都進(jìn)行垃圾回收,F(xiàn)ull GC 是開發(fā)或調(diào)優(yōu)中盡量要避免的,這樣暫時(shí)時(shí)間會(huì)短一些。
Minor GC觸發(fā)條件: 當(dāng)Eden區(qū)滿時(shí),觸發(fā)Minor GC。
Full GC觸發(fā)條件:
(1)調(diào)用System.gc時(shí),系統(tǒng)建議執(zhí)行Full GC,但是不必然執(zhí)行
(2)老年代空間不足
(3)方法區(qū)空間不足
(4)通過Minor GC后進(jìn)入老年代的平均大小大于老年代的可用內(nèi)存
(5)由Eden區(qū)、From Space區(qū)向To Sp3ace區(qū)復(fù)制時(shí),對(duì)象大小大于To Space可存,則把該對(duì)象轉(zhuǎn)存到老年代,且老年代的可用內(nèi)存小于該對(duì)象大??;
最基本的垃圾收集器,使用復(fù)制算法,單線程,雖然收集垃圾時(shí)需要暫停其他所有的工作線程,但簡(jiǎn)單高效,是 java 虛擬機(jī)運(yùn)行在 Client 模式下默認(rèn)的新生代垃圾收集器。
在HotSpot虛擬機(jī)中,使用-XX:+UseSerialGC參數(shù)可以指定年輕代和老年代都使用串行收集器。等價(jià)于新生代用Serial GC,且老年代用Serial Old GC。
是 Serial 收集器的多線程版本 ,除了多線程進(jìn)行GC外,其他與Serial一樣,默認(rèn)開啟和 CPU 數(shù)目相同的線程數(shù) 。是很多 java虛擬機(jī)運(yùn)行在 Server 模式下新生代的默認(rèn)垃圾收集器。
1)可以通過選項(xiàng)"-XX:+UseParNewGC"手動(dòng)指定使用ParNew收集器執(zhí)行內(nèi)存回收任務(wù)。它表示年輕代使用并行收集器,不影響老年代
2)這里的多線程指的是垃圾收集時(shí),多線程并行,并不是垃圾收集與程序運(yùn)行并行
3)收集垃圾時(shí),也需要暫停其他所有工作線程,然后多線程收集垃圾。
4)單CPU環(huán)境下,因?yàn)榫€程切換,性能較差。
關(guān)注程序的吞吐量,即吞吐量?jī)?yōu)先。主要適用于在后臺(tái)運(yùn)算而不需要太多交互的任務(wù)。 自適應(yīng)調(diào)節(jié)策略也是 ParallelScavenge 收集器與 ParNew 收集器的一個(gè)重要區(qū)別。
1)吞吐量=運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間+垃圾收集時(shí)間) ; 吞吐量?jī)?yōu)先,意味著在單位時(shí)間內(nèi),STW的時(shí)間最短;與之相對(duì)應(yīng)的 低延遲 就是暫停時(shí)間優(yōu)先,盡可能讓單次STW時(shí)間最短;這兩個(gè)無法同時(shí)實(shí)現(xiàn)。
2)收集垃圾時(shí),也需要暫停其他所有工作線程,然后多線程收集垃圾。
3)參數(shù)配置
-XX:+UseParallelGC 手動(dòng)指定年輕代使用Parallel并行收集器執(zhí)行內(nèi)存回收任務(wù)。
-XX:+UseParallelOldGC 手動(dòng)指定老年代都是使用并行回收收集器。
-XX:ParallelGCThreads 設(shè)置年輕代并行收集器的線程數(shù)。一般地,最好與CPU數(shù)量相等,以避免過多的線程數(shù)影響垃圾收集性能。
-XX:MaxGCPauseMillis 設(shè)置垃圾收集器最大停頓時(shí)間(即STw的時(shí)間),單位是毫秒。 為了盡可能地把停頓時(shí)間控制在MaxGCPauseMills以內(nèi),收集器在工作時(shí)會(huì)調(diào)整Java堆大小或者其他一些參數(shù)。對(duì)于用戶來講,停頓時(shí)間越短體驗(yàn)越好。但是在服務(wù)器端,我們注重高并發(fā),整體的吞吐量。所以服務(wù)器端適合Parallel進(jìn)行控制。該參數(shù)使用需謹(jǐn)慎。
-XX:GCTimeRatio 垃圾收集時(shí)間占總時(shí)間的比例(=1/(N+1))。用于衡量吞吐量的大小。 取值范圍(0, 100)。默認(rèn)值99,也就是垃圾回收時(shí)間不超過1%。與前一個(gè)-XX:MaxGCPauseMillis參數(shù)有一定矛盾性。暫停時(shí)間越長(zhǎng),Radio參數(shù)就容易超過設(shè)定的比例。
-XX:+UseAdaptivesizePolicy 設(shè)置Parallel Scavenge收集器具有自適應(yīng)調(diào)節(jié)策略 。在這種模式下,年輕代的大小、Eden和Survivor的比例、晉升老年代的對(duì)象年齡等參數(shù)會(huì)被自動(dòng)調(diào)整,已達(dá)到在堆大小、吞吐量和停頓時(shí)間之間的平衡點(diǎn)。在手動(dòng)調(diào)優(yōu)比較困難的場(chǎng)合,可以直接使用這種自適應(yīng)的方式,僅指定虛擬機(jī)的最大堆、目標(biāo)的吞吐量(GCTimeRatio)和停頓時(shí)間(MaxGCPauseMills,讓虛擬機(jī)自己完成調(diào)優(yōu)工作。
1)是Serial的老年代版本,收集垃圾時(shí)也需要暫停其他所有的工作線程。
2)是Client模式下默認(rèn)的老年代垃圾收集器
3)Server模式下,搭配新生代的Parallel Scavenge 收集器使用(在 JDK1.5 之前版本中)。同時(shí)也作為老年代中使用 CMS 收集器的后備垃圾收集方案(當(dāng)CMS發(fā)生Concurrent Mode Failure)。
1)Parallel Scavenge的老年代版本
2)吞吐量?jī)?yōu)先,意味著在單位時(shí)間內(nèi),STW的時(shí)間最短;與之相對(duì)應(yīng)的 低延遲 就是暫停時(shí)間優(yōu)先,盡可能讓單次STW時(shí)間最短;這兩個(gè)無法同時(shí)實(shí)現(xiàn)。
3)若相同對(duì)于吞吐量要求較高,可以Parallel Scavenge搭配Parallel Old使用。
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。目前很大一部分的Java應(yīng)用集中在互聯(lián)網(wǎng)站或者B/S系統(tǒng)的服務(wù)端上,這類應(yīng)用尤其重視服務(wù)的響應(yīng)速度,希望系統(tǒng)停頓時(shí)間最短,以給用戶帶來較好的體驗(yàn)。CMS收集器就非常符合這類應(yīng)用的需求。
從名字(包含“Mark Sweep”)上就可以看出,CMS收集器是基于“標(biāo)記—清除”算法實(shí)現(xiàn)的,它的運(yùn)作過程可以分為6個(gè)步驟,包括:初始標(biāo)記、并發(fā)標(biāo)記、預(yù)處理、重新標(biāo)記、并發(fā)清除、重置。
CMS是一款優(yōu)秀的收集器,它的主要優(yōu)點(diǎn)在名字上已經(jīng)體現(xiàn)出來了:并發(fā)收集、低停頓,但是CMS還遠(yuǎn)達(dá)不到完美的程度,它有以下3個(gè)明顯的缺點(diǎn):
(1)CMS收集器對(duì)CPU資源非常敏感。
(2)CMS收集器無法處理浮動(dòng)垃圾(Floating Garbage),可能出現(xiàn)“Concurrent Mode Failure”失敗而導(dǎo)致另一次Full GC的產(chǎn)生。
(3)CMS是一款基于“標(biāo)記—清除”算法實(shí)現(xiàn)的收集器,這意味著收集結(jié)束時(shí)會(huì)有大量空間碎片產(chǎn)生。
G1(Garbage-First)收集器是當(dāng)今收集器技術(shù)發(fā)展的最前沿成果之一。G1是一款面向服務(wù)端應(yīng)用的垃圾收集器。與其他GC收集器相比,G1具備如下特點(diǎn):并行與并發(fā)、分代收集、空間整合、可預(yù)測(cè)的停頓。
在G1之前的其他收集器進(jìn)行收集的范圍都是整個(gè)新生代或者老年代,而G1不再是這樣。使用G1收集器時(shí),Java堆的內(nèi)存布局就與其他收集器有很大差別,它將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域,雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離的了,它們都是一部分Region(不需要連續(xù))的集合。
G1收集器之所以能建立可預(yù)測(cè)的停頓時(shí)間模型,是因?yàn)樗梢杂杏?jì)劃地避免在整個(gè)Java堆中進(jìn)行全區(qū)域的垃圾收集。G1跟蹤各個(gè)Region里面的垃圾堆積的價(jià)值大?。ɑ厥账@得的空間大小以及回收所需時(shí)間的經(jīng)驗(yàn)值),在后臺(tái)維護(hù)一個(gè)優(yōu)先列表,每次根據(jù)允許的收集時(shí)間,優(yōu)先回收價(jià)值最大的Region(這也就是Garbage-First名稱的來由)。這種使用Region劃分內(nèi)存空間以及有優(yōu)先級(jí)的區(qū)域回收方式,保證了G1收集器在有限的時(shí)間內(nèi)可以獲取盡可能高的收集效率。
Mixed GC是G1垃圾收集器特有的收集方式,Mixed GC大致可劃分為全局并發(fā)標(biāo)記(global concurrent marking)和拷貝存活對(duì)象(evacuation)兩個(gè)大部分:
global concurrent marking是基于SATB形式的并發(fā)標(biāo)記,包括以下4個(gè)階段:初始標(biāo)記(Initial Marking)、并發(fā)標(biāo)記(Concurrent Marking)、最終標(biāo)記(Final Marking)、清理(Clean Up)。Evacuation階段是全暫停的。它負(fù)責(zé)把一部分region里的活對(duì)象拷貝到空region里去,然后回收原本的region的空間。
垃圾收集器 | 分類 | 作用位置 | 使用算法 | 特點(diǎn) | 適用場(chǎng)景 |
---|---|---|---|---|---|
Serial | 串行 | 新生代 | 復(fù)制算法 | 響應(yīng)速度優(yōu)先 | 適用于單CPU環(huán)境下的client模式 |
ParNew | 并行 | 新生代 | 復(fù)制算法 | 響應(yīng)速度優(yōu)先 | 多CPU環(huán)境Server模式下與CMS配合使用 |
Parallel | 并行 | 新生代 | 復(fù)制算法 | 吞吐量?jī)?yōu)先 | 適用于后臺(tái)運(yùn)算而不需要太多交互的場(chǎng)景 |
Serial Old | 串行 | 老年代 | 標(biāo)記-整理(壓縮)算法 | 響應(yīng)速度優(yōu)先 | 適用于單CPU環(huán)境下的Client模式 |
Paraller Old | 并行 | 老年代 | 標(biāo)記-整理(壓縮)算法 | 標(biāo)記-整理(壓縮)算法 | 適用于后臺(tái)運(yùn)算而不需要太多交互的場(chǎng)景 |
CMS | 并發(fā) | 老年代 | 標(biāo)記-清除算法 | 響應(yīng)速度優(yōu)先 | 適用于互聯(lián)網(wǎng)或B/S業(yè)務(wù) |
G1 | 并發(fā)、并行 | 新生代、老年代 | 標(biāo)記-整理(壓縮)算法 | 響應(yīng)速度優(yōu)先 | 響應(yīng)速度優(yōu)先 |
1)多數(shù)的Java應(yīng)用不需要在服務(wù)器上進(jìn)行GC優(yōu)化;
2)多數(shù)導(dǎo)致GC問題的Java應(yīng)用,都不是因?yàn)槲覀儏?shù)設(shè)置錯(cuò)誤,而是代碼問題;
3)在應(yīng)用上線前,先考慮將JVM參數(shù)設(shè)置到最優(yōu);
4)減少對(duì)象創(chuàng)建的數(shù)量;
5)減少全局變量和大對(duì)象;
6)GC優(yōu)化是最后不得已才使用的手段,在實(shí)際應(yīng)用中,分析GC情況優(yōu)化代碼比優(yōu)化GC參數(shù)要多得多;
通過看監(jiān)控中的jvm是否有fgc,頻繁fgc才需要優(yōu)化(頻繁fgc需要抓緊改配置)
1)JDK的命令行工具
Sun JDK監(jiān)控和故障處理命令有jps、jstat、jmap、jhat、jstack、jinfo
jps(虛擬機(jī)進(jìn)程狀況工具):顯示指定系統(tǒng)內(nèi)所有的HotSpot虛擬機(jī)進(jìn)程。 jstat(虛擬機(jī)統(tǒng)計(jì)信息監(jiān)視工具):用于監(jiān)視虛擬機(jī)運(yùn)行時(shí)狀態(tài)信息的命令,它可以顯示出虛擬機(jī)進(jìn)程中的類裝載、內(nèi)存、垃圾收集、JIT編譯等運(yùn)行數(shù)據(jù)。 jinfo(Java配置信息工具):jinfo的作用是實(shí)時(shí)地查看和調(diào)整虛擬機(jī)各項(xiàng)參數(shù)。
jmap(Java內(nèi)存映像工具):dump堆到文件,可用于對(duì)文件的分析。
jhat(虛擬機(jī)堆轉(zhuǎn)儲(chǔ)快照分析工具):jhat命令與jmap搭配使用,來分析jmap生成的堆 轉(zhuǎn)儲(chǔ)快照。jhat內(nèi)置了一個(gè)微型的HTTP/HTML服務(wù)器,生成dump文件的分析結(jié)果后,可以在瀏覽器中查看。
jstack(Java堆棧跟蹤工具):jstack命令用于生成虛擬機(jī)當(dāng)前時(shí)刻的線程快照。線程快照就是當(dāng)前虛擬機(jī)內(nèi)每一條線程正在執(zhí)行的方法堆棧 的集合,生成線程快照的主要目的是定位線程出現(xiàn)長(zhǎng)時(shí)間停頓的原因,如線程間死鎖、死循 環(huán)、請(qǐng)求外部資源導(dǎo)致的長(zhǎng)時(shí)間等待等都是導(dǎo)致線程長(zhǎng)時(shí)間停頓的常見原因。線程出現(xiàn)停頓 的時(shí)候通過jstack來查看各個(gè)線程的調(diào)用堆棧,就可以知道沒有響應(yīng)的線程到底在后臺(tái)做些 什么事情,或者等待著什么資源。
2)JConsole
Jconsole(Java Monitoring and Management Console)是從java5開始,在JDK中自帶的java監(jiān)控和管理控制臺(tái),用于對(duì)JVM中內(nèi)存,線程和類等的監(jiān)控,是一個(gè)基于JMX(java management extensions)的GUI性能監(jiān)測(cè)工具。jconsole使用jvm的擴(kuò)展機(jī)制獲取并展示虛擬機(jī)中運(yùn)行的應(yīng)用程序的性能和資源消耗等信息。
概覽:包括堆內(nèi)存使用情況、線程、類、CPU使用情況四項(xiàng)信息的曲線圖。
3)VisualVM
VisualVM(All-in-One Java Troubleshooting Tool)是功能最強(qiáng)大的運(yùn)行監(jiān)視和故障處理程序之一,曾經(jīng)在很長(zhǎng)一段時(shí)間內(nèi)是Oracle官方主力發(fā)展的虛擬機(jī)故障處理工具。
相比一些第三方工具,VisualVM有一個(gè)很大的優(yōu)點(diǎn):不需要被監(jiān)視的程序基于特殊Agent去運(yùn)行,因此它的通用性很強(qiáng),對(duì)應(yīng)用程序?qū)嶋H性能的影響也較小,使得它可以直接應(yīng)用在生產(chǎn)環(huán)境中。
Visual GC 是常常使用的一個(gè)功能,需要通過插件按照,可以明顯的看到年輕代、老年代的內(nèi)存變化,以及gc頻率、gc的時(shí)間等!
觸發(fā) java.lang.OutOfMemoryError:最常見的原因就是應(yīng)用程序需要的堆空間需要的是大的,但是 JVM 提供的卻是小的,從而導(dǎo)致內(nèi)存溢出。這個(gè)解決方法就是提供大的堆空間即可。
除此之外還有復(fù)雜的原因:內(nèi)存泄露。特定的編程錯(cuò)誤會(huì)導(dǎo)致你的應(yīng)用程序不停的消耗更多的內(nèi)存,每次使用有內(nèi)存泄漏風(fēng)險(xiǎn)的功能就會(huì)留下一些不能被回收的對(duì)象到堆空間中,隨著時(shí)間的推移,泄漏的對(duì)象會(huì)消耗所有的堆空間,最終觸發(fā)java.lang.OutOfMemoryError: Java heap space 錯(cuò)誤。
1.確保有足夠的堆空間來正常運(yùn)行你的應(yīng)用程序,在 JVM 的啟動(dòng)配置中增加如下配置:-Xmx1024m。
2.流量/數(shù)據(jù)量峰值:應(yīng)用程序在設(shè)計(jì)之初均有用戶量和數(shù)據(jù)量的限制,某一時(shí)刻,當(dāng)用戶數(shù)量或數(shù)據(jù)量突然達(dá)到一個(gè) 峰 值 , 并 且 這 個(gè) 峰 值 已 經(jīng) 超 過 了 設(shè) 計(jì) 之 初 預(yù) 期 的 閾 值 , 那 么 以 前 正 常 的 功 能 將 會(huì) 停 止 , 并 觸 發(fā)java.lang.OutOfMemoryError
3.Java heap space 異常解決方案,如果你的應(yīng)用程序確實(shí)內(nèi)存不足,增加堆內(nèi)存會(huì)解決 GC overhead limit 問題,就如下面這樣,給你的應(yīng)用程序 1G 的堆內(nèi)存:java -Xmx1024m com.yourcompany.YourClass。