##《java虛擬機(jī)》匯總所有關(guān)鍵要點(diǎn)

一 绢陌、java虛擬機(jī)底層結(jié)構(gòu)詳解

我們知道凛剥,一個JVM實例的行為不光是它自己的事噪猾,還涉及到它的子系統(tǒng)地消、存儲區(qū)域、數(shù)據(jù)類型和指令這些部分畏妖,它們描述了JVM的一個抽象的內(nèi)部體系結(jié)構(gòu)脉执,其目的不光規(guī)定實現(xiàn)JVM時它內(nèi)部的體系結(jié)構(gòu),更重要的是提供了一種方式戒劫,用于嚴(yán)格定義實現(xiàn)時的外部行為半夷。每個JVM都有兩種機(jī)制婆廊,一個是裝載具有合適名稱的類(類或是接口),叫做類裝載子系統(tǒng)巫橄;另外的一個負(fù)責(zé)執(zhí)行包含在已裝載的類或接口中的指令淘邻,叫做運(yùn)行引擎。每個JVM又包括方法區(qū)湘换、堆宾舅、Java棧、程序計數(shù)器和本地方法棧這五個部分彩倚,這幾個部分和類裝載機(jī)制與運(yùn)行引擎機(jī)制一起組成的體系結(jié)構(gòu)圖為: JVM的每個實例都有一個它自己的方法域和一個堆筹我,運(yùn)行于JVM內(nèi)的所有的線程都共享這些區(qū)域;當(dāng)虛擬機(jī)裝載類文件的時候帆离,它解析其中的二進(jìn)制數(shù)據(jù)所包含的類信息蔬蕊,并把它們放到方法域中;當(dāng)程序運(yùn)行的時候哥谷,JVM把程序初始化的所有對象置于堆上岸夯;而每個線程創(chuàng)建的時候,都會擁有自己的程序計數(shù)器和Java棧们妥,其中程序計數(shù)器中的值指向下一條即將被執(zhí)行的指令猜扮,線程的Java棧則存儲為該線程調(diào)用Java方法的狀態(tài);本地方法調(diào)用的狀態(tài)被存儲在本地方法棧监婶,該方法棧依賴于具體的實現(xiàn)破镰。
下面分別對這幾個部分進(jìn)行說明。
執(zhí)行引擎處于JVM的核心位置压储,在Java虛擬機(jī)規(guī)范中鲜漩,它的行為是由指令集所決定的。盡管對于每條指令集惋,規(guī)范很詳細(xì)地說明了當(dāng)JVM執(zhí)行字節(jié)碼遇到指令時孕似,它的實現(xiàn)應(yīng)該做什么,但對于怎么做卻言之甚少刮刑。Java虛擬機(jī)支持大約248個字節(jié)碼喉祭。每個字節(jié)碼執(zhí)行一種基本的CPU運(yùn)算,例如,把一個整數(shù)加到寄存器,子程序轉(zhuǎn)移等。Java指令集相當(dāng)于Java程序的匯編語言雷绢。
Java指令集中的指令包含一個單字節(jié)的操作符,用于指定要執(zhí)行的操作,還有0個或多個操作數(shù),提供操作所需的參數(shù)或數(shù)據(jù)泛烙。許多指令沒有操作數(shù),僅由一個單字節(jié)的操作符構(gòu)成。

[圖片上傳中翘紊。蔽氨。。(2)]

JVM寄存器
pc: Java程序計數(shù)器;
optop: 指向操作數(shù)棧頂端的指針鹉究;
frame: 指向當(dāng)前執(zhí)行方法的執(zhí)行環(huán)境的指針宇立;。
vars: 指向當(dāng)前執(zhí)行方法的局部變量區(qū)第一個變量的指針自赔。

JVM棧結(jié)構(gòu)
局部變量區(qū)
每個Java方法使用一個固定大小的局部變量集妈嘹。它們按照與vars寄存器的字偏移量來尋址。局部變量都是32位的绍妨。長整數(shù)和雙精度浮點(diǎn)數(shù)占據(jù)了兩個局部變量的空間,卻按照第一個局部變量的索引來尋址润脸。(例如,一個具有索引n的局部變量,如果是一個雙精度浮點(diǎn)數(shù),那么它實際占據(jù)了索引n和n+1所代表的存儲空間)虛擬機(jī)規(guī)范并不要求在局部變量中的64位的值是64位對齊的。虛擬機(jī)提供了把局部變量中的值裝載到操作數(shù)棧的指令,也提供了把操作數(shù)棧中的值寫入局部變量的指令他去。

運(yùn)行環(huán)境區(qū)

在運(yùn)行環(huán)境中包含的信息用于動態(tài)鏈接,正常的方法返回以及異常捕捉毙驯。

操作數(shù)棧區(qū)

機(jī)器指令只從操作數(shù)棧中取操作數(shù),對它們進(jìn)行操作,并把結(jié)果返回到棧中

例子展示

上面對虛擬機(jī)的各個部分進(jìn)行了比較詳細(xì)的說明,下面通過一個具體的例子來分析它的運(yùn)行過程孤页。
虛擬機(jī)通過調(diào)用某個指定類的方法main啟動尔苦,傳遞給main一個字符串?dāng)?shù)組參數(shù)涩馆,使指定的類被裝載行施,同時鏈接該類所使用的其它的類型,并且初始化它們魂那。例如對于程序:

class HelloApp

{ public static void main(String[] args) { System.out.println("Hello World!"); for (int i = 0; i < args.length; i++ ) { System.out.println(args[i]); } }}

編譯后在命令行模式下鍵入: Java HelloApp run virtual machine
將通過調(diào)用HelloApp的方法main來啟動java虛擬機(jī)蛾号,傳遞給main一個包含三個字符串"run"、"virtual"鲜结、"machine"的數(shù)組∨剩現(xiàn)在我們略述虛擬機(jī)在執(zhí)行HelloApp時可能采取的步驟。
開始試圖執(zhí)行類HelloApp的main方法,發(fā)現(xiàn)該類并沒有被裝載壤圃,也就是說虛擬機(jī)當(dāng)前不包含該類的二進(jìn)制代表,于是虛擬機(jī)使用ClassLoader試圖尋找這樣的二進(jìn)制代表憋沿。如果這個進(jìn)程失敗悯舟,則拋出一個異常。類被裝載后同時在main方法被調(diào)用之前,必須對類HelloApp與其它類型進(jìn)行鏈接然后初始化程癌。鏈接包含三個階段:檢驗,準(zhǔn)備和解析。檢驗檢查被裝載的主類的符號和語義,準(zhǔn)備則創(chuàng)建類或接口的靜態(tài)域以及把這些域初始化為標(biāo)準(zhǔn)的默認(rèn)值筐赔,解析負(fù)責(zé)檢查主類對其它類或接口的符號引用贿肩,在這一步它是可選的茬射。類的初始化是對類中聲明的靜態(tài)初始化函數(shù)和靜態(tài)域的初始化構(gòu)造方法的執(zhí)行萧恕。一個類在初始化之前它的父類必須被初始化刚梭。整個過程如下:
[圖片上傳中。票唆。朴读。(3)]
二、類的生命周期(上)類的加載和連接

類加載器走趋,顧名思義衅金,類加載器(class loader)用來加載 Java 類到 Java 虛擬機(jī)中。一般來說,Java 虛擬機(jī)使用 Java 類的方式如下:Java 源程序(.java 文件)在經(jīng)過 Java 編譯器編譯之后就被轉(zhuǎn)換成 Java 字節(jié)代碼(.class 文件)氮唯。類加載器負(fù)責(zé)讀取 Java 字節(jié)代碼鉴吹,并轉(zhuǎn)換成 java.lang.Class類的一個實例。每個這樣的實例用來表示一個 Java 類惩琉。通過此實例的 newInstance()方法就可以創(chuàng)建出該類的一個對象豆励。實際的情況可能更加復(fù)雜,比如 Java 字節(jié)代碼可能是通過工具動態(tài)生成的瞒渠,也可能是通過網(wǎng)絡(luò)下載的肆糕。基本上所有的類加載器都是 java.lang.ClassLoader類的一個實例在孝。其實我們研究類加載器主要研究的就是類的生命周期
首先來了解一下jvm(java虛擬機(jī))中的幾個比較重要的內(nèi)存區(qū)域诚啃,這幾個區(qū)域在java類的生命周期中扮演著比較重要的角色:
方法區(qū):在java的虛擬機(jī)中有一塊專門用來存放已經(jīng)加載的類信息、常量私沮、靜態(tài)變量以及方法代碼的內(nèi)存區(qū)域始赎,叫做方法區(qū)。

