JVM汪疮、Dalvik返咱、ART

文章較長,主要講解了JVM的整個(gè)流程哥放,其次點(diǎn)了Dalvik與JVM的區(qū)別及ART
JVM標(biāo)題下
Class文件結(jié)構(gòu) -> JVM內(nèi)存模型 -> 類加載器 -> 類加載過程 -> 類的引用方式 -> 內(nèi)存分配策略 -> GC -> 對象的引用類型 -> 類卸載

先前知識(shí)

眾所周知java是一種跨平臺(tái)的語言歼指,但實(shí)際上跨平臺(tái)的并不是java而是JVM。

JVM(Java Virtual Machine)是一種虛擬機(jī)甥雕,用來將由java文件編譯成的class字節(jié)碼文件再編譯成機(jī)器語言踩身,供機(jī)器識(shí)別。有了JVM中間人的存在就不需要直接與操作系統(tǒng)打交道社露,且不同的操作系統(tǒng)有不同的JVM挟阻,于是就屏蔽了操作系統(tǒng)間的差異,從而使java成為跨平臺(tái)語言峭弟。

DVM又是什么附鸽?

Dalvik Virtual Machine簡稱DVM也是一種虛擬機(jī),是專門為Android平臺(tái)開發(fā)的瞒瘸,它與JVM是有差別的坷备。

Dalvik基于寄存器,而JVM 基于棧情臭。性能有很大的提升击你∮褡椋基于寄存器的虛擬機(jī)對于更大的程序來說,在它們編譯的時(shí)候丁侄,花費(fèi)的時(shí)間更短惯雳。

寄存器的概念

寄存器是中央處理器內(nèi)的組成部分。寄存器是有限存貯容量的高速存貯部件鸿摇,它們可用來暫存指令石景、數(shù)據(jù)和位址。在中央處理器的控制部件中拙吉,包含的寄存器有指令寄存器(IR)和程序計(jì)數(shù)器(PC)潮孽,在中央處理器的算術(shù)及邏輯部件中,包含的寄存器有累加器(ACC)

棧的概念

棧是線程獨(dú)有的筷黔,保存其運(yùn)行狀態(tài)和局部自動(dòng)變量的(所以多線程中局部變量都是相互獨(dú)立的往史,不同于類變量)。棧在線程開始的時(shí)候初始化(線程的Start方法佛舱,初始化分配棧)椎例,每個(gè)線程的棧互相獨(dú)立请祖。每個(gè)函數(shù)都有自己的棧订歪,棧被用來在函數(shù)之間傳遞參數(shù)。操作系統(tǒng)在切換線程的時(shí)候會(huì)自動(dòng)的切換棧肆捕,就是切換SS/ESP寄存器刷晋。棧空間不需要在高級(jí)語言里面顯式的分配和釋放慎陵。

JVM

java的使用流程:

  • 編寫.java文件
  • 編譯成.class
    • 打包成.jar(Java Archive) .war(Web Archive)使用
    • 命令行則直接使用.class

其實(shí).jar和.war是.class文件的壓縮包眼虱,其中還包含了不同的配置文件,使用時(shí)通過類加載器取其內(nèi)部的.class字節(jié)碼文件加載到JVM席纽。

Class文件結(jié)構(gòu)

JVM接收的最初數(shù)據(jù)是class字節(jié)碼文件蒙幻,由.java文件編譯產(chǎn)生。并不是只有java語言可以編譯成class文件胆筒,其他語言也是可以(Scala、Groovy)诈豌。

class文件結(jié)構(gòu)

class文件是由8位為一組的字節(jié)為基本單位仆救,構(gòu)成的二進(jìn)制文件,為什么是二進(jìn)制文件呢矫渔?

  • 機(jī)器語言為二進(jìn)制彤蔽,所以使用便捷;
  • 占用空間小庙洼,3.1415927用文本文件存儲(chǔ)需要將各個(gè)位轉(zhuǎn)成ASCII碼再存儲(chǔ)需占用9字節(jié)顿痪,二進(jìn)制文件存儲(chǔ)只需四字節(jié)镊辕;
  • 存儲(chǔ)數(shù)據(jù)精確不會(huì)丟失。

結(jié)構(gòu)如上圖蚁袭,最上方為起始位征懈,內(nèi)容包含了.java文件的信息。

類型

class文件中只有兩種類型:無符號(hào)數(shù)和表

無符號(hào)數(shù)為基本類型揩悄,有:u1卖哎、u2、u4删性、u8亏娜,數(shù)字代表字節(jié)數(shù)。無符號(hào)數(shù)可以代表數(shù)字蹬挺、索引引用维贺、數(shù)量值,或者按照UTF-8編碼構(gòu)成字符串值

表則是由基本類型和表構(gòu)成的類型巴帮,屬于組合類型

magic

文件最初的4個(gè)字節(jié)溯泣,稱為魔數(shù),是計(jì)算機(jī)識(shí)別文件類型的依據(jù)晰韵,不同于感官发乔,.word .png .avi這種通過擴(kuò)展名識(shí)別文件類型的方式,計(jì)算機(jī)識(shí)別多數(shù)文件是通過文件頭部的魔數(shù)雪猪。

這種做法的優(yōu)點(diǎn)在于安全性栏尚,文件的擴(kuò)展名可以人為隨意的修改,也許并不會(huì)造成文件的不可用(早年間的“圖種”一詞不知多少人有經(jīng)歷)只恨,也可能造成文件不可用译仗,但文件的類型在文件創(chuàng)建之初就被賦予魔數(shù)的話,就可以大限度的保證文件的安全性官觅。

version

表示此.class的版本信息纵菌,有minor_version和major_version兩種類型共占了4字節(jié)。

不同版本Java有不同的特性休涤,產(chǎn)生的class結(jié)構(gòu)也會(huì)不同咱圆,JVM通過識(shí)別版本從而確定是否可識(shí)別此文件。JVM是向下兼容的功氨,如果.class版本過高則不能運(yùn)行(Unsupported major.minor version **)序苏。

constant

constant_pool為常量池,用來存放常量捷凄,constant_pool_count為池中的計(jì)數(shù)忱详。

constant_pool的索引從1開始,當(dāng)指針不想引用此constant_pool時(shí)則將指針指向0跺涤,此操作簡單(賦值永遠(yuǎn)比刪除簡單)匈睁。

常量池中有兩大類常量類型:字面量和符號(hào)引用

  • 字面量為java中的基本數(shù)據(jù)類型
  • 符號(hào)引用:以一組符號(hào)來描述所引用的目標(biāo)监透,引用的目標(biāo)并不一定已經(jīng)加載到內(nèi)存中,在類加載過程的解析階段JVM將常量池內(nèi)的符號(hào)引用替換為直接引用航唆。類型有:
    1胀蛮、CONSTANT_Class_info 類和接口的全限定名(該名稱在所有類型中唯一標(biāo)識(shí)該類型)
    2、CONSTANT_Fieldref_info 字段的名稱和描述符
    3佛点、CONSTANT_Methodref_info 方法的名稱和描述符

常量池中每一項(xiàng)常量都是一個(gè)表

常量類型

講解:

class A{
    int i=9;
    int b=new B();
}

class B{
}

編譯時(shí)會(huì)產(chǎn)生A.class和B.class醇滥,此時(shí)A.class有兩個(gè)常量9和B。JVM加載A.class時(shí)超营,將由于常量9屬于字面量即基本數(shù)據(jù)類型鸳玩,直接放入常量池。

到常量B時(shí)演闭,由于常量B不屬于字面量即基本數(shù)據(jù)類型不跟,所以此時(shí)產(chǎn)生一個(gè)符號(hào)引用來代表常量B。

等到A.class加載到了解析階段米碰,需要將符號(hào)引用改為直接引用窝革,但找不到符號(hào)引用B的直接引用,

在使用階段吕座,由于A對象主動(dòng)引用了B類虐译,所以JVM通過類加載器開始加載B.class(同樣的加載步驟),并創(chuàng)建了B對象吴趴,并將符號(hào)引用B改為B對象的直接引用漆诽。

access

access_flags即java中的類修飾符

類的身份信息

一個(gè)類要有類名,關(guān)系要有extends和implement锣枝。java中類是單繼承厢拭,所以除Object外所有類都有一個(gè)父類,而接口則可以有多實(shí)現(xiàn)撇叁。

this_class是這個(gè)類的全限定名
super_class是這個(gè)類父類的全限定名
interfaces是這個(gè)類實(shí)現(xiàn)接口的集合
interfaces_count是這個(gè)類實(shí)現(xiàn)接口的數(shù)量

fields

fields_count表示fields表中的數(shù)量

fields是表結(jié)構(gòu)用來存放字段供鸠,字段即為類中聲明的變量。字段包括了類級(jí)變量或?qū)嵗?jí)變量陨闹,static修飾符為判斷依據(jù)楞捂。

public static final transient String str = "Hello World";

一個(gè)字段包含的信息有:

  • 作用域(public、private趋厉、protected修飾符)
  • 類級(jí)變量還是實(shí)例級(jí)變量(static修飾符)
  • 可變性(final)
  • 并發(fā)可見性(volatile修飾符寨闹,是否強(qiáng)制從主內(nèi)存讀寫)
  • 可否序列化(transient修飾符)
  • 字段數(shù)據(jù)類型(基本類型、對象觅廓、數(shù)組)
  • 字段名稱(str)

一個(gè)字段有多種修飾符,每種修飾符只有兩種狀態(tài):有涵但、沒有杈绸,所以采用標(biāo)志位來表示最為合理帖蔓。字段的其他信息,叫什么名字瞳脓、被定義為什么數(shù)據(jù)類型塑娇,這些都是無法固定的,所以引用常量池中的常量來描述劫侧。

字段結(jié)構(gòu)

access_flags:修飾符

修飾符標(biāo)志位

name_index:簡單名稱
指變量名埋酬,存放在常量池烧栋。例如字段str的簡單名稱“str”珍特。