常量池:常量池是方法區(qū)的一部分仔燕,主要用來存放常量和類中的符號引用等信息造垛。
堆區(qū): 用于存放類的對象實例。
棧區(qū): 也叫java虛擬機(jī)棧晰搀,是由一個一個的棧幀組成的后進(jìn)先出的棧式結(jié)構(gòu)五辽,棧楨中存放方法運(yùn)行時產(chǎn)生的局部變量、方法出口等信息外恕。當(dāng)調(diào)用一個方法時杆逗,虛擬機(jī)棧中就會創(chuàng)建一個棧幀存放這些數(shù)據(jù),當(dāng)方法調(diào)用完成時鳞疲,棧幀消失罪郊,如果方法中調(diào)用了其他方法,則繼續(xù)在棧頂創(chuàng)建新的棧楨尚洽。類的生命周期
當(dāng)我們編寫一個java的源文件后悔橄,經(jīng)過編譯會生成一個后綴名為class的文件,這種文件叫做字節(jié)碼文件腺毫,只有這種字節(jié)碼文件才能夠在java虛擬機(jī)中運(yùn)行癣疟,java類的生命周期就是指一個class文件從加載到卸載的全過程。一個java類的完整的生命周期會經(jīng)歷加載潮酒、連接睛挚、初始化、使用澈灼、和卸載五個階段竞川,當(dāng)然也有在加載或者連接之后沒有被初始化就直接被使用的情況店溢,這里我們主要來研究類加載器所執(zhí)行的部分,也就是加載委乌,鏈接和初始化床牧。如圖所示:

[圖片上傳中。遭贸。戈咳。(5)][圖片上傳中。壕吹。著蛙。(6)]下面我先簡單看一下類加載器所執(zhí)行的三部分的簡單介紹1、加載:查找并加載類的二進(jìn)制數(shù)據(jù)
2耳贬、連接
–驗證:確保被加載的類的正確性
–準(zhǔn)備:為類的靜態(tài)變量分配內(nèi)存踏堡,并將其初始化為默認(rèn)值
–解析:把類中的符號引用轉(zhuǎn)換為直接引用
3、初始化:為類的靜態(tài)變量賦予正確的初始值
從上邊我們可以看出類的靜態(tài)變量賦了兩回值咒劲。這是為什么呢顷蟆?原因是,在連接過程中時為靜態(tài)變量賦值為默認(rèn)值,也就是說,只要是你定義了靜態(tài)變量鸟款,不管你開始給沒給它設(shè)置,我系統(tǒng)都為他初始化一個默認(rèn)值削樊。到了初始化過程,系統(tǒng)就檢查是否用戶定義靜態(tài)變量時有沒有給設(shè)置初始化值兔毒,如果有就把靜態(tài)變量設(shè)置為用戶自己設(shè)置的初始化值漫贞,如果沒有還是讓靜態(tài)變量為初始化值
類的加載、連接和初始化

[圖片上傳中眼刃。绕辖。摇肌。(8)]
類的加載
類的加載指的是將類的.class文件中的二進(jìn)制數(shù)據(jù)讀入到內(nèi)存中擂红,將其放在運(yùn)行時數(shù)據(jù)區(qū)的方法區(qū)內(nèi),然后在堆區(qū)創(chuàng)建一個java.lang.Class對象围小,用來封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu) 昵骤。這里的class對象其實就像一面鏡子一樣,外面是類的源程序肯适,里面是class對象变秦,它實時的反應(yīng)了類的數(shù)據(jù)結(jié)構(gòu)和信息。
加載.class文件的方式
1框舔、從本地系統(tǒng)中直接加載
2蹦玫、通過網(wǎng)絡(luò)下載.class文件
3赎婚、從zip,jar等歸檔文件中加載.class文件
4樱溉、從專有數(shù)據(jù)庫中提取.class文件
5挣输、將Java源文件動態(tài)編譯為.class文件
類的加載過程
[圖片上傳中。福贞。撩嚼。(9)]

結(jié)論:
1、類的加載的最終產(chǎn)品是位于堆區(qū)中的Class對象
2挖帘、Class對象封裝了類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)完丽,并且向Java程序員提供了訪問方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)的接口
Java虛擬機(jī)給我們提供了兩種類加載器:
1、Java虛擬機(jī)自帶的加載器
1)根類加載器(使用C++編寫拇舀,程序員無法在Java代碼中獲得該類)
2)擴(kuò)展加載器逻族,使用Java代碼實現(xiàn)
3)系統(tǒng)加載器(應(yīng)用加載器),使用Java代碼實現(xiàn)
2骄崩、用戶自定義的類加載器
java.lang.ClassLoader的子類
用戶可以定制類的加載方式

我們看一下API對ClassLoader的介紹:
類加載器是負(fù)責(zé)加載類的對象瓷耙。ClassLoader 類是一個抽象類。如果給定類的二進(jìn)制名稱刁赖,那么類加載器會試圖查找或生成構(gòu)成類定義的數(shù)據(jù)搁痛。一般策略是將名稱轉(zhuǎn)換為某個文件名,然后從文件系統(tǒng)讀取該名稱的“類文件”宇弛。每個class對象都包含一個對定義它的 ClassLoader 的引用鸡典。
我們再來看一下Class類的一個方法getClassLoader
public ClassLoader getClassLoader()
返回該類的類加載器。有些實現(xiàn)可能使用 null 來表示根類加載器枪芒。如果該類由根類加載器加載彻况,則此方法在這類實現(xiàn)中將返回 null。

下面我們來看一個小例子來驗證一下:


[圖片上傳中舅踪。纽甘。。(12)]

看一下打印結(jié)果抽碌,一目了然:


[圖片上傳中悍赢。。货徙。(14)]
從上面打印結(jié)果可以看出,第一個為null蠢棱,也就是它用根類加載器加載的亿鲜,第二個是我們自己寫的類垒探,也就是說构挤,我們自己寫的那個類用sun.misc.Launcher$AppClassLoader@1372a1a加載器加載的呀邢,我們可以看到APP,也就是應(yīng)用類加載器剪验,也就是系統(tǒng)加載器

還記得我們以前用過的動態(tài)代理吧,InvocationHandler灯萍,當(dāng)我們利用proxy對象調(diào)用newProxyInstance建立一個代理類時真屯,我們要給他傳一個ClassLoader凉馆,也就是類加載器京革,如下:
[圖片上傳中。咬扇。甲葬。(15)]