descriptor_index:描述符
描述字段的類型


類型列表

例子:
java.lang.String[][] —— [[Ljava/lang/String
int[] —— [I
String s —— Ljava/lang/String

attributes:屬性集合,以用于描述某些場景專有的信息魔吐。

上面的類型只定義了變量信息扎筒,那變量的初始賦值操作呢?

賦值操作是將常量賦值給變量嗜桌,常量有字面量和符號(hào)引用辞色,字面量會(huì)在常量池中骨宠,符號(hào)引用依據(jù)情況會(huì)在解析或使用階段改為直接引用。

字段賦值的時(shí)機(jī):
a:對于非靜態(tài)的field字段的賦值將會(huì)出現(xiàn)在實(shí)例構(gòu)造方法<init>()中
b:對于靜態(tài)的field字段雳灵,有兩個(gè)選擇:

1棕所、在類構(gòu)造方法<cinit>()中進(jìn)行;
2悯辙、使用ConstantValue屬性進(jìn)行賦值
編譯器對于靜態(tài)field字段的初始化賦值策略:

如果final和static同時(shí)修飾一個(gè)字段琳省,并且這個(gè)字段是基本類型或者String類型的,
那么編譯器在編譯這個(gè)字段的時(shí)候躲撰,會(huì)在對應(yīng)的field_info結(jié)構(gòu)體中增加一個(gè)ConstantValue類型的結(jié)構(gòu)體针贬,在賦值的時(shí)候使用這個(gè)ConstantValue進(jìn)行賦值;

如果該field字段并沒有被final修飾拢蛋,或者不是基本類型或者String類型桦他,那么將在類構(gòu)造方法中賦值。

對于全局變量的值是被編譯在構(gòu)造器中賦值的

https://www.cnblogs.com/straybirds/p/8331687.html

methods

methods_count表示methods表中的數(shù)量

methods是表結(jié)構(gòu)用來存放方法谆棱,表結(jié)構(gòu)和字段的表結(jié)構(gòu)一致

由于部分關(guān)鍵字相對于變量和方法是有區(qū)別的

例子:
int indexOf(char[] source,int sourceOffset,int sourceCount,char[] targetOffset,int targetCount,int fromIndex) —— ([CII[CII)I

方法內(nèi)部的代碼存到什么地方了快压?

attributes:屬性表

在字段表和方法表中都有屬性表

屬性表結(jié)構(gòu)

屬性表所能識(shí)別的屬性有

方法中到具體代碼就存放在方法表中屬性表的Code屬性中

參考 https://blog.csdn.net/sinat_37138973/article/details/54378263

在了解了字節(jié)碼文件的結(jié)構(gòu)后圆仔,JVM要想使用此文件,首先要將其加載到內(nèi)存蔫劣,那內(nèi)存結(jié)構(gòu)是怎樣的呢坪郭?

JVM內(nèi)存模型

圖來自https://www.cnblogs.com/xing901022/p/7725961.html
  • PC
    與CPU中的PC不同,CPU中的PC是記錄即將執(zhí)行的下條指令的地址脉幢,而JVM中記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址歪沃,且執(zhí)行native方法時(shí)PC為空
  • 虛擬機(jī)棧
    每個(gè)方法被執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)棧幀,用于存儲(chǔ)局部變量表嫌松、操作棧沪曙、動(dòng)態(tài)鏈接、方法出口等信息豆瘫。每一個(gè)方法被調(diào)用直至執(zhí)行完成的過程珊蟀,就對應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過程。
    如果線程請求的棧深度大于虛擬機(jī)所允許的深度外驱,將拋出StackOverflowError 異常育灸;如果虛擬機(jī)棧可以動(dòng)態(tài)擴(kuò)展當(dāng)擴(kuò)展時(shí)無法申請到足夠的內(nèi)存時(shí)會(huì)拋出OutOfMemoryError 異常昵宇。
  • 本地方法棧
    為虛擬機(jī)使用到的Native方法服務(wù)
    本地方法棧區(qū)域也會(huì)拋出StackOverflowError 和OutOfMemoryError異常磅崭。
  • 方法區(qū)
    用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量瓦哎、靜態(tài)變量砸喻、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時(shí)蒋譬,將拋出OutOfMemoryError 異常割岛。

  • 存放對象實(shí)例,如果在堆中沒有內(nèi)存完成實(shí)例分配犯助,并且堆也無法再擴(kuò)展時(shí)癣漆,將會(huì)拋出OutOfMemoryError 異常。

參考 https://www.cnblogs.com/dingyingsi/p/3760447.html

在知道了class字節(jié)碼文件結(jié)構(gòu)和JVM內(nèi)存模型后剂买,需要一個(gè)過程將字節(jié)碼文件加載到內(nèi)存惠爽。

類加載器

負(fù)責(zé)將字節(jié)碼文件載入到內(nèi)存,

BootstrapClassLoader – JRE/lib/rt.jar
ExtensionClassLoader – JRE/lib/ext或者java.ext.dirs指向的目錄
ApplicationClassLoader – CLASSPATH環(huán)境變量, 由-classpath或-cp選項(xiàng)定義,或者是JAR中的Manifest的classpath屬性定義

從上至下依次為父子關(guān)系瞬哼,并不是繼承關(guān)系婚肆。

機(jī)制

  • 委托機(jī)制
    當(dāng)加載B.class時(shí),請求首先發(fā)到ApplicationClassLoader坐慰,ApplicationClassLoader看都不看就交給父親ExtensionClassLoader较性,ExtensionClassLoader也是看都不看就交給父親BootstrapClassLoader,BootstrapClassLoader為始祖了,于是在自己的管轄區(qū)內(nèi)查找看有沒有B.class赞咙,有就加載沒有就告訴兒子ExtensionClassLoader永毅,你自己處理,ExtensionClassLoader收到父親的信息后在自己的管轄區(qū)內(nèi)查找B.class人弓,有就加載沒有就告訴兒子ApplicationClassLoader,你自己處理着逐,ApplicationClassLoader收到父親的信息后在自己的管轄區(qū)內(nèi)查找B.class崔赌,有就加載沒有就ClassNotFoundException。

  • 可見性機(jī)制
    子類加載器可以看到父類加載器加載的類

  • 單一性機(jī)制
    因委托機(jī)制的關(guān)系耸别,一個(gè)類(唯一的全限定名)只能被一個(gè)類加載器加載一次

加載方式

  • 顯式加載
    通過class.forname()等方法健芭,顯式加載需要的類

  • 隱式加載
    程序在運(yùn)行過程中當(dāng)碰到通過new等方式生成對象時(shí),隱式調(diào)用類裝載器加載對應(yīng)的類到j(luò)vm中

自定義類加載器

以上三種類加載器秀姐,在某些場景下就不適用慈迈。由于以上三種類加載器都是加載指定位置的class,當(dāng)加載異地加密的class時(shí)就無法使用省有,此時(shí)需要自定義類加載器痒留,加載指定位置的class到內(nèi)存并在執(zhí)行解密后使用。

有了class字節(jié)碼文件結(jié)構(gòu)蠢沿、JVM內(nèi)存模型和類加載器這三個(gè)部分的初識(shí)伸头,接著就是三個(gè)獨(dú)立部分合作的場景:類加載過程

類加載過程

類加載器將字節(jié)碼文件載入JVM內(nèi)存的過程

  • 加載
    主要是獲取定義此類的二進(jìn)制字節(jié)流,并將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)舷蟀,最后在Java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對象作為方法區(qū)這些數(shù)據(jù)的訪問入口恤磷。
    類加載器參與的階段。

  • 驗(yàn)證
    確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求野宜,并且不會(huì)危害虛擬機(jī)自身的安全扫步。主要驗(yàn)證過程包括:文件格式驗(yàn)證(魔數(shù)、版本號(hào))匈子,元數(shù)據(jù)驗(yàn)證(類關(guān)系)河胎,字節(jié)碼驗(yàn)證(數(shù)據(jù)流、控制流)以及符號(hào)引用驗(yàn)證(引用可達(dá))旬牲。

  • 準(zhǔn)備
    正式為類變量(static變量)分配內(nèi)存并設(shè)置類變量初始值的階段仿粹。關(guān)于準(zhǔn)備階段為類變量設(shè)置零值的唯一例外就是當(dāng)這個(gè)類變量同時(shí)也被final修飾,那么在編譯時(shí)原茅,就會(huì)直接為這個(gè)常量賦上目標(biāo)值吭历。

  • 解析
    解析時(shí)虛擬機(jī)將常量池中的符號(hào)引用替換為直接引用。

  • 初始化
    初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過程擂橘。類構(gòu)造器<clinit>()方法是由編譯器自動(dòng)收藏類中的所有類變量的賦值動(dòng)作和靜態(tài)語句塊(static塊)中的語句合并產(chǎn)生晌区。
    當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化,則需要先觸發(fā)其父類的初始化。
    虛擬機(jī)會(huì)保證一個(gè)類的<clinit>()方法在多線程環(huán)境中被正確加鎖和同步敬飒。

參考 https://www.cnblogs.com/dooor/p/5289994.html

一個(gè)項(xiàng)目径簿、jar包、war包中有數(shù)百成千上萬的字節(jié)碼文件灾馒,一個(gè)字節(jié)碼文件只能被加載一次(類加載器的委托機(jī)制),JVM是一次性加載全部文件的嗎遣总?肯定不是睬罗,具體的實(shí)現(xiàn)由不同的JVM自由發(fā)揮,但對于初始化階段JVM有明確要求(被引用)旭斥,自然初始化之前的階段也必須完成容达。

類的引用方式

主動(dòng)引用

1、當(dāng)使用new關(guān)鍵字實(shí)例化對象時(shí)垂券,當(dāng)讀取或者設(shè)置一個(gè)類的靜態(tài)字段(被final修飾的除外)時(shí)花盐,以及當(dāng)調(diào)用一個(gè)類的靜態(tài)方法時(shí)(比如構(gòu)造方法就是靜態(tài)方法),如果類未初始化菇爪,則需先初始化算芯。
2、通過反射機(jī)制對類進(jìn)行調(diào)用時(shí)凳宙,如果類未初始化也祠,則需先初始化。
3近速、當(dāng)初始化一個(gè)類時(shí)诈嘿,如果其父類未初始化,先初始化父類削葱。
4奖亚、用戶指定的執(zhí)行主類(含main方法的那個(gè)類)在虛擬機(jī)啟動(dòng)時(shí)會(huì)先被初始化。

被動(dòng)引用

除了上面這4種方式析砸,所有引用類的方式都不會(huì)觸發(fā)初始化昔字,稱為被動(dòng)引用。如:
通過子類引用父類的靜態(tài)字段首繁,不會(huì)導(dǎo)致子類初始化作郭;
通過數(shù)組定義來引用類,不會(huì)觸發(fā)此類的初始化弦疮;
引用類的靜態(tài)常量不會(huì)觸發(fā)定義常量的類的初始化夹攒,因?yàn)槌A吭诰幾g階段已經(jīng)被放到常量池中了。

參考 https://blog.csdn.net/zcxwww/article/details/51330327

初始化的對象會(huì)被放在什么地方呢胁塞?

內(nèi)存分配策略

兩個(gè)存儲(chǔ)位置:本地線程緩存TLAB和堆

新對象產(chǎn)生時(shí)首先檢查本地線程是否開啟了緩存咏尝,是則存儲(chǔ)在TLAB压语,否則去堆中尋找位置。

堆又分了:Eden编检、兩個(gè)Survivor胎食、Tenured共4個(gè)區(qū),Eden與Survivor大小比是8:1允懂,Eden和Survivor稱為新生代厕怜,Tenured稱為老年代(JDK8已經(jīng)沒有持久代了)

當(dāng)新對象產(chǎn)生時(shí),存放在Eden蕾总,當(dāng)Eden放不下時(shí)觸發(fā)Minor GC酣倾,將Eden中存活的對象復(fù)制到一Survivor中。繼續(xù)存放對象到Eden谤专,當(dāng)Eden放不下時(shí)觸發(fā)Minor GC,將Eden和非空閑Survivor中存活的對象復(fù)制到空閑Survivor中午绳,往復(fù)操作置侍。每經(jīng)過一次Minor GC,對象的年齡加1拦焚,當(dāng)對象年齡達(dá)到閥值(默認(rèn)15)進(jìn)入Tenured蜡坊。如果在Minor GC期間發(fā)現(xiàn)存活對象無法放入空閑的Survivor區(qū),則會(huì)通過空間分配擔(dān)保機(jī)制使對象提前進(jìn)入Tenured赎败。如果在Survivor空間中的相同年齡的所有對象大小的總和大于Survivor空間的一半秕衙,年齡大于和等于該年的對象就可以直接進(jìn)入老年代,無需等到指定的閥值僵刮。

空間分配擔(dān)保機(jī)制:
在執(zhí)行Minor GC前, VM會(huì)首先檢查Tenured是否有足夠的空間存放新生代尚存活對象据忘,由于新生代使用復(fù)制收集算法,為了提升內(nèi)存利用率搞糕,只使用了其中一個(gè)Survivor作為輪換備份勇吊,因此當(dāng)出現(xiàn)大量對象在Minor GC后仍然存活的情況時(shí),就需要老年代進(jìn)行分配擔(dān)保窍仰,讓Survivor無法容納的對象直接進(jìn)入老年代汉规,但前提是老年代需要有足夠的空間容納這些存活對象。但存活對象的大小在實(shí)際完成GC前是無法明確知道的驹吮,因此Minor GC前针史,VM會(huì)先首先檢查老年代連續(xù)空間是否大于新生代對象總大小或歷次晉升的平均大小,如果條件成立, 則進(jìn)行Minor GC碟狞,否則進(jìn)行Full GC(讓老年代騰出更多空間)啄枕。然而取歷次晉升的對象的平均大小也是有一定風(fēng)險(xiǎn)的,如果某次Minor GC存活后的對象突增族沃,遠(yuǎn)遠(yuǎn)高于平均值的話射亏,依然可能導(dǎo)致?lián)J?Handle Promotion Failure近忙,老年代也無法存放這些對象了),此時(shí)就只好在失敗后重新發(fā)起一次Full GC(讓老年代騰出更多空間)智润。

分代的唯一理由就是優(yōu)化GC性能及舍,讓GC在固定區(qū)域工作。

GC

  • Minor GC
    在年輕代(Eden和Survivor)中執(zhí)行的GC
  • Major GC
    在老年代(Tenured)中執(zhí)行的GC
  • Full GC
    清理整個(gè)堆空間包括年輕代和老年代

垃圾回收最重要的一點(diǎn)是如何判斷對象為垃圾窟绷?

可達(dá)性分析算法

通過一系列稱為GC Roots的對象作為起點(diǎn)锯玛,然后向下搜索,搜索所走過的路徑稱為引用鏈/Reference Chain兼蜈,當(dāng)一個(gè)對象到GC Roots沒有任何引用鏈相連時(shí)攘残,即該對象不可達(dá),也就說明此對象是不可用的为狸,如圖:Object5歼郭、6、7雖然互有關(guān)聯(lián)辐棒,但它們到GC Roots是不可達(dá)的病曾,因此也會(huì)被判定為可回收的對象。

回收時(shí)的回收算法:

分代

根據(jù)對象存活周期的不同將內(nèi)存劃分為幾塊漾根,如JVM中的新生代泰涂、老年代,這樣就可以根據(jù)各年代特點(diǎn)分別采用最適當(dāng)?shù)腉C算法:

在新生代:每次垃圾收集都能發(fā)現(xiàn)大批對象已死辐怕,只有少量存活逼蒙。因此選用復(fù)制算法,只需要付出少量存活對象的復(fù)制成本就可以完成收集寄疏。

在老年代:因?yàn)閷ο蟠婊盥矢呤抢巍]有額外空間對它進(jìn)行分配擔(dān)保,就必須采用“標(biāo)記—清理”或“標(biāo)記—整理”算法來進(jìn)行回收陕截,不必進(jìn)行內(nèi)存復(fù)制妖泄,且直接騰出空閑內(nèi)存。

新生代-復(fù)制算法

該算法的核心是將可用內(nèi)存按容量劃分為大小相等的兩塊艘策,每次只用其中一塊蹈胡,當(dāng)這一塊的內(nèi)存用完,就將還存活的對象復(fù)制到另外一塊上面朋蔫,然后把已使用過的內(nèi)存空間一次清理掉罚渐。

這使得每次只對其中一塊內(nèi)存進(jìn)行回收,分配也就不用考慮內(nèi)存碎片等復(fù)雜情況驯妄,實(shí)現(xiàn)簡單且運(yùn)行高效荷并。

但由于新生代中的98%的對象都是生存周期極短的,因此并不需完全按照1:1的比例劃分新生代空間青扔,所以新生代劃分為一塊較大的Eden區(qū)和兩塊較小的Survivor區(qū)源织。