當(dāng)時我們學(xué)習(xí)的時候,只知道這里的loader隨便給他設(shè)置一個類的類加載器就可以⌒负兀現(xiàn)在我們來想想為什么這里需要一個類加載器呢经窖?我們知道這個newProxyInstance是動態(tài)的給我們生成一個代理類,然后根據(jù)這個代理類生成一個代理對象梭灿。動態(tài)生成這個代理類之后我們不得把他加載到內(nèi)存里嗎画侣,加載到內(nèi)存里我們才可以用他。用什么加載到內(nèi)存里胎源,只有類加載器棉钧,所以我們要給他指定一個類加載器。

  類加載器并不需要等到某個類被“首次主動使用”時再加載它 涕蚤。JVM規(guī)范允許類加載器在預(yù)料某個類將要被使用時就預(yù)先加載它宪卿,如果在預(yù)先加載的過程中遇到了.class文件缺失或存在錯誤,類加載器必須在程序首次主動使用該類時才報告錯誤(LinkageError錯誤) 如果這個類一直沒有被程序主動使用万栅,那么類加載器就不會報告錯誤 佑钾。大家在做web開發(fā)的時候有可能會出現(xiàn)這種問題,比如我們在做[測試](http://lib.csdn.net/base/softwaretest)的時候是用的jdk1.6烦粒,而我們在部署的時候我們用的是jdk1.5.這時候就很可能匯報LinkageError錯誤,版本不兼容扰她。

類的連接
類被加載后兽掰,就進(jìn)入連接階段。連接就是將已經(jīng)讀入到內(nèi)存的類的二進(jìn)制數(shù)據(jù)合并到虛擬機(jī)的運(yùn)行時環(huán)境中去徒役。
驗證:當(dāng)一個類被加載之后孽尽,必須要驗證一下這個類是否合法,比如這個類是不是符合字節(jié)碼的格式忧勿、變量與方法是不是有重復(fù)杉女、數(shù)據(jù)類型是不是有效、繼承與實現(xiàn)是否合乎標(biāo)準(zhǔn)等等鸳吸⊙妫總之,這個階段的目的就是保證加載的類是能夠被jvm所運(yùn)行晌砾。很多人都感覺坎拐,既然這個類都通過編譯加載到內(nèi)存里了,那肯定就是合法的了,為什么還要驗證呢廉白,這是因為這里的驗證時為了避免有人惡意編寫class文件个初,也就是說并不是通過編譯得到的class文件乖寒。所以這里驗證其實是檢查的class文件的內(nèi)部結(jié)構(gòu)是否符合字節(jié)碼的要求
準(zhǔn)備:準(zhǔn)備階段的工作就是為類的靜態(tài)變量分配內(nèi)存并設(shè)為jvm默認(rèn)的初值猴蹂,對于非靜態(tài)的變量,則不會為它們分配內(nèi)存楣嘁。有一點(diǎn)需要注意磅轻,這時候,靜態(tài)變量的初值為jvm默認(rèn)的初值逐虚,而不是我們在程序中設(shè)定的初值聋溜。jvm默認(rèn)的初值是這樣的:基本類型(int、long叭爱、short撮躁、char、byte买雾、boolean把曼、float、double)的默認(rèn)值為0漓穿。引用類型的默認(rèn)值為null嗤军。常量的默認(rèn)值為我們程序中設(shè)定的值,比如我們在程序中定義final static int a = 100晃危,則準(zhǔn)備階段中a的初值就是100叙赚。
解析:這一階段的任務(wù)就是把常量池中的符號引用轉(zhuǎn)換為直接引用。那么什么是符號引用僚饭,什么又是直接引用呢震叮?我們來舉個例子:我們要找一個人,我們現(xiàn)有的信息是這個人的身份證號是1234567890鳍鸵。只有這個信息我們顯然找不到這個人苇瓣,但是通過公安局的身份系統(tǒng),我們輸入1234567890這個號之后权纤,就會得到它的全部信息:比如山東省濱州市濱城區(qū)18號張三钓简,通過這個信息我們就能找到這個人了。這里汹想,123456790就好比是一個符號引用外邓,而山東省濱州市濱城區(qū)18號張三就是直接引用。在內(nèi)存中也是一樣古掏,比如我們要在內(nèi)存中找一個類里面的一個叫做show的方法损话,顯然是找不到。但是在解析階段,jvm就會把show這個名字轉(zhuǎn)換為指向方法區(qū)的的一塊內(nèi)存地址丧枪,比如c17164光涂,通過c17164就可以找到show這個方法具體分配在內(nèi)存的哪一個區(qū)域了。這里show就是符號引用拧烦,而c17164就是直接引用忘闻。在解析階段,jvm會將所有的類或接口名恋博、字段名齐佳、方法名轉(zhuǎn)換為具體的內(nèi)存地址。

類的初始化:在類的生命周期執(zhí)行完加載和連接之后就開始了類的初始化债沮。在類的初始化階段炼吴,java虛擬機(jī)執(zhí)行類的初始化語句,為類的靜態(tài)變量賦值疫衩,在程序中硅蹦,類的初始化有兩種途徑:(1)在變量的聲明處賦值。(2)在靜態(tài)代碼塊處賦值闷煤,比如下面的代碼童芹,a就是第一種初始化,b就是第二種初始化


[圖片上傳中曹傀。辐脖。。(18)]

  靜態(tài)變量的聲明和靜態(tài)代碼塊的初始化都可以看做靜態(tài)變量的初始化皆愉,類的靜態(tài)變量的初始化是有順序的嗜价。順序為類文件從上到下進(jìn)行初始化,想到這幕庐,想起來一個很無恥的面試題久锥,分享給大家看一下:

[圖片上傳中。异剥。瑟由。(19)]

大家先看看這里的程序會輸出什么?
不知道大家的答案是什么冤寿,如果不介意的話可以把你的答案寫到評論上歹苦,看看有多少人的答案和你一樣的。我先說說我剛開始的答案吧督怜。我認(rèn)為會輸出:
counter1 = 1
Counter2 = 1
不知道大家的答案是不是這個殴瘦,反正我的是。下面我們來看一下正確答案:



[圖片上傳中号杠。蚪腋。丰歌。(22)]
不知道你做對沒有,反正我剛開始做錯了屉凯。好立帖,現(xiàn)在我來解釋一下為什么會是這個答案。在給出解釋之前悠砚,我們先來看一個概念:
Java程序?qū)︻惖氖褂梅绞娇煞譃閮煞N
主動使用
被動使用
?所有的Java虛擬機(jī)實現(xiàn)必須在每個類或接口被Java程序“首次主動使用”時才初始化他們
主動使用(六種)
–創(chuàng)建類的實例
–訪問某個類或接口的靜態(tài)變量晓勇,或者對該靜態(tài)變量賦值
–調(diào)用類的靜態(tài)方法
–反射(如Class.forName(“com.bzu.csh.Test”))
–初始化一個類的子類
–Java虛擬機(jī)啟動時被標(biāo)明為啟動類的類(Java Test)
OK,我們開始解釋一下上面的答案哩簿,程序開始運(yùn)行宵蕉,首先執(zhí)行main方法酝静,執(zhí)行main方法第一條語句节榜,調(diào)用Singleton類的靜態(tài)方法,這里調(diào)用Singleton類的靜態(tài)方法就是主動使用Singleton類别智。所以開始加載Singleton類宗苍。在加載Singleton類的過程中,首先對靜態(tài)變量賦值為默認(rèn)值薄榛,
Singleton=null
counter1 = 0
Counter2 = 0
給他們賦值完默認(rèn)值值之后讳窟,要進(jìn)行的就是對靜態(tài)變量初始化,對聲明時已經(jīng)賦值的變量進(jìn)行初始化敞恋。我們上面提到過丽啡,初始化是從類文件從上到下賦值的。所以首先給Singleton賦值硬猫,給它賦值补箍,就要執(zhí)行它的構(gòu)造方法,然后執(zhí)行counter1++;counter2++;所以這里的counter1 = 1;counter2 = 1;執(zhí)行完這個初始化之后啸蜜,然后執(zhí)行counter2的初始化坑雅,我們聲明的時候給他初始化為0 了,所以counter2 的值又變?yōu)榱?.初始化完之后執(zhí)行輸出衬横。所以這是的
counter1 = 1
counter2 = 0
類初始化步驟
(1)假如一個類還沒有被加載或者連接裹粤,那就先加載和連接這個類
(2)假如類存在直接的父類,并且這個父類還沒有被初始化蜂林,那就先初始化直接的父類
(3)假如類中存在初始化語句遥诉,那就直接按順序執(zhí)行這些初始化語句
在上邊我們我們說了java虛擬機(jī)實現(xiàn)必須在每個類或接口被Java程序“首次主動使用”時才初始化他們,上面也舉出了六種主動使用的說明噪叙。除了上述六種情形咆爽,其他使用Java類的方式都被看作是被動使用,不會導(dǎo)致類的初始化宿稀。程序中對子類的“主動使用”會導(dǎo)致父類被初始化哈垢;但對父類的“主動”使用并不會導(dǎo)致子類初始化(不可能說生成一個Object類的對象就導(dǎo)致系統(tǒng)中所有的子類都會被初始化)

注:調(diào)用ClassLoader類的loadClass方法加載一個類,并不是對類的主動使用,不會導(dǎo)致類的初始化猫缭。
當(dāng)java虛擬機(jī)初始化一個類時葱弟,要求它的所有的父類都已經(jīng)被初始化,但這條規(guī)則并不適用于接口猜丹。
在初始化一個類時芝加,并不會先初始化它所實現(xiàn)的接口
在初始化一個接口時,并不會先初始化它的父接口
因此射窒,一個父接口并不會因為它的子接口或者實現(xiàn)類的初始化而初始化藏杖。只有當(dāng)程序首次使用特定接口的靜態(tài)變量時,才會導(dǎo)致該接口的初始化脉顿。只有當(dāng)程序訪問的靜態(tài)變量或靜態(tài)方法確實在當(dāng)前類或當(dāng)前接口中定義時蝌麸,才可以認(rèn)為是對類或接口的主動使用 。如果是調(diào)用的子類的父類屬性艾疟,那么子類不會被初始化来吩。