老年代-標(biāo)記清除算法

該算法分為“標(biāo)記”和“清除”兩個(gè)階段:首先標(biāo)記出所有需要回收的對象(可達(dá)性分析)翩伪, 在標(biāo)記完成后統(tǒng)一清理掉所有被標(biāo)記的對象。

該算法會(huì)有以下兩個(gè)問題:
1谈息、效率問題:標(biāo)記和清除過程的效率都不高缘屹;
2、空間問題:標(biāo)記清除后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片侠仇,空間碎片太多可能會(huì)導(dǎo)致在運(yùn)行過程中需要分配較大對象時(shí)無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集轻姿。

老年代-標(biāo)記整理算法

標(biāo)記清除算法會(huì)產(chǎn)生內(nèi)存碎片問題,而復(fù)制算法需要有額外的內(nèi)存擔(dān)甭叽叮空間互亮,于是針對老年代的特點(diǎn),又有了標(biāo)記整理算法余素。標(biāo)記整理算法的標(biāo)記過程與標(biāo)記清除算法相同豹休,但后續(xù)步驟不再對可回收對象直接清理,而是讓所有存活的對象都向一端移動(dòng)桨吊,然后清理掉端邊界以外的內(nèi)存威根。

分區(qū)

將整個(gè)堆空間劃分為連續(xù)的不同小區(qū)間,每個(gè)小區(qū)間獨(dú)立使用屏积,獨(dú)立回收。這樣做的好處是可以控制一次回收多少個(gè)小區(qū)間磅甩。

在相同條件下炊林,堆空間越大,一次GC耗時(shí)就越長卷要,從而產(chǎn)生的停頓也越長渣聚。為了更好地控制GC產(chǎn)生的停頓時(shí)間,將一塊大的內(nèi)存區(qū)域分割為多個(gè)小塊僧叉,根據(jù)目標(biāo)停頓時(shí)間奕枝,每次合理地回收若干個(gè)小區(qū)間(而不是整個(gè)堆),從而減少一次GC所產(chǎn)生的停頓瓶堕。

參考 http://www.importnew.com/23035.html

對象逃逸(點(diǎn)到為止)

本該銷毀的對象隘道,逃到了它處。

public class A {
    public static Object obj;
    public void globalVariableEscape() {  // 給全局變量賦值郎笆,發(fā)生逃逸
        obj = new Object();//new的對象本該在棧幀出棧時(shí)銷毀谭梗,但被外部static引用導(dǎo)致進(jìn)入方法區(qū)常量池
    }
    public Object methodEscape() {  // 方法返回值,發(fā)生逃逸
        return new Object();//new的對象本該在棧幀出棧時(shí)銷毀宛蚓,但被外部方法或線程引用激捏,導(dǎo)致對象只能在外部方法棧幀出棧或線程銷毀時(shí)被清理
    }
    public void instanceEscape() {  // 實(shí)例引用發(fā)生逃逸
        b = new B(this); //(示意而已凄吏,并不準(zhǔn)確)新建B對象時(shí)引用了A對象远舅,除B使用外闰蛔,其余無引用A,此時(shí)本可以回收A图柏,但B卻引用導(dǎo)致無法回收序六。循環(huán)引用就是A在引用B,導(dǎo)致互相引用都不能被回收爆办。
    }
}