三、java虛擬機(jī)的垃圾回收機(jī)制

一蔽莱、Java垃圾回收機(jī)制
Java 的垃圾回收器要負(fù)責(zé)完成3 件任務(wù):
1.分配內(nèi)存
2.確保被引用的對象的內(nèi)存不被錯誤回收
3.回收不再被引用的對象的內(nèi)存空間弟疆。
垃圾回收是一個復(fù)雜而且耗時的操作。如果JVM 花費(fèi)過多的時間在垃圾回收上盗冷,則勢必會影響應(yīng)用的運(yùn)行性能怠苔。一般情況下,當(dāng)垃圾回收器在進(jìn)行回收操作的時候仪糖,整個應(yīng)用的執(zhí)行是被暫時中止(stop-the-world)的柑司。這是因為垃圾回收器需要更新應(yīng)用中所有對象引用的實際內(nèi)存地址。不同的硬件平臺所能支持的垃圾回收方式也不同乓诽。比如在多CPU 的平臺上帜羊,就可以通過并行的方式來回收垃圾。而單CPU 平臺則只能串行進(jìn)行鸠天。不同的應(yīng)用所期望的垃圾回收方式也會有所不同讼育。服務(wù)器端應(yīng)用可能希望在應(yīng)用的整個運(yùn)行時間中,花在垃圾回收上的時間總數(shù)越小越好稠集。而對于與用戶交互的應(yīng)用來說奶段,則可能希望所垃圾回收所帶來的應(yīng)用停頓的時間間隔越小越好。對于這種情況剥纷,JVM 中提供了多種垃圾回收方法以及對應(yīng)的性能調(diào)優(yōu)參數(shù)痹籍,應(yīng)用可以根據(jù)需要來進(jìn)行定制。
二晦鞋、判斷對象是否該被回收算法
1.引用計數(shù)算法
給對象添加一個引用計數(shù)器蹲缠,每當(dāng)有一個地方引用它時棺克,計數(shù)器值就加1,當(dāng)引用失效時线定,計數(shù)器值就減1娜谊;任何時刻計數(shù)器值都為0時對象就表示它不可能被使用了。這個算法實現(xiàn)簡單斤讥,但很難解決對象之間循環(huán)引用的問題纱皆,因此Java并沒有用這種算法!這是很多人都誤解了的地方芭商。
2.根搜索算法
通過一系列名為“GC ROOT”的對象作為起始點(diǎn)派草,從這些結(jié)點(diǎn)開始向下搜索,搜索所走過的路徑稱為引用鏈铛楣,當(dāng)一個對象到GC ROOT沒有任何引用鏈相連時近迁,則證明這個對象是不可用的。如果對象在進(jìn)行根搜索后發(fā)現(xiàn)沒有與GC ROOT相連接的引用鏈蛉艾,則會被第一次第標(biāo)記钳踊,并看此對象是否需要執(zhí)行finalize()方法(忘記finalize()這個方法吧,它可以被try-finally或其他方式代替的)勿侯,當(dāng)?shù)诙伪粯?biāo)記時,對象就會被回收缴罗。
三助琐、Java虛擬機(jī)基本垃圾回收算法:
1.標(biāo)記-清除(Mark-Sweep)


此算法執(zhí)行分兩階段。第一階段從引用根節(jié)點(diǎn)開始標(biāo)記所有被引用的對象面氓,第二階段遍歷整個堆兵钮,把未標(biāo)記的對象清除。它停止所有工作舌界,收集器從根開始訪問每一個活躍的節(jié)點(diǎn)掘譬,標(biāo)記它所訪問的每一個節(jié)點(diǎn)。走過所有引用后呻拌,收集就完成了葱轩,然后就對堆進(jìn)行清除(即對堆中的每一個對象進(jìn)行檢查),所有沒有標(biāo)記的對象都作為垃圾回收并返回空閑列表藐握。下圖 展示了垃圾收集之前的堆靴拱,陰影塊是垃圾,因為用戶程序不能到達(dá)它們:
可到達(dá)和不可到達(dá)的對象
[圖片上傳中猾普。袜炕。。(24)]
標(biāo)記-清除實現(xiàn)起來很簡單初家,可以容易地回收循環(huán)的結(jié)構(gòu)偎窘,并且不像引用計數(shù)那樣增加編譯器或者賦值函數(shù)的負(fù)擔(dān)乌助。但是它也有不足 ―― 收集暫停可能會很長陌知,在清除階段整個堆都是可訪問的眷茁,這對于可能有頁面交換的堆的虛擬內(nèi)存系統(tǒng)有非常負(fù)面的性能影響。
標(biāo)記-清除的最大問題是纵诞,每一個活躍的(即已分配的)對象上祈,不管是不是可到達(dá)的,在清除階段都是可以訪問的浙芙。因為很多對象都可能成為垃圾登刺,這意思著收集器花費(fèi)大量精力去檢查并處理垃圾。標(biāo)記-清除收集器還容易使堆產(chǎn)生碎片嗡呼,這會產(chǎn)生區(qū)域性問題并可以造成分配失敗纸俭,即使看來有足夠的自由內(nèi)存可用。此算法需要暫停整個應(yīng)用南窗,同時揍很,會產(chǎn)生內(nèi)存碎片。
[圖片上傳中万伤。窒悔。。(25)]

2.復(fù)制(Copying)

此算法把內(nèi)存空間劃為兩個相等的區(qū)域敌买,每次只使用其中一個區(qū)域简珠。垃圾回收時,遍歷當(dāng)前使用區(qū)域虹钮,把正在使用中的對象復(fù)制到另外一個區(qū)域中聋庵。次算法每次只處理正在使用中的對象,因此復(fù)制成本比較小芙粱,同時復(fù)制過去以后還能進(jìn)行相應(yīng)的內(nèi)存整理祭玉,不過出現(xiàn)“碎片”問題。當(dāng)然春畔,此算法的缺點(diǎn)也是很明顯的脱货,就是需要兩倍內(nèi)存空間。


[圖片上傳中拐迁。蹭劈。。(28)]

3.標(biāo)記-整理(Mark-Compact)

此算法結(jié)合了“標(biāo)記-清除”和“復(fù)制”兩個算法的優(yōu)點(diǎn)线召。也是分兩階段铺韧,第一階段從根節(jié)點(diǎn)開始標(biāo)記所有被引用對象,第二階段遍歷整個堆缓淹,把清除未標(biāo)記對象并且把存活對象“壓縮”到堆的其中一塊哈打,按順序排放塔逃。此算法避免了“標(biāo)記-清除”的碎片問題,同時也避免了“復(fù)制”算法的空間問題料仗。


[圖片上傳中湾盗。。立轧。(30)]

4.增量收集(Incremental Collecting)
實施垃圾回收算法格粪,即:在應(yīng)用進(jìn)行的同時進(jìn)行垃圾回收。不知道什么原因JDK5.0中的收集器沒有使用這種算法的氛改。

5.堆內(nèi)存的分代回收

[圖片上傳中帐萎。。胜卤。(31)]

[圖片上傳中疆导。。葛躏。(33)]
新生代(Young)

新生代包括兩個區(qū):Eden區(qū)和Survivor區(qū)澈段,其中Survivor區(qū)一般也分成兩塊,簡稱Survivor1 Space 和 Survivor2 Space (或者From Space 和 To Space)舰攒。新生代通常存活時間較短败富,因此基于標(biāo)記清除復(fù)制算法來進(jìn)行回收,掃描出存活的對象芒率,并復(fù)制到一塊新的完全未使用的空間中囤耳,對應(yīng)于新生代,就是在Eden和From或To之間copy偶芍。新生代采用空閑指針的方式來控制GC觸發(fā),指針保持最后一個分配的對象在新生代區(qū)間的位置德玫,當(dāng)有新的對象要分配內(nèi)存時匪蟀,用于檢查空間是否足夠,不夠就觸發(fā)GC宰僧。當(dāng)連續(xù)分配對象時材彪,對象會逐漸從eden到Survior,最后到舊生代琴儿。

可以采用串行處理和并行處理器

老年代(Old)

在垃圾回收多次段化,如果對象仍然存活,并且新生代的空間不夠造成,則對象會存放在老年代显熏。
在老年代采用的是 標(biāo)記清除壓縮算法。因為老年代的對象一般存活時間比較長晒屎,每次標(biāo)記清除之后喘蟆,會有很多的零碎空間缓升,這個就是所謂的浮動垃圾。當(dāng)老年代的零碎空間不足以分配一個大的對象的時候蕴轨,就會采用壓縮算法港谊。在壓縮的時候,應(yīng)用需要暫停橙弱。
可以采用串行處理歧寺,并行處理器,以及并發(fā)處理器棘脐。

持久代(Permanent)

這部分空間主要存放Java方法區(qū)的數(shù)據(jù)以及啟動類加載器加載的對象斜筐。這一部分對象通常不會被回收。所以持久代空間在默認(rèn)的情況下是不會被垃圾回收的荆残。

  由于把內(nèi)存空間分為三塊奴艾,一般把新生代的GC稱為minor GC ,把老年代的GC成為 full GC内斯,所謂full gc 會先出發(fā)一次minor gc蕴潦,然后在進(jìn)行老年代的GC。

具體的過程如下:
首先想eden區(qū)申請分配空間俘闯,如果空間夠潭苞,就直接進(jìn)行分配,否則進(jìn)行一次Minor GC真朗。minor GC 首先會對Eden區(qū)的對象進(jìn)行標(biāo)記此疹,標(biāo)記出來存活的對象。然后把存活的對象copy到From空間遮婶。如果From空間足夠蝗碎,則回收eden區(qū)可回收的對象。如果from內(nèi)存空間不夠旗扑,則把From空間存活的對象復(fù)制到To區(qū)蹦骑,如果TO區(qū)的內(nèi)存空間也不夠的話,則把To區(qū)存活的對象復(fù)制到老年代臀防。如果老年代空間也不夠(或者達(dá)到觸發(fā)老年年垃圾回收條件的話)則觸發(fā)一次full GC眠菇。

四、探秘Java虛擬機(jī) gc的監(jiān)控

[圖片上傳中袱衷。捎废。。(34)]

2致燥、常用的內(nèi)存區(qū)域調(diào)節(jié)參數(shù)
-Xms:初始堆大小登疗,默認(rèn)為物理內(nèi)存的1/64(<1GB);默認(rèn)(MinHeapFreeRatio參數(shù)可以調(diào)整)空余堆內(nèi)存小于40%時篡悟,JVM就會增大堆直到-Xmx的最大限制
-Xmx:最大堆大小谜叹,默認(rèn)(MaxHeapFreeRatio參數(shù)可以調(diào)整)空余堆內(nèi)存大于70%時匾寝,JVM會減少堆直到 -Xms的最小限制
-Xmn:新生代的內(nèi)存空間大小,注意:此處的大小是(eden+ 2 survivor space)荷腊。與jmap -heap中顯示的New gen是不同的艳悔。整個堆大小=新生代大小 + 老生代大小 + 永久代大小。 在保證堆大小不變的情況下女仰,增大新生代后,將會減小老生代大小猜年。此值對系統(tǒng)性能影響較大,Sun官方推薦配置為整個堆的3/8。
-XX:SurvivorRatio:新生代中Eden區(qū)域與Survivor區(qū)域的容量比值疾忍,默認(rèn)值為8乔外。兩個Survivor區(qū)與一個Eden區(qū)的比值為2:8,一個Survivor區(qū)占整個年輕代的1/10。
-Xss:每個線程的堆棧大小一罩。JDK5.0以后每個線程堆棧大小為1M,以前每個線程堆棧大小為256K杨幼。應(yīng)根據(jù)應(yīng) 用的線程所需內(nèi)存大小進(jìn)行適當(dāng)調(diào)整。在相同物理內(nèi)存下,減小這個值能生成更多的線程聂渊。但是操作系統(tǒng)對一個進(jìn)程內(nèi)的線程數(shù)還是有限制的差购,不能無限生成,經(jīng)驗 值在3000~5000左右汉嗽。一般小的應(yīng)用欲逃, 如果棧不是很深, 應(yīng)該是128k夠用的饼暑,大的應(yīng)用建議使用256k稳析。這個選項對性能影響比較大,需要嚴(yán)格的測試弓叛。和threadstacksize選項解釋很類似,官方文檔似乎沒有解釋,在論壇中有這樣一句話:"-Xss is translated in a VM flag named ThreadStackSize”一般設(shè)置這個值就可以了彰居。
-XX:PermSize:設(shè)置永久代(perm gen)初始值。默認(rèn)值為物理內(nèi)存的1/64撰筷。
-XX:MaxPermSize:設(shè)置持久代最大值裕菠。物理內(nèi)存的1/4。
3闭专、內(nèi)存分配方法
1)堆上分配 2)棧上分配 3)堆外分配(DirectByteBuffer或直接使用Unsafe.allocateMemory,但不推薦這種方式)
4、監(jiān)控方法
1)系統(tǒng)程序運(yùn)行時可通過jstat –gcutil來查看堆中各個內(nèi)存區(qū)域的變化以及GC的工作狀態(tài)旧烧; 2)啟動時可添加-XX:+PrintGCDetails –Xloggc:<file>輸出到日志文件來查看GC的狀況影钉; 3)jmap –heap可用于查看各個內(nèi)存空間的大小掘剪;
5)斷代法可用GC匯總