public class B {
    public static Object obj;
    public void instance(A a) {  // 引用傳入的實(shí)例
        obj = a;
    }
}

對象的引用類型(強(qiáng)軟弱虛)

JVM中真正將一個(gè)對象判死刑至少需要經(jīng)歷兩次標(biāo)記過程:

第一個(gè)過程难咕,可達(dá)性分析算法
第二個(gè)過程,判斷這個(gè)對象是否需要執(zhí)行finalize()方法距辆。

第一次GC時(shí)余佃,對象在經(jīng)歷了可達(dá)性分析算法被標(biāo)記后,若對象重寫了finalize()方法且沒被執(zhí)行過則會(huì)被放入F-Queue隊(duì)列中跨算,否則回收爆土。
第二次GC時(shí),JVM會(huì)有一個(gè)優(yōu)先級(jí)比較低的線程去執(zhí)行隊(duì)列中對象的finalize()方法诸蚕,執(zhí)行只是觸發(fā)finalize()方法并不會(huì)確保方法一定完成步势,防止死循環(huán)或異常等情況導(dǎo)致對象不可被回收,這時(shí)第二次標(biāo)記完成對象被回收背犯。

只有當(dāng)對象存在引用鏈連接GC Roots時(shí)才確保不會(huì)被回收坏瘩,即對象為強(qiáng)引用。那么有些對象漠魏,我們希望在內(nèi)存充足的情況下不要回收倔矾,在內(nèi)存不足的時(shí)候再將其回收掉。如果只有強(qiáng)引用柱锹,那這個(gè)對象永遠(yuǎn)都不會(huì)被回收哪自。于是有了軟引用、弱引用禁熏、虛引用的概念壤巷。

  • 強(qiáng)引用
    即使OOM也不會(huì)被回收
  • 軟引用
    內(nèi)存不足時(shí)才會(huì)被回收
  • 弱引用
    只要GC就會(huì)被回收
  • 虛引用
    唯一的作用就是監(jiān)聽被回收

參考 https://blog.csdn.net/huachao1001/article/details/51547290

類卸載

卸載需要滿足三個(gè)條件:
1、該類所有的實(shí)例已經(jīng)被回收
2瞧毙、加載該類的ClassLoder已經(jīng)被回收
3胧华、該類對應(yīng)的java.lang.Class對象沒有被引用

JVM自帶的根類加載器、擴(kuò)展類加載器和系統(tǒng)類加載器宙彪,JVM本身會(huì)始終引用這些類加載器撑柔,因此條件2不會(huì)形成。

而這些類加載器則會(huì)始終引用它們所加載的類對象您访,因此條件3也不會(huì)形成铅忿。

唯一會(huì)被卸載的類只有自定義的類加載器加載的類。

Dalvik(DVM)

為什么大篇幅講JVM灵汪,因?yàn)镈alvik虛擬機(jī)是Google按照J(rèn)VM虛擬機(jī)規(guī)范定制的虛擬機(jī)檀训,所應(yīng)用的多是處理能力柑潦、內(nèi)存、和存儲(chǔ)等處理能力受限的設(shè)備峻凫,更符合移動(dòng)設(shè)備的環(huán)境要求渗鬼。

架構(gòu)

JVM基于棧架構(gòu),Dalvik基于寄存器架構(gòu)荧琼,因此讀寫速度較快

空間

可執(zhí)行程序的字節(jié)碼不同
JVM:java -> class -> jar
DVM:java -> class -> dex -> apk

jar由多個(gè)class構(gòu)成譬胎,而dex是由多個(gè)class合并構(gòu)成,消除了數(shù)據(jù)冗余節(jié)省了空間命锄,但合并后方法數(shù)變多堰乔,產(chǎn)生了方法數(shù)受限(65535)的問題。

沙盒

Dalvik虛擬機(jī)允許在內(nèi)存中創(chuàng)建多個(gè)實(shí)例脐恩,以隔離不同的應(yīng)用程序镐侯。這樣,當(dāng)一個(gè)應(yīng)用程序在自己的進(jìn)程中崩潰后驶冒,不會(huì)影響其它進(jìn)程的運(yùn)行苟翻。

ART

在Dalvik下,應(yīng)用每次運(yùn)行都需要通過即時(shí)編譯器(JIT)將字節(jié)碼轉(zhuǎn)換為機(jī)器碼骗污,即每次都要編譯加運(yùn)行崇猫,所以啟動(dòng)時(shí)間長。

ART則在應(yīng)用安裝時(shí)就預(yù)編譯字節(jié)碼到機(jī)器語言需忿,所以安裝過程較Dalvik時(shí)間長存儲(chǔ)空間占用更大诅炉,但應(yīng)用每次運(yùn)行時(shí)不需要編譯減少了CPU的負(fù)擔(dān),啟動(dòng)時(shí)間短贴谎。