一平委、新生代可用GC
1)串行GC(Serial Copying):client模式下默認(rèn)GC方式,也可通過-XX:+UseSerialGC來強(qiáng)制指定夺谁;默認(rèn)情況下 eden廉赔、s0肉微、s1的大小通過-XX:SurvivorRatio來控制,默認(rèn)為8蜡塌,含義 為eden:s0的比例碉纳,啟動后可通過jmap –heap [pid]來查看。
默認(rèn)情況下馏艾,僅在TLAB或eden上分配劳曹,只有兩種情況下會在老生代分配: 1、需要分配的內(nèi)存大小超過eden space大欣拍Α铁孵; 2、在配置了PretenureSizeThreshold的情況下房资,對象大小大于此值蜕劝。
默認(rèn)情況下陶夜,觸發(fā)Minor GC時: 之前Minor GC晉級到old的平均大小 < 老生代的剩余空間 < eden+from Survivor的使用空間席纽。當(dāng)HandlePromotionFailure為true捷犹,則僅觸發(fā)minor gc涉瘾;如為false户盯,則觸發(fā)full GC氧骤。
默認(rèn)情況下奈泪,新生代對象晉升到老生代的規(guī)則:
1疾棵、經(jīng)歷多次minor gc仍存活的對象戳稽,可通過以下參數(shù)來控制:以MaxTenuringThreshold值為準(zhǔn)馆蠕,默認(rèn)為15。 2惊奇、to space放不下的互躬,直接放入老生代;
2)并行GC(ParNew):CMS GC時默認(rèn)采用颂郎,也可采用-XX:+UseParNewGC強(qiáng)制指定吼渡;垃圾回收的時候采用多線程的方式。
3)并行回收GC(Parallel Scavenge):server模式下默認(rèn)的GC方式乓序,也可采用-XX:+UseParallelGC強(qiáng)制指定寺酪;eden、s0替劈、s1的大小可通過-XX:SurvivorRatio來控制寄雀,但默認(rèn)情況下 以-XX:InitialSurivivorRatio為準(zhǔn),此值默認(rèn)為8陨献,代表的為新生代大小 : s0盒犹,這點(diǎn)要特別注意。
默認(rèn)情況下,當(dāng)TLAB急膀、eden上分配都失敗時沮协,判斷需要分配的內(nèi)存大小是否 >= eden space的一半大小,如是就直接在老生代上分配卓嫂;
默認(rèn)情況下的垃圾回收規(guī)則:
1慷暂、在回收前PS GC會先檢測之前每次PS GC時,晉升到老生代的平均大小是否大于老生代的剩余空間命黔,如大于則直接觸發(fā)full GC呜呐; 2、在回收后悍募,也會按照上面的規(guī)則進(jìn)行檢測蘑辑。
默認(rèn)情況下的新生代對象晉升到老生代的規(guī)則: 1、經(jīng)歷多次minor gc仍存活的對象坠宴,可通過以下參數(shù)來控制:AlwaysTenure洋魂,默認(rèn)false,表示只要minor GC時存活喜鼓,就晉升到老生代副砍;NeverTenure,默認(rèn)false庄岖,表示永不晉升到老生代豁翎;上面兩個都沒設(shè)置的情冴下,如 UseAdaptiveSizePolicy隅忿,啟動時以InitialTenuringThreshold值作為存活次數(shù)的閾值心剥,在每次ps gc后會動態(tài)調(diào)整,如不使用UseAdaptiveSizePolicy背桐,則以MaxTenuringThreshold為準(zhǔn)优烧。 2、to space放不下的链峭,直接放入老生代畦娄。
在回收后,如UseAdaptiveSizePolicy弊仪,PS GC會根據(jù)運(yùn)行狀態(tài)動態(tài)調(diào)整eden熙卡、to以及TenuringThreshold的大小。如果不希望動態(tài)調(diào)整可設(shè)置 -XX:-UseAdaptiveSizePolicy励饵。如希望跟蹤每次的變化情況再膳,可在啟勱參數(shù)上增加: PrintAdaptiveSizePolicy。
二曲横、老生代可用GC
1、串行GC(Serial Copying):client方式下默認(rèn)GC方式,可通過-XX:+UseSerialGC強(qiáng)制指定禾嫉。
觸發(fā)機(jī)制匯總: 1)old gen空間不足灾杰; 2)perm gen空間不足; 3)minor gc時的悲觀策略熙参; 4)minor GC后在eden上分配內(nèi)存仍然失斞薹汀; 5)執(zhí)行heap dump時孽椰; 6)外部調(diào)用System.gc昭娩,可通過-XX:+DisableExplicitGC來禁止。
2黍匾、并行回收GC(Parallel Scavenge): server模式下默認(rèn)GC方式栏渺,可通過-XX:+UseParallelGC強(qiáng)制指定; 并行的線程數(shù)為當(dāng)cpu core<=8 ? cpu core : 3+(cpu core5)/8或通過-XX:ParallelGCThreads=x來強(qiáng)制指定锐涯。如ScavengeBeforeFullGC為true(默認(rèn) 值)磕诊,則先執(zhí)行minor GC。
3纹腌、并行Compacting:可通過-XX:+UseParallelOldGC強(qiáng)制指定霎终。
4、并發(fā)CMS:可通過-XX:+UseConcMarkSweepGC來強(qiáng)制指定升薯。并發(fā)的線程數(shù)默認(rèn)為:( 并行GC線程數(shù)+3)/4莱褒,也可通過ParallelCMSThreads指定。
觸發(fā)機(jī)制: 1涎劈、當(dāng)老生代空間的使用到達(dá)一定比率時觸發(fā)广凸;
Hotspot V 1.6中默認(rèn)為65%,可通過PrintCMSInitiationStatistics(此參數(shù)在V 1.5中不能用)來查看這個值到底是多少责语;可通過CMSInitiatingOccupancyFraction來強(qiáng)制指定炮障,默認(rèn)值并不是賦值在了這個值 上,是根據(jù)如下公式計算出來的: ((100 - MinHeapFreeRatio) +(double)(CMSTriggerRatio * MinHeapFreeRatio) / 100.0)/ 100.0; 其中,MinHeapFreeRatio默認(rèn)值: 40 CMSTriggerRatio默認(rèn)值: 80坤候。
2胁赢、當(dāng)perm gen采用CMS收集且空間使用到一定比率時觸發(fā);
perm gen采用CMS收集需設(shè)置:-XX:+CMSClassUnloadingEnabled Hotspot V 1.6中默認(rèn)為65%白筹;可通過CMSInitiatingPermOccupancyFraction來強(qiáng)制指定智末,同樣,它是根據(jù)如下公式計算出來的: ((100 - MinHeapFreeRatio) +(double)(CMSTriggerPermRatio
MinHeapFreeRatio) / 100.0)/ 100.0; 其中徒河,MinHeapFreeRatio默認(rèn)值: 40 CMSTriggerPermRatio默認(rèn)值: 80系馆。
3、Hotspot根據(jù)成本計算決定是否需要執(zhí)行CMS GC顽照;可通過-XX:+UseCMSInitiatingOccupancyOnly來去掉這個動態(tài)執(zhí)行的策略由蘑。 4闽寡、外部調(diào)用了System.gc,且設(shè)置了ExplicitGCInvokesConcurrent尼酿;需要注意爷狈,在hotspot 6中,在這種情況下如應(yīng)用同時使用了NIO裳擎,可能會出現(xiàn)bug涎永。
6、GC組合
1)默認(rèn)GC組合

2)可選的GC組合