補(bǔ)充內(nèi)容

JVM運(yùn)行參數(shù)

JVM參數(shù)
-Xms 初始堆內(nèi)存大小
-Xmx 最大堆內(nèi)存大小
-Xmn 新生代大小
-Xss 每個(gè)線程的堆棧大小
-XX:PermSize 非堆內(nèi)存汞扎,永久代大屑疚取(>256M)
-XX:MaxPermSize 非堆內(nèi)存擅这,永久代最大大小
-XX:SurvivorRatio Eden: Survivor大小
-XX:PretenureSizeThreshold 手動(dòng)設(shè)定大對象的標(biāo)準(zhǔn)
-XX:MaxTenuringThreshold 設(shè)置年齡閥值
-XX:+HandlePromotionFailure +開啟-禁用空間分配擔(dān)保
打印GC信息
-verbose:gc
-XX:+PrintGCDetails
設(shè)置使用的垃圾回收器,不同場景下適合不同的垃圾回收器
-XX:+UseSerialGC
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC

JVM監(jiān)控

JDK自帶工具有

  • jps:java process status Java進(jìn)程狀態(tài)
    jps -l 運(yùn)行主類全名
    jps -m main所接收參數(shù)
    jps -v jvm所接收參數(shù)

  • jstat:監(jiān)控運(yùn)行程序的各項(xiàng)信息景鼠,參數(shù)很多自行百度

  • jinfo:查看調(diào)整虛擬機(jī)各項(xiàng)參數(shù)

  • jmap:堆信息工具

  • jhat:JVM Heap Analysis Tool

  • jstack:用來生成線程快照

  • jconsole:圖形界面的監(jiān)控工具

visualVM:功能最強(qiáng)大的工具仲翎,需要單獨(dú)下載

Tip

類加載過程中:加載與驗(yàn)證是并行執(zhí)行的

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市铛漓,隨后出現(xiàn)的幾起案子溯香,更是在濱河造成了極大的恐慌,老刑警劉巖浓恶,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件玫坛,死亡現(xiàn)場離奇詭異,居然都是意外死亡包晰,警方通過查閱死者的電腦和手機(jī)湿镀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門炕吸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人勉痴,你說我怎么就攤上這事赫模。” “怎么了蒸矛?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵瀑罗,是天一觀的道長。 經(jīng)常有香客問我雏掠,道長斩祭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任磁玉,我火速辦了婚禮停忿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蚊伞。我一直安慰自己席赂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布时迫。 她就那樣靜靜地躺著颅停,像睡著了一般。 火紅的嫁衣襯著肌膚如雪掠拳。 梳的紋絲不亂的頭發(fā)上癞揉,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音溺欧,去河邊找鬼喊熟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛姐刁,可吹牛的內(nèi)容都是我干的芥牌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼聂使,長吁一口氣:“原來是場噩夢啊……” “哼壁拉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起柏靶,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤弃理,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后屎蜓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痘昌,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辆苔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笔诵。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖姑子,靈堂內(nèi)的尸體忽然破棺而出乎婿,到底是詐尸還是另有隱情,我是刑警寧澤街佑,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布谢翎,位于F島的核電站,受9級(jí)特大地震影響沐旨,放射性物質(zhì)發(fā)生泄漏森逮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一磁携、第九天 我趴在偏房一處隱蔽的房頂上張望褒侧。 院中可真熱鬧,春花似錦谊迄、人聲如沸闷供。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽歪脏。三九已至,卻和暖如春粮呢,著一層夾襖步出監(jiān)牢的瞬間婿失,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工啄寡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留豪硅,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓挺物,卻偏偏與公主長得像懒浮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子姻乓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 這篇文章是我之前翻閱了不少的書籍以及從網(wǎng)絡(luò)上收集的一些資料的整理嵌溢,因此不免有一些不準(zhǔn)確的地方眯牧,同時(shí)不同JDK版本的...
    高廣超閱讀 15,603評(píng)論 3 83
  • 介紹JVM中7個(gè)區(qū)域蹋岩,然后把每個(gè)區(qū)域可能造成內(nèi)存的溢出的情況說明 程序計(jì)數(shù)器:看做當(dāng)前線程所執(zhí)行的字節(jié)碼行號(hào)指示器...
    jemmm閱讀 2,229評(píng)論 0 9
  • Java 虛擬機(jī)(Java virtual machine,JVM)是運(yùn)行 Java 程序必不可少的機(jī)制学少。JVM實(shí)...
    Rick617閱讀 864評(píng)論 0 0
  • 工作之余剪个,想總結(jié)一下JVM相關(guān)知識(shí)。 Java運(yùn)行時(shí)數(shù)據(jù)區(qū): Java虛擬機(jī)在執(zhí)行Java程序的過程中會(huì)將其管理的...
    Huang遠(yuǎn)閱讀 635評(píng)論 0 2
  • Java8張圖 11版确、字符串不變性 12扣囊、equals()方法乎折、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,707評(píng)論 0 11