7鹿响、GC監(jiān)測
1)jstat –gcutil [pid] [intervel] [count] 2)-verbose:gc // 可以輔助輸出一些詳細(xì)的GC信息羡微;-XX:+PrintGCDetails // 輸出GC詳細(xì)信息;-XX:+PrintGCApplicationStoppedTime // 輸出GC造成應(yīng)用暫停的時間 -XX:+PrintGCDateStamps // GC發(fā)生的時間信息惶我;-XX:+PrintHeapAtGC // 在GC前后輸出堆中各個區(qū)域的大新杈蟆;-Xloggc:[file] // 將GC信息輸出到單獨(dú)的文件中指孤,建議都加上启涯,這個消耗不大,而且對查問題和調(diào)優(yōu)有很大的幫助恃轩。gc的日志拿下來后可使用GCLogViewer或 gchisto進(jìn)行分析。 3)圖形化的情況下可直接用jvisualvm進(jìn)行分析松忍。
4)查看內(nèi)存的消耗狀況
(1)長期消耗筷厘,可以直接dump鸣峭,然后MAT(內(nèi)存分析工具)查看即可
(2)短期消耗酥艳,圖形界面情況下摊溶,可使用jvisualvm的memory profiler或jprofiler。
8、系統(tǒng)調(diào)優(yōu)方法
步驟:1、評估現(xiàn)狀 2惰爬、設(shè)定目標(biāo) 3沪么、嘗試調(diào)優(yōu) 4、衡量調(diào)優(yōu) 5刊殉、細(xì)微調(diào)整
設(shè)定目標(biāo):
1)降低Full GC的執(zhí)行頻率瓤湘? 2)降低Full GC的消耗時間渔嚷? 3)降低Full GC所造成的應(yīng)用停頓時間? 4)降低Minor GC執(zhí)行頻率? 5)降低Minor GC消耗時間欺劳? 例如某系統(tǒng)的GC調(diào)優(yōu)目標(biāo):降低Full GC執(zhí)行頻率的同時,盡可能降低minor GC的執(zhí)行頻率鹏往、消耗時間以及GC對應(yīng)用造成的停頓時間群凶。
衡量調(diào)優(yōu):
1形真、衡量工具 1)打印GC日志信息:-XX:+PrintGCDetails –XX:+PrintGCApplicationStoppedTime -Xloggc: {文件名} -XX:+PrintGCTimeStamps 2)jmap:(由于每個版本jvm的默認(rèn)值可能會有改變超全,建議還是用jmap首先觀察下目前每個代的內(nèi)存大小咆霜、GC方式) ? 3)運(yùn)行狀況監(jiān)測工具:jstat、jvisualvm嘶朱、sar 蛾坯、gclogviewer
2、應(yīng)收集的信息 1)minor gc的執(zhí)行頻率疏遏;full gc的執(zhí)行頻率脉课,每次GC耗時多少? 2)高峰期什么狀況财异? 3)minor gc回收的效果如何倘零?survivor的消耗狀況如何,每次有多少對象會進(jìn)入老生代戳寸? 4)full gc回收的效果如何呈驶?(簡單的memory leak判斷方法) 5)系統(tǒng)的load、cpu消耗疫鹊、qps or tps袖瞻、響應(yīng)時間
QPS每秒查詢率:是對一個特定的查詢服務(wù)器在規(guī)定時間內(nèi)所處理流量多少的衡量標(biāo)準(zhǔn)司致。在因特網(wǎng)上,作為域名服務(wù)器的機(jī)器性能經(jīng)常用每秒查詢率來衡量聋迎。對應(yīng)fetches/sec脂矫,即每秒的響應(yīng)請求數(shù),也即是最大吞吐能力霉晕。 TPS(Transaction Per Second):每秒鐘系統(tǒng)能夠處理的交易或事務(wù)的數(shù)量庭再。
嘗試調(diào)優(yōu):
注意Java RMI的定時GC觸發(fā)機(jī)制,可通過:-XX:+DisableExplicitGC來禁止或通過 -Dsun.rmi.dgc.server.gcInterval=3600000來控制觸發(fā)的時間牺堰。
1)降低Full GC執(zhí)行頻率 – 通常瓶頸 老生代本身占用的內(nèi)存空間就一直偏高佩微,所以只要稍微放點(diǎn)對象到老生代,就full GC了萌焰; 通常原因:系統(tǒng)緩存的東西太多; 例如:使用Oracle 10g驅(qū)動時preparedstatement cache太大谷浅; 查找辦法:現(xiàn)執(zhí)行Dump然后再進(jìn)行MAT分析扒俯;
(1)Minor GC后總是有對象不斷的進(jìn)入老生代,導(dǎo)致老生代不斷的滿 通常原因:Survivor太小了 系統(tǒng)表現(xiàn):系統(tǒng)響應(yīng)太慢一疯、請求量太大撼玄、每次請求分配的內(nèi)存太多、分配的對象太大... 查找辦法:分析兩次minor GC之間到底哪些地方分配了內(nèi)存墩邀; 利用jstat觀察Survivor的消耗狀況掌猛,-XX:PrintHeapAtGC,輸出GC前后的詳細(xì)信息眉睹; 對于系統(tǒng)響應(yīng)慢可以采用系統(tǒng)優(yōu)化荔茬,不是GC優(yōu)化的內(nèi)容;
(2)老生代的內(nèi)存占用一直偏高 調(diào)優(yōu)方法:① 擴(kuò)大老生代的大兄窈!(減少新生代的大小或調(diào)大heap的 大心轿怠); 減少new注意對minor gc的影響并且同時有可能造成full gc還是嚴(yán)重斋配; 調(diào)大heap注意full gc的時間的延長孔飒,cpu夠強(qiáng)悍嘛,os是32 bit的嗎艰争? ② 程序優(yōu)化(去掉一些不必要的緩存)
(3)Minor GC后總是有對象不斷的進(jìn)入老生代 前提:這些進(jìn)入老生代的對象在full GC時大部分都會被回收 調(diào)優(yōu)方法: ① 降低Minor GC的執(zhí)行頻率坏瞄; ② 讓對象盡量在Minor GC中就被回收掉:增大Eden區(qū)、增大survivor甩卓、增大TenuringThreshold鸠匀;注意這些可能會造成minor gc執(zhí)行頻繁; ③ 切換成CMS GC:老生代還沒有滿就回收掉猛频,從而降低Full GC觸發(fā)的可能性狮崩; ④ 程序優(yōu)化:提升響應(yīng)速度蛛勉、降低每次請求分配的內(nèi)存、
(4)降低單次Full GC的執(zhí)行時間 通常原因:老生代太大了... 調(diào)優(yōu)方法:1)是并行GC嗎睦柴? 2)升級CPU 3)減小Heap或老生代
(5)降低Minor GC執(zhí)行頻率 通常原因:每次請求分配的內(nèi)存多诽凌、請求量大 通常辦法:1)擴(kuò)大heap、擴(kuò)大新生代坦敌、擴(kuò)大eden侣诵。注意點(diǎn):降低每次請求分配的內(nèi)存;橫向增加機(jī)器的數(shù)量分擔(dān)請求的數(shù)量狱窘。
(6)降低Minor GC執(zhí)行時間 通常原因:新生代太大了杜顺,響應(yīng)速度太慢了,導(dǎo)致每次Minor GC時存活的對象多 通常辦法:1)減小點(diǎn)新生代吧蘸炸;2)增加CPU的數(shù)量躬络、升級CPU的配置;加快系統(tǒng)的響應(yīng)速度
細(xì)微調(diào)整:
首先需要了解以下情況:
① 當(dāng)響應(yīng)速度下降到多少或請求量上漲到多少時搭儒,系統(tǒng)會宕掉穷当?
② 參數(shù)調(diào)整后系統(tǒng)多久會執(zhí)行一次Minor GC,多久會執(zhí)行一次Full GC淹禾,高峰期會如何馁菜?
需要計算的量:
①每次請求平均需要分配多少內(nèi)存?系統(tǒng)的平均響應(yīng)時間是多少呢铃岔?請求量是多少汪疮、多常時間執(zhí)行一次Minor GC、Full GC毁习?
②現(xiàn)有參數(shù)下智嚷,應(yīng)該是多久一次Minor GC、Full GC蜓洪,對比真實狀況纤勒,做一定的調(diào)整;
必殺技:提升響應(yīng)速度隆檀、降低每次請求分配的內(nèi)存摇天?
9、系統(tǒng)調(diào)優(yōu)舉例
現(xiàn)象:1恐仑、系統(tǒng)響應(yīng)速度大概為100ms泉坐;2、當(dāng)系統(tǒng)QPS增長到40時裳仆,機(jī)器每隔5秒就執(zhí)行一次minor gc腕让,每隔3分鐘就執(zhí)行一次full gc,并且很快就一直full GC了;4纯丸、每次Full gc后舊生代大概會消耗400M偏形,有點(diǎn)多了。
解決方案:解決Full GC次數(shù)過多的問題
(1)降低響應(yīng)時間或請求次數(shù)觉鼻,這個需要重構(gòu)俊扭,比較麻煩;——這個是終極方法坠陈,往往能夠順利的解決問題萨惑,因為大部分的問題均是由程序自身造成的。
(2)減少老生代內(nèi)存的消耗仇矾,比較靠譜庸蔼;——可以通過分析Dump文件(jmap dump),并利用MAT查找內(nèi)存消耗的原因贮匕,從而發(fā)現(xiàn)程序中造成老生代內(nèi)存消耗的原因姐仅。
(3)減少每次請求的內(nèi)存的消耗,貌似比較靠譜刻盐;——這個是海市蜃樓萍嬉,沒有太好的辦法。
(4)降低GC造成的應(yīng)用暫停的時間——可以采用CMS GS垃圾回收器隙疚。參數(shù)設(shè)置如下:
-Xms1536m -Xmx1536m -Xmn700m -XX:SurvivorRatio=7 -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection
-XX:CMSMaxAbortablePrecleanTime=1000 -XX:+CMSClassUnloadingEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:+DisableExplicitGC
(5)減少每次minor gc晉升到old的對象】牡溃可選方法:1) 調(diào)大新生代供屉。2)調(diào)大Survivor。3)調(diào)大TenuringThreshold溺蕉。
調(diào)大Survivor:當(dāng)前采用PS GC伶丐,Survivor space會被動態(tài)調(diào)整。由于調(diào)整幅度很小疯特,導(dǎo)致了經(jīng)常有對象直接轉(zhuǎn)移到了老生代哗魂;于是禁止Survivor區(qū)的動態(tài)調(diào)整 了,-XX:-UseAdaptiveSizePolicy漓雅,并計算Survivor Space需要的大小录别,于是繼續(xù)觀察,并做微調(diào)…邻吞。最終將Full GC推遲到2小時1次组题。
10、垃圾回收的實現(xiàn)原理
內(nèi)存回收的實現(xiàn)方法:1)引用計數(shù):不適合復(fù)雜對象的引用關(guān)系抱冷,尤其是循環(huán)依賴的場景崔列。2)有向圖Tracing:適合于復(fù)雜對象的引用關(guān)系場景,Hotspot采用這種旺遮。常用算法:Copying赵讯、Mark-Sweep盈咳、Mark-Compact。
Hotspot從root set開始掃描有引用的對象并對Reference類型的對象進(jìn)行特殊處理边翼。 以下是Root Set的列表:1)當(dāng)前正在執(zhí)行的線程鱼响;2)全局/靜態(tài)變量;3)JVM Handles讯私;4)JNI 【 Java Native Interface 】Handles热押;
另外:minor GC只掃描新生代,當(dāng)老生代的對象引用了新生代的對象時斤寇,會采用如下的處理方式:在給對象賦引用時桶癣,會經(jīng)過一個write barrier的過程腊瑟,以便檢查是否有老生代引用新生代對象的情況雀费,如有則記錄到remember set中。并在minor gc時罐农,remember set指向的新生代對象也作為root set莫秆。
新生代串行GC(Serial Copying):
新生代串行GC(Serial Copying)完整內(nèi)存的分配策略:
1)首先在TLAB(本地線程分配緩沖區(qū))上嘗試分配间雀; 2)檢查是否需要在新生代上分配,如需要分配的大小小于PretenureSizeThreshold镊屎,則在eden區(qū)上進(jìn)行分配惹挟,分配成功則返回;分配失敗則繼續(xù)缝驳; 3)檢查是否需要嘗試在老生代上分配连锯,如需要,則遍歷所有代并檢查是否可在該代上分配用狱,如可以則進(jìn)行分配运怖;如不需要在老生代上嘗試分配,則繼續(xù)夏伊; 4)根據(jù)策略決定執(zhí)行新生代GC或Full GC摇展,執(zhí)行full gc時不清除soft Ref; 5)如需要分配的大小大于PretenureSizeThreshold溺忧,嘗試在老生代上分配咏连,否則嘗試在新生代上分配; 6)嘗試擴(kuò)大堆并分配鲁森; 7)執(zhí)行full gc捻勉,并清除所有soft Ref,按步驟5繼續(xù)嘗試分配刀森。
新生代串行GC(Serial Copying)完整內(nèi)存回收策略 1)檢查to是否為空踱启,不為空返回false; 2)檢查老生代剩余空間是否大于當(dāng)前eden+from已用的大小,如大于則返回true埠偿,如小于且HandlePromotionFailure為 true透罢,則檢查剩余空間是否大于之前每次minor gc晉級到老生代的平均大小,如大于返回true冠蒋,如小于返回false羽圃。 3)如上面的結(jié)果為false,則執(zhí)行full gc抖剿;如上面的結(jié)果為true朽寞,執(zhí)行下面的步驟; 4)掃描引用關(guān)系斩郎,將活的對象copy到to space脑融,如對象在minor gc中的存活次數(shù)超過tenuring_threshold或分配失敗,則往老生代復(fù)制缩宜,如仍然復(fù)制失敗肘迎,則取決于 HandlePromotionFailure,如不需要處理锻煌,直接拋出OOM妓布,并退出vm,如需處理宋梧,則保持這些新生代對象不動匣沼;
新生代可用GC-PS
完整內(nèi)存分配策略 1)先在TLAB上分配,分配失敗則直接在eden上分配捂龄; 2)當(dāng)eden上分配失敗時肛著,檢查需要分配的大小是否 >= eden space的一半,如是跺讯,則直接在老生代分配; 3)如分配仍然失敗殉农,且gc已超過頻率刀脏,則拋出OOM; 4)進(jìn)入基本分配策略失敗的模式超凳; 5)執(zhí)行PS GC愈污,在eden上分配; 6)執(zhí)行非最大壓縮的full gc轮傍,在eden上分配暂雹; 7)在舊生代上分配; 8)執(zhí)行最大壓縮full gc创夜,在eden上分配杭跪; 9)在舊生代上分配; 10)如還失敗,回到2涧尿。
最悲慘的情況系奉,分配觸發(fā)多次PS GC和多次Full GC,直到OOM姑廉。
完整內(nèi)存回收策略 1)如gc所執(zhí)行的時間超過缺亮,直接結(jié)束; 2)先調(diào)用invoke_nopolicy 2.1 先檢查是不是要嘗試scavenge桥言; 2.1.1 to space必須為空萌踱,如不為空,則返回false号阿; 2.1.2 獲取之前所有minor gc晉級到old的平均大小并鸵,并對比目前eden+from已使用的大小,取更小的一個值倦西,如老生代剩余空間小于此值能真,則返回false,如大于則返回true扰柠; 2.2 如不需要嘗試scavenge粉铐,則返回false,否則繼續(xù)卤档; 2.3 多線程掃描活的對象蝙泼,并基亍copying算法回收,回收時相應(yīng)的晉升對象到舊生代劝枣; 2.4 如UseAdaptiveSizePolicy汤踏,那么重新計算to space和tenuringThreshold的值,并調(diào)整舔腾。 3)如invoke_nopolicy返回的是false溪胶,或之前所有minor gc晉級到老生代的平均大小 > 舊生代的剩余空間,那么繼續(xù)下面的步驟稳诚,否則結(jié)束哗脖; 4)如UseParallelOldGC,則執(zhí)行PSParallelCompact扳还,如不是UseParallelOldGC才避,則執(zhí)行PSMarkSweep。
老生代并行CMS GC:
優(yōu)缺點(diǎn):
1) 大部分時候和應(yīng)用并發(fā)進(jìn)行氨距,因此只會造成很短的暫停時間桑逝; 2)浮動垃圾,沒辦法俏让,所以內(nèi)存空間要稍微大一點(diǎn)楞遏; 3)內(nèi)存碎片茬暇,-XX:+UseCMSCompactAtFullCollection 來解決; 4) 爭搶CPU橱健,這GC方式就這樣而钞; 5)多次remark,所以總的gc時間會比并行的長拘荡; 6)內(nèi)存分配臼节,free list方式,so性能稍差珊皿,對minor GC會有一點(diǎn)影響网缝; 7)和應(yīng)用并發(fā),有可能分配和回收同時蟋定,產(chǎn)生競爭粉臊,引入了鎖,JVM分配優(yōu)先驶兜。
11扼仲、TLAB的解釋
堆內(nèi)的對象數(shù)據(jù)是各個線程所共享的,所以當(dāng)在堆內(nèi)創(chuàng)建新的對象時抄淑,就需要進(jìn)行鎖操作屠凶。鎖操作是比較耗時,因此JVM為每個線在堆上分配了一塊“自留地” ——TLAB(全稱是Thread Local Allocation Buffer)肆资,位于堆內(nèi)存的新生代矗愧,也就是Eden區(qū)。每個線程在創(chuàng)建新的對象時郑原,會首先嘗試在自己的TLAB里進(jìn)行分配唉韭,如果成功就返回,失敗了再到 共享的Eden區(qū)里去申請空間犯犁。在線程自己的TLAB區(qū)域創(chuàng)建對象失敗一般有兩個原因:一是對象太大属愤,二是自己的TLAB區(qū)剩余空間不夠。通常默認(rèn)的 TLAB區(qū)域大小是Eden區(qū)域的1%酸役,當(dāng)然也可以手工進(jìn)行調(diào)整住诸,對應(yīng)的JVM參數(shù)是-XX:TLABWasteTargetPercent〈睾矗《java虛擬機(jī)》匯總所有關(guān)鍵要點(diǎn) - 小草君技術(shù)專欄 - 博客頻道 - CSDN.NET http://blog.csdn.net/ldds_520/article/details/51932125

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市俏拱,隨后出現(xiàn)的幾起案子暑塑,更是在濱河造成了極大的恐慌,老刑警劉巖锅必,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件事格,死亡現(xiàn)場離奇詭異惕艳,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)驹愚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門远搪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人逢捺,你說我怎么就攤上這事谁鳍。” “怎么了劫瞳?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵倘潜,是天一觀的道長。 經(jīng)常有香客問我志于,道長涮因,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任伺绽,我火速辦了婚禮养泡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘奈应。我一直安慰自己澜掩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布钥组。 她就那樣靜靜地躺著输硝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪程梦。 梳的紋絲不亂的頭發(fā)上点把,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天,我揣著相機(jī)與錄音屿附,去河邊找鬼郎逃。 笑死,一個胖子當(dāng)著我的面吹牛挺份,可吹牛的內(nèi)容都是我干的褒翰。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼匀泊,長吁一口氣:“原來是場噩夢啊……” “哼优训!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起各聘,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤揣非,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后躲因,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體早敬,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡忌傻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了搞监。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片水孩。...
    茶點(diǎn)故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖琐驴,靈堂內(nèi)的尸體忽然破棺而出俘种,到底是詐尸還是另有隱情,我是刑警寧澤棍矛,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布安疗,位于F島的核電站,受9級特大地震影響够委,放射性物質(zhì)發(fā)生泄漏荐类。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一茁帽、第九天 我趴在偏房一處隱蔽的房頂上張望玉罐。 院中可真熱鬧,春花似錦潘拨、人聲如沸吊输。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽季蚂。三九已至,卻和暖如春琅束,著一層夾襖步出監(jiān)牢的瞬間扭屁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工涩禀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留料滥,地道東北人。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓艾船,卻偏偏與公主長得像葵腹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子屿岂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評論 2 355

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