JVM

一.Java運行時數(shù)據(jù)區(qū)

Java虛擬機在執(zhí)行Java程序的過程中會將其管理的內(nèi)存劃分為若干個不同的數(shù)據(jù)區(qū)域留量,這些區(qū)域有各自的用途惶我、創(chuàng)建和銷毀的時間狞甚,有些區(qū)域隨虛擬機進程的啟動而存在裂允,有些區(qū)域則是依賴用戶線程的啟動和結(jié)束來建立和銷毀。

Java虛擬機所管理的內(nèi)存包括以下幾個運行時數(shù)據(jù)區(qū)域,如圖:

1禁谦、程序計數(shù)器:指向當前線程正在執(zhí)行的字節(jié)碼指令胁黑。線程私有的。

2州泊、虛擬機棧:虛擬機棧是Java執(zhí)行方法的內(nèi)存模型丧蘸。每個方法被執(zhí)行的時候,都會創(chuàng)建一個棧幀拥诡,把棧幀壓人棧触趴,當方法正常返回或者拋出未捕獲的異常時,棧幀就會出棧渴肉。

(1)棧幀:棧幀存儲方法的相關(guān)信息冗懦,包含局部變量數(shù)表、返回值仇祭、操作數(shù)棧披蕉、動態(tài)鏈接

局部變量表:包含了方法執(zhí)行過程中的所有變量。局部變量數(shù)組所需要的空間在編譯期間完成分配乌奇,在方法運行期間不會改變局部變量數(shù)組的大小没讲。

返回值:如果有返回值的話,壓入調(diào)用者棧幀中的操作數(shù)棧中礁苗,并且把PC的值指向 方法調(diào)用指令 后面的一條指令地址爬凑。

操作數(shù)棧:操作變量的內(nèi)存模型。操作數(shù)棧的最大深度在編譯的時候已經(jīng)確定(寫入方法區(qū)code屬性的max_stacks項中)试伙。操作數(shù)棧的的元素可以是任意Java類型嘁信,包括long和double,32位數(shù)據(jù)占用検柽叮空間為1潘靖,64位數(shù)據(jù)占用2。方法剛開始執(zhí)行的時候蚤蔓,棧是空的卦溢,當方法執(zhí)行過程中,各種字節(jié)碼指令往棧中存取數(shù)據(jù)秀又。

動態(tài)鏈接:每個棧幀都持有在運行時常量池中該棧幀所屬方法的引用单寂,持有這個引用是為了支持方法調(diào)用過程中的動態(tài)鏈接。

(2)線程私有

3涮坐、本地方法棧:

調(diào)用本地native的內(nèi)存模型

線程獨享凄贩。

4、方法區(qū):

用于存儲已被虛擬機加載的類信息袱讹、常量、靜態(tài)變量、即時編譯后的代碼等數(shù)據(jù)

1.線程共享的

2.運行時常量池:

是方法區(qū)的一部分

存放編譯期生成的各種字面量和符號引用

Class文件中除了存有類的版本捷雕、字段椒丧、方法、接口等描述信息救巷,還有一項是常量池壶熏,存有這個類的 編譯期生成的各種字面量和符號引用,這部分內(nèi)容將在類加載后浦译,存放到方法區(qū)的運行時常量池中棒假。

5、堆(Heap):

Java對象存儲的地方

Java堆是虛擬機管理的內(nèi)存中最大的一塊

Java堆是所有線程共享的區(qū)域

在虛擬機啟動時創(chuàng)建

此內(nèi)存區(qū)域的唯一目的就是存放對象實例精盅,幾乎所有對象實例都在這里分配內(nèi)存帽哑。存放new生成的對象和數(shù)組

Java堆是垃圾收集器管理的內(nèi)存區(qū)域,因此很多時候稱為“GC堆”

二.JMM Java內(nèi)存模型

1叹俏、 Java的并發(fā)采用“共享內(nèi)存”模型妻枕,線程之間通過讀寫內(nèi)存的公共狀態(tài)進行通訊。多個線程之間是不能通過直接傳遞數(shù)據(jù)交互的粘驰,它們之間交互只能通過共享變量實現(xiàn)屡谐。

2、 主要目的是定義程序中各個變量的訪問規(guī)則蝌数。

3愕掏、 Java內(nèi)存模型規(guī)定所有變量都存儲在主內(nèi)存中,每個線程還有自己的工作內(nèi)存顶伞。

線程的工作內(nèi)存中保存了被該線程使用到的變量的拷貝(從主內(nèi)存中拷貝過來)饵撑,線程對變量的所有操作都必須在工作內(nèi)存中執(zhí)行,而不能直接訪問主內(nèi)存中的變量枝哄。

不同線程之間無法直接訪問對方工作內(nèi)存的變量肄梨,線程間變量值的傳遞都要通過主內(nèi)存來完成。

主內(nèi)存主要對應(yīng)Java堆中實例數(shù)據(jù)部分挠锥。工作內(nèi)存對應(yīng)于虛擬機棧中部分區(qū)域众羡。

4、Java線程之間的通信由內(nèi)存模型JMM(Java Memory Model)控制蓖租。

JMM決定一個線程對變量的寫入何時對另一個線程可見粱侣。

線程之間共享變量存儲在主內(nèi)存中

每個線程有一個私有的本地內(nèi)存,里面存儲了讀/寫共享變量的副本蓖宦。

JMM通過控制每個線程的本地內(nèi)存之間的交互齐婴,來為程序員提供內(nèi)存可見性保證。

5稠茂、可見性柠偶、有序性:

當一個共享變量在多個本地內(nèi)存中有副本時情妖,如果一個本地內(nèi)存修改了該變量的副本,其他變量應(yīng)該能夠看到修改后的值诱担,此為可見性毡证。

保證線程的有序執(zhí)行,這個為有序性蔫仙。(保證線程安全)

6料睛、內(nèi)存間交互操作:

lock(鎖定):作用于主內(nèi)存的變量,把一個變量標識為一條線程獨占狀態(tài)摇邦。

unlock(解鎖):作用于主內(nèi)存的變量恤煞,把一個處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定施籍。

read(讀染影恰):作用于主內(nèi)存變量,把主內(nèi)存的一個變量讀取到工作內(nèi)存中法梯。

load(載入):作用于工作內(nèi)存苔货,把read操作讀取到工作內(nèi)存的變量載入到工作內(nèi)存的變量副本中

use(使用):作用于工作內(nèi)存的變量,把工作內(nèi)存中的變量值傳遞給一個執(zhí)行引擎立哑。

assign(賦值):作用于工作內(nèi)存的變量夜惭。把執(zhí)行引擎接收到的值賦值給工作內(nèi)存的變量。

store(存儲):把工作內(nèi)存的變量的值傳遞給主內(nèi)存

write(寫入):把store操作的值入到主內(nèi)存的變量中

6.1铛绰、注意:

不允許read诈茧、load、store捂掰、write操作之一單獨出現(xiàn)

不允許一個線程丟棄assgin操作

不允許一個線程不經(jīng)過assgin操作敢会,就把工作內(nèi)存中的值同步到主內(nèi)存中

一個新的變量只能在主內(nèi)存中生成

一個變量同一時刻只允許一條線程對其進行l(wèi)ock操作。但lock操作可以被同一條線程執(zhí)行多次这嚣,只有執(zhí)行相同次數(shù)的unlock操作鸥昏,變量才會解鎖

如果對一個變量進行l(wèi)ock操作,將會清空工作內(nèi)存中此變量的值姐帚,在執(zhí)行引擎使用這個變量前吏垮,需要重新執(zhí)行l(wèi)oad或者assgin操作初始化變量的值。

如果一個變量沒有被鎖定罐旗,不允許對其執(zhí)行unlock操作膳汪,也不允許unlock一個被其他線程鎖定的變量

對一個變量執(zhí)行unlock操作之前,需要將該變量同步回主內(nèi)存中

堆的內(nèi)存劃分

Java堆的內(nèi)存劃分如圖所示九秀,分別為年輕代遗嗽、Old Memory(老年代)、Perm(永久代)鼓蜒。其中在Jdk1.8中痹换,永久代被移除征字,使用MetaSpace代替。

1晴音、新生代:

使用復(fù)制清除算法(Copinng算法)柔纵,原因是年輕代每次GC都要回收大部分對象缔杉。新生代里面分成一份較大的Eden空間和兩份較小的Survivor空間锤躁。每次只使用Eden和其中一塊Survivor空間,然后垃圾回收的時候或详,把存活對象放到未使用的Survivor(劃分出from系羞、to)空間中,清空Eden和剛才使用過的Survivor空間霸琴。

分為Eden椒振、Survivor From、Survivor To梧乘,比例默認為8:1:1

內(nèi)存不足時發(fā)生Minor GC

2澎迎、老年代:

采用標記-整理算法(mark-compact),原因是老年代每次GC只會回收少部分對象选调。

3夹供、Perm:用來存儲類的元數(shù)據(jù),也就是方法區(qū)仁堪。

Perm的廢除:在jdk1.8中哮洽,Perm被替換成MetaSpace,MetaSpace存放在本地內(nèi)存中弦聂。原因是永久代進場內(nèi)存不夠用鸟辅,或者發(fā)生內(nèi)存泄漏。

MetaSpace(元空間):元空間的本質(zhì)和永久代類似莺葫,都是對JVM規(guī)范中方法區(qū)的實現(xiàn)匪凉。不過元空間與永久代之間最大的區(qū)別在于:元空間并不在虛擬機中,而是使用本地內(nèi)存捺檬。

4再层、堆內(nèi)存的劃分在JVM里面的示意圖:

GC垃圾回收

一、 判斷對象是否要回收的方法:可達性分析法

1欺冀、 可達性分析法:

通過一系列“GC Roots”對象作為起點進行搜索树绩,如果在“GC Roots”和一個對象之間沒有可達路徑,則稱該對象是不可達的隐轩。不可達對象不一定會成為可回收對象饺饭。進入DEAD狀態(tài)的線程還可以恢復(fù),GC不會回收它的內(nèi)存职车。(把一些對象當做root對象瘫俊,JVM認為root對象是不可回收的鹊杖,并且root對象引用的對象也是不可回收的)

2、 以下對象會被認為是root對象:

虛擬機棧(棧幀中本地變量表)中引用的對象

方法區(qū)中靜態(tài)屬性引用的對象

方法區(qū)中常量引用的對象

本地方法棧中Native方法引用的對象

3扛芽、 對象被判定可被回收骂蓖,需要經(jīng)歷兩個階段:

第一個階段是可達性分析,分析該對象是否可達

第二個階段是當對象沒有重寫finalize()方法或者finalize()方法已經(jīng)被調(diào)用過川尖,虛擬機認為該對象不可以被救活登下,因此回收該對象。(finalize()方法在垃圾回收中的作用是叮喳,給該對象一次救活的機會)

4被芳、 方法區(qū)中的垃圾回收:

1.常量池中一些常量、符號引用沒有被引用馍悟,則會被清理出常量池

2.無用的類:被判定為無用的類畔濒,會被清理出方法區(qū)。判定方法如下:

該類的所有實例被回收

加載該類的ClassLoader被回收

該類的Class對象沒有被引用

5锣咒、 finalize():

GC垃圾回收要回收一個對象的時候侵状,調(diào)用該對象的finalize()方法。然后在下一次垃圾回收的時候毅整,才去回收這個對象的內(nèi)存趣兄。

可以在該方法里面,指定一些對象在釋放前必須執(zhí)行的操作毛嫉。

三诽俯、常見的垃圾回收算法:

1、Mark-Sweep(標記-清除算法):

(1)思想:標記清除算法分為兩個階段承粤,標記階段和清除階段暴区。標記階段任務(wù)是標記出所有需要回收的對象,清除階段就是清除被標記對象的空間辛臊。

(2)優(yōu)缺點:實現(xiàn)簡單仙粱,容易產(chǎn)生內(nèi)存碎片

2、Copying(復(fù)制清除算法):

(1)思想:將可用內(nèi)存劃分為大小相等的兩塊彻舰,每次只使用其中的一塊伐割。當進行垃圾回收的時候了,把其中存活對象全部復(fù)制到另外一塊中刃唤,然后把已使用的內(nèi)存空間一次清空掉隔心。

(2)優(yōu)缺點:不容易產(chǎn)生內(nèi)存碎片;可用內(nèi)存空間少尚胞;存活對象多的話硬霍,效率低下。

3笼裳、Mark-Compact(標記-整理算法):

(1)思想:先標記存活對象唯卖,然后把存活對象向一邊移動粱玲,然后清理掉端邊界以外的內(nèi)存。

(2)優(yōu)缺點:不容易產(chǎn)生內(nèi)存碎片拜轨;內(nèi)存利用率高抽减;存活對象多并且分散的時候,移動次數(shù)多橄碾,效率低下

4卵沉、分代收集算法:(目前大部分JVM的垃圾收集器所采用的算法)

思想:把堆分成新生代和老年代。(永久代指的是方法區(qū))

因為新生代每次垃圾回收都要回收大部分對象堪嫂,所以新生代采用Copying算法偎箫。新生代里面分成一份較大的Eden空間和兩份較小的Survivor空間。每次只使用Eden和其中一塊Survivor空間皆串,然后垃圾回收的時候,把存活對象放到未使用的Survivor(劃分出from眉枕、to)空間中恶复,清空Eden和剛才使用過的Survivor空間。

由于老年代每次只回收少量的對象速挑,因此采用mark-compact算法谤牡。

在堆區(qū)外有一個永久代。對永久代的回收主要是無效的類和常量

5姥宝、GC使用時對程序的影響翅萤?

垃圾回收會影響程序的性能,Java虛擬機必須要追蹤運行程序中的有用對象腊满,然后釋放沒用對象套么,這個過程消耗處理器時間

6、幾種不同的垃圾回收類型:

(1)Minor GC:從年輕代(包括Eden碳蛋、Survivor區(qū))回收內(nèi)存胚泌。

當JVM無法為一個新的對象分配內(nèi)存的時候,越容易觸發(fā)Minor GC肃弟。所以分配率越高玷室,內(nèi)存越來越少,越頻繁執(zhí)行Minor GC

執(zhí)行Minor GC操作的時候笤受,不會影響到永久代(Tenured)穷缤。從永久代到年輕代的引用,被當成GC Roots箩兽,從年輕代到老年代的引用在標記階段直接被忽略掉津肛。

(2)Major GC:清理整個老年代,當eden區(qū)內(nèi)存不足時觸發(fā)比肄。

(3)Full GC:清理整個堆空間快耿,包括年輕代和老年代囊陡。當老年代內(nèi)存不足時觸發(fā)

HotSpot 虛擬機詳解

1、 Java對象創(chuàng)建過程:

(1)虛擬機遇到一條new指令時掀亥,首先檢查這個指令的參數(shù)能否在常量池中定位到一個類的符號引用撞反,并檢查這個符號引用代表的類是否已經(jīng)加載、連接和初始化搪花。如果沒有遏片,就執(zhí)行該類的加載過程。(Java知音公眾號內(nèi)回復(fù)“面試題聚合”撮竿,送你一份面試寶典)

(2)為該對象分配內(nèi)存吮便。

假設(shè)Java堆是規(guī)整的,所有用過的內(nèi)存放在一邊幢踏,空閑的內(nèi)存放在另外一邊髓需,中間放著一個指針作為分界點的指示器。那分配內(nèi)存只是把指針向空閑空間那邊挪動與對象大小相等的距離房蝉,這種分配稱為“指針碰撞”

假設(shè)Java堆不是規(guī)整的僚匆,用過的內(nèi)存和空閑的內(nèi)存相互交錯,那就沒辦法進行“指針碰撞”搭幻。虛擬機通過維護一個列表咧擂,記錄哪些內(nèi)存塊是可用的,在分配的時候找出一塊足夠大的空間分配給對象實例檀蹋,并更新表上的記錄松申。這種分配方式稱為“空閑列表“。

使用哪種分配方式由Java堆是否規(guī)整決定俯逾。Java堆是否規(guī)整由所采用的垃圾收集器是否帶有壓縮整理功能決定贸桶。

分配對象保證線程安全的做法:虛擬機使用CAS失敗重試的方式保證更新操作的原子性。(實際上還有另外一種方案:每個線程在Java堆中預(yù)先分配一小塊內(nèi)存纱昧,稱為本地線程分配緩沖刨啸,TLAB。哪個線程要分配內(nèi)存识脆,就在哪個線程的TLAB上分配设联,只有TLAB用完并分配新的TLAB時,才進行同步鎖定灼捂。虛擬機是否使用TLAB离例,由-XX:+/-UseTLAB參數(shù)決定)

(3)虛擬機為分配的內(nèi)存空間初始化為零值(默認值)

(4)虛擬機對對象進行必要的設(shè)置,例如這個對象是哪個類的實例悉稠、如何才能找到對象的元數(shù)據(jù)信息宫蛆、對象的Hash碼、對象的GC分代年齡等信息。這些信息存放在對象的對象頭中耀盗。

(5) 執(zhí)行<init>方法想虎,把對象按照程序員的意愿進行初始化。

2叛拷、 對象的定位訪問的方式(通過引用如何去定位到堆上的具體對象的位置):

(1)句柄:使用句柄的方式舌厨,Java堆中將會劃分出一塊內(nèi)存作為作為句柄池,引用中存儲的就是對象的句柄的地址忿薇。而句柄中包含了對象實例數(shù)據(jù)和對象類型數(shù)據(jù)的地址裙椭。

(2)直接指針:使用直接指針的方式,引用中存儲的就是對象的地址署浩。Java堆對象的布局必須必須考慮如何去訪問對象類型數(shù)據(jù)揉燃。

(3)兩種方式各有優(yōu)點:

使用句柄訪問的好處是引用中存放的是穩(wěn)定的句柄地址,當對象被移動(比如說垃圾回收時移動對象)筋栋,只會改變句柄中實例數(shù)據(jù)指針炊汤,而引用本身不會被修改。

使用直接指針二汛,節(jié)省了一次指針定位的時間開銷婿崭。

3、HotSpot的GC算法實現(xiàn):

(1)HotSpot怎么快速找到GC Root肴颊?

HotSpot使用一組稱為OopMap的數(shù)據(jù)結(jié)構(gòu)。在類加載完成的時候承匣,HotSpot就把對象內(nèi)什么偏移量上是什么類型的數(shù)據(jù)計算出來创夜,在JIT編譯過程中师抄,也會在棧和寄存器中哪些位置是引用。這樣子竟宋,在GC掃描的時候,就可以直接知道哪些是可達對象了形纺。

(2)安全點:

HotSpot只在特定的位置生成OopMap丘侠,這些位置稱為安全點。

程序執(zhí)行過程中并非所有地方都可以停下來開始GC逐样,只有在到達安全點是才可以暫停蜗字。

安全點的選定基本上以“是否具有讓程序長時間執(zhí)行“的特征選定的。比如說方法調(diào)用脂新、循環(huán)跳轉(zhuǎn)挪捕、異常跳轉(zhuǎn)等。具有這些功能的指令才會產(chǎn)生Safepoint争便。

(3)中斷方式:

搶占式中斷:在GC發(fā)生時级零,首先把所有線程中斷,如果發(fā)現(xiàn)有線程不在安全點上滞乙,就恢復(fù)線程奏纪,讓它跑到安全點上鉴嗤。

主動式中斷:GC需要中斷線程時,不直接對線程操作序调,僅僅設(shè)置一個標志醉锅,各個線程執(zhí)行時主動去輪詢這個標志,當發(fā)現(xiàn)中斷標記為真就自己中斷掛起炕置。輪詢標記的地方和安全點是重合的荣挨。

(4)安全區(qū)域:

一段代碼片段中,對象的引用關(guān)系不會發(fā)生變化朴摊,在這個區(qū)域中任何地方開始GC都是安全的默垄。在線程進入安全區(qū)域時,它首先標志自己已經(jīng)進入安全區(qū)域甚纲,在這段時間里口锭,當JVM發(fā)起GC時,就不用管進入安全區(qū)域的線程了介杆。

在線程將要離開安全區(qū)域時鹃操,它檢查系統(tǒng)是否完成了GC過程,如果完成了春哨,它就繼續(xù)前行荆隘。否則,它就必須等待直到收到可以離開安全區(qū)域的信號赴背。(Java知音公眾號內(nèi)回復(fù)“面試題聚合”椰拒,送你一份面試寶典)

4、 GC時為什么要停頓所有Java線程凰荚?

因為GC先進行可達性分析燃观。可達性分析是判斷GC Root對象到其他對象是否可達便瑟,假如分析過程中對象的引用關(guān)系在不斷變化缆毁,分析結(jié)果的準確性就無法得到保證。

5到涂、 CMS收集器:

(1)一種以獲取最短回收停頓時間為目標的收集器脊框。

(2)一般用于互聯(lián)網(wǎng)站或者B/S系統(tǒng)的服務(wù)端

(3)基于標記-清除算法的實現(xiàn),不過更為復(fù)雜养盗,整個過程為4個步驟:

初始標記:標記GC Root能直接引用的對象

并發(fā)標記:利用多線程對每個GC Root對象進行tracing搜索缚陷,在堆中查找其下所有能關(guān)聯(lián)到的對象。

重新標記:為了修正并發(fā)標記期間往核,用戶程序繼續(xù)運作而導(dǎo)致標志產(chǎn)生變動的那一部分對象的標記記錄箫爷。

并發(fā)清除:利用多個線程對標記的對象進行清除

(4)由于耗時最長的并發(fā)標記和并發(fā)清除操作都是用戶線程一起工作,所以總體來說,CMS的內(nèi)存回收工作是和用戶線程一起并發(fā)執(zhí)行的虎锚。

(5)缺點:

對CPU資源占用比較多硫痰。可能因為占用一部分CPU資源導(dǎo)致應(yīng)用程序響應(yīng)變慢窜护。

CMS無法處理浮動垃圾效斑。在并發(fā)清除階段,用戶程序繼續(xù)運行柱徙,可能產(chǎn)生新的內(nèi)存垃圾缓屠,這一部分垃圾出現(xiàn)在標記過程之后,因此护侮,CMS無法清除敌完。這部分垃圾稱為“浮動垃圾“

需要預(yù)留一部分內(nèi)存,在垃圾回收時羊初,給用戶程序使用滨溉。

基于標記-清除算法,容易產(chǎn)生大量內(nèi)存碎片长赞,導(dǎo)致full GC(full GC進行內(nèi)存碎片的整理)

6晦攒、 對象頭部分的內(nèi)存布局:

HotSpot的對象頭分為兩部分,第一部分用于存儲對象自身的運行時數(shù)據(jù)得哆,比如哈希碼脯颜、GC分代年齡等。另外一部分用于指向方法區(qū)對象類型數(shù)據(jù)的指針贩据。

7伐脖、 偏向鎖:

偏向鎖偏向于第一個獲取它的線程,如果在接下來的執(zhí)行過程乐设,沒有其他線程獲取該鎖,則持有偏向鎖的線程永遠不需要同步绎巨。(當一個線程獲取偏向鎖近尚,它每次進入這個鎖相關(guān)的同步塊,虛擬機不在進行任何同步操作场勤。當有另外一個線程嘗試獲取這個鎖時戈锻,偏向模式宣告結(jié)束)

JVM優(yōu)化

1、一般來說和媳,當survivor區(qū)不夠大或者占用量達到50%格遭,就會把一些對象放到老年區(qū)。通過設(shè)置合理的eden區(qū)留瞳,survivor區(qū)及使用率拒迅,可以將年輕對象保存在年輕代,從而避免full GC,使用-Xmn設(shè)置年輕代的大小

2璧微、對于占用內(nèi)存比較多的大對象作箍,一般會選擇在老年代分配內(nèi)存。如果在年輕代給大對象分配內(nèi)存前硫,年輕代內(nèi)存不夠了胞得,就要在eden區(qū)移動大量對象到老年代,然后這些移動的對象可能很快消亡屹电,因此導(dǎo)致full GC阶剑。通過設(shè)置參數(shù):-XX:PetenureSizeThreshold=1000000,單位為B危号,標明對象大小超過1M時牧愁,在老年代(tenured)分配內(nèi)存空間。

3葱色、一般情況下递宅,年輕對象放在eden區(qū),當?shù)谝淮蜧C后苍狰,如果對象還存活办龄,放到survivor區(qū),此后淋昭,每GC一次俐填,年齡增加1,當對象的年齡達到閾值翔忽,就被放到tenured老年區(qū)英融。這個閾值可以同構(gòu)-XX:MaxTenuringThreshold設(shè)置。如果想讓對象留在年輕代歇式,可以設(shè)置比較大的閾值驶悟。

4、設(shè)置最小堆和最大堆:-Xmx和-Xms穩(wěn)定的堆大小堆垃圾回收是有利的材失,獲得一個穩(wěn)定的堆大小的方法是設(shè)置-Xms和-Xmx的值一樣痕鳍,即最大堆和最小堆一樣,如果這樣子設(shè)置龙巨,系統(tǒng)在運行時堆大小理論上是恒定的笼呆,穩(wěn)定的堆空間可以減少GC次數(shù),因此旨别,很多服務(wù)端都會將這兩個參數(shù)設(shè)置為一樣的數(shù)值诗赌。穩(wěn)定的堆大小雖然減少GC次數(shù),但是增加每次GC的時間秸弛,因為每次GC要把堆的大小維持在一個區(qū)間內(nèi)铭若。

5洪碳、一個不穩(wěn)定的堆并非毫無用處。在系統(tǒng)不需要使用大內(nèi)存的時候奥喻,壓縮堆空間偶宫,使得GC每次應(yīng)對一個較小的堆空間,加快單次GC次數(shù)环鲤〈壳鳎基于這種考慮,JVM提供兩個參數(shù)冷离,用于壓縮和擴展堆空間吵冒。

-XX:MinHeapFreeRatio參數(shù)用于設(shè)置堆空間的最小空閑比率。默認值是40西剥,當堆空間的空閑內(nèi)存比率小于40痹栖,JVM便會擴展堆空間

-XX:MaxHeapFreeRatio參數(shù)用于設(shè)置堆空間的最大空閑比率。默認值是70瞭空, 當堆空間的空閑內(nèi)存比率大于70揪阿,JVM便會壓縮堆空間。

當-Xmx和-Xmx相等時咆畏,上面兩個參數(shù)無效

6南捂、通過增大吞吐量提高系統(tǒng)性能,可以通過設(shè)置并行垃圾回收收集器旧找。

-XX:+UseParallelGC:年輕代使用并行垃圾回收收集器溺健。這是一個關(guān)注吞吐量的收集器,可以盡可能的減少垃圾回收時間钮蛛。

-XX:+UseParallelOldGC:設(shè)置老年代使用并行垃圾回收收集器鞭缭。

7、嘗試使用大的內(nèi)存分頁:使用大的內(nèi)存分頁增加CPU的內(nèi)存尋址能力魏颓,從而系統(tǒng)的性能岭辣。-XX:+LargePageSizeInBytes設(shè)置內(nèi)存頁的大小

8、使用非占用的垃圾收集器甸饱。-XX:+UseConcMarkSweepGC老年代使用CMS收集器降低停頓易结。

9、-XXSurvivorRatio=8柜候,表示年輕代中的分配比率:survivor:from:eden = 1:1:8

????? -XXNewRatio= 2,表示年輕代和老年代的比例為:1:2

10、JVM性能調(diào)優(yōu)的工具:

jps(Java Process Status):輸出JVM中運行的進程狀態(tài)信息(現(xiàn)在一般使用jconsole)

jstack:查看java進程內(nèi)線程的堆棧信息躏精。

jmap:用于生成堆轉(zhuǎn)存快照

jhat:用于分析jmap生成的堆轉(zhuǎn)存快照(一般不推薦使用渣刷,而是使用Ecplise Memory Analyzer)

jstat是JVM統(tǒng)計監(jiān)測工具〈V颍可以用來顯示垃圾回收信息辅柴、類加載信息箩溃、新生代統(tǒng)計信息等。

VisualVM:故障處理工具

四.類加載機制

一碌嘀、 概念

類加載器把class文件中的二進制數(shù)據(jù)讀入到內(nèi)存中涣旨,存放在方法區(qū),然后在堆區(qū)創(chuàng)建一個java.lang.Class對象股冗,用來封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)霹陡。

類加載的步驟如下:

1、加載:查找并加載類的二進制數(shù)據(jù)(把class文件里面的信息加載到內(nèi)存里面)

2止状、連接:把內(nèi)存中類的二進制數(shù)據(jù)合并到虛擬機的運行時環(huán)境中

(1)驗證:確保被加載的類的正確性烹棉。包括:

類文件的結(jié)構(gòu)檢查:檢查是否滿足Java類文件的固定格式

語義檢查:確保類本身符合Java的語法規(guī)范

字節(jié)碼驗證:確保字節(jié)碼流可以被Java虛擬機安全的執(zhí)行。字節(jié)碼流是操作碼組成的序列怯疤。每一個操作碼后面都會跟著一個或者多個操作數(shù)浆洗。字節(jié)碼檢查這個步驟會檢查每一個操作碼是否合法。

二進制兼容性驗證:確保相互引用的類之間是協(xié)調(diào)一致的集峦。

(2)準備:為類的靜態(tài)變量分配內(nèi)存伏社,并將其初始化為默認值

(3)解析:把類中的符號引用轉(zhuǎn)化為直接引用(比如說方法的符號引用,是有方法名和相關(guān)描述符組成塔淤,在解析階段摘昌,JVM把符號引用替換成一個指針,這個指針就是直接引用凯沪,它指向該類的該方法在方法區(qū)中的內(nèi)存位置)

3第焰、初始化:

為類的靜態(tài)變量賦予正確的初始值。當靜態(tài)變量的等號右邊的值是一個常量表達式時妨马,不會調(diào)用static代碼塊進行初始化挺举。只有等號右邊的值是一個運行時運算出來的值,才會調(diào)用static初始化烘跺。

二湘纵、雙親委派模型

1、當一個類加載器收到類加載請求的時候滤淳,它首先不會自己去加載這個類的信息梧喷,而是把該請求轉(zhuǎn)發(fā)給父類加載器,依次向上脖咐。

所以所有的類加載請求都會被傳遞到父類加載器中铺敌,只有當父類加載器中無法加載到所需的類,子類加載器才會自己嘗試去加載該類屁擅。當當前類加載器和所有父類加載器都無法加載該類時偿凭,拋出ClassNotFindException異常。

2派歌、意義:

提高系統(tǒng)的安全性弯囊。用戶自定義的類加載器不可能加載應(yīng)該由父加載器加載的可靠類痰哨。(比如用戶定義了一個惡意代碼,自定義的類加載器首先讓系統(tǒng)加載器去加載匾嘱,系統(tǒng)加載器檢查該代碼不符合規(guī)范斤斧,于是就不繼續(xù)加載了)

3、定義類加載器:如果某個類加載器能夠加載一個類霎烙,那么這個類加載器就叫做定義類加載器

4撬讽、初始類加載器:定義類加載器及其所有子加載器都稱作初始類加載器。

5吼过、運行時包:

由同一個類加載器加載并且擁有相同包名的類組成運行時包

只有屬于同一個運行時包的類锐秦,才能訪問包可見(default)的類和類成員。作用是 限制用戶自定義的類冒充核心類庫的類去訪問核心類庫的包可見成員盗忱。

6酱床、加載兩份相同的class對象的情況:A和B不屬于父子類加載器關(guān)系,并且各自都加載了同一個類趟佃。

三扇谣、特點:

1、全盤負責:當一個類加載器加載一個類時闲昭,該類所依賴的其他類也會被這個類加載器加載到內(nèi)存中罐寨。

2、緩存機制:所有的Class對象都會被緩存序矩,當程序需要使用某個Class時鸯绿,類加載器先從緩存中查找,找不到簸淀,才從class文件中讀取數(shù)據(jù)瓶蝴,轉(zhuǎn)化成Class對象,存入緩存中租幕。

?類加載器:

兩種類型的類加載器:

1舷手、 JVM自帶的類加載器(3種):

(1)根類加載器(Bootstrap):

C++編寫的,程序員無法在程序中獲取該類

負責加載虛擬機的核心庫劲绪,比如java.lang.Object

沒有繼承ClassLoader類

(2)擴展類加載器(Extension):

Java編寫的男窟,從指定目錄中加載類庫

父加載器是根類加載器

是ClassLoader的子類

如果用戶把創(chuàng)建的jar文件放到指定目錄中,也會被擴展加載器加載贾富。

(3)系統(tǒng)加載器(System)或者應(yīng)用加載器(App):

Java編寫的

父加載器是擴展類加載器

從環(huán)境變量或者class.path中加載類

是用戶自定義類加載的默認父加載器

是ClassLoader的子類

2歉眷、用戶自定義的類加載器:

Java.lang.ClassLoader類的子類

用戶可以定制類的加載方式

父類加載器是系統(tǒng)加載器

編寫步驟:

A、繼承ClassLoader

B颤枪、重寫findClass方法姥芥。從特定位置加載class文件,得到字節(jié)數(shù)組汇鞭,然后利用defineClass把字節(jié)數(shù)組轉(zhuǎn)化為Class對象

(5)為什么要自定義類加載器凉唐?

可以從指定位置加載class文件,比如說從數(shù)據(jù)庫霍骄、云端加載class文件

加密:Java代碼可以被輕易的反編譯台囱,因此,如果需要對代碼進行加密读整,那么加密以后的代碼簿训,就不能使用Java自帶的ClassLoader來加載這個類了,需要自定義ClassLoader米间,對這個類進行解密强品,然后加載。

問題:Java程序?qū)︻惖膱?zhí)行有幾種方式:

1屈糊、 主動使用(6種情況):

JVM必須在每個類“首次 主動使用”的時候的榛,才會初始化這些類。

創(chuàng)建類的實例

讀寫某個類或者接口的靜態(tài)變量

調(diào)用類的靜態(tài)方法

同過反射的API(Class.forName())獲取類

初始化一個類的子類

JVM啟動的時候逻锐,被標明啟動類的類(包含Main方法的類)

只有當程序使用的靜態(tài)變量或者靜態(tài)方法確實在該類中定義時夫晌,該可以認為是對該類或者接口的主動使用。

2昧诱、 被動使用:

除了主動使用的6種情況晓淀,其他情況都是被動使用,都不會導(dǎo)致類的初始化盏档。

3凶掰、 JVM規(guī)范允許類加載器在預(yù)料某個類將要被使用的時候,就預(yù)先加載它蜈亩。如果該class文件缺失或者存在錯誤懦窘,則在程序“首次 主動使用”的時候,才報告這個錯誤勺拣。(Linkage Error錯誤)奶赠。如果這個類一直沒有被程序“主動使用”,就不會報錯药有。

類加載機制與接口:

當Java虛擬機初始化一個類時毅戈,不會初始化該類實現(xiàn)的接口。

在初始化一個接口時愤惰,不會初始化這個接口父接口苇经。

只有當程序首次使用該接口的靜態(tài)變量時,才導(dǎo)致該接口的初始化宦言。

ClassLoader:

調(diào)用Classloader的loadClass方法去加載一個類扇单,不是主動使用,因此不會進行類的初始化奠旺。

類的卸載:

有JVM自帶的三種類加載器(根蜘澜、擴展施流、系統(tǒng))加載的類始終不會卸載。因為JVM始終引用這些類加載器鄙信,這些類加載器使用引用他們所加載的類瞪醋,因此這些Class類對象始終是可到達的。

由用戶自定義類加載器加載的類装诡,是可以被卸載的银受。

補充:

JDK和JRK

JDK :Java Development Kit,開發(fā)的時候用到的類包鸦采。

JRE :Java Runtime Environment宾巍,Java運行的基礎(chǔ),包含運行時需要的所有類庫渔伯。

圖解java文件轉(zhuǎn)化成機器碼

JVM虛擬機先將java文件編譯成class文件(字節(jié)碼文件)顶霞,然后再將class文件轉(zhuǎn)換成所有操作系統(tǒng)都能運行的機器指令。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咱旱,一起剝皮案震驚了整個濱河市确丢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吐限,老刑警劉巖鲜侥,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異诸典,居然都是意外死亡描函,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門狐粱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來舀寓,“玉大人,你說我怎么就攤上這事肌蜻』ツ梗” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵蒋搜,是天一觀的道長篡撵。 經(jīng)常有香客問我,道長豆挽,這世上最難降的妖魔是什么育谬? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮帮哈,結(jié)果婚禮上膛檀,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好咖刃,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布泳炉。 她就那樣靜靜地躺著,像睡著了一般嚎杨。 火紅的嫁衣襯著肌膚如雪胡桃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天磕潮,我揣著相機與錄音,去河邊找鬼容贝。 笑死自脯,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的斤富。 我是一名探鬼主播单起,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼导披,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起痹筛,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎泞坦,沒想到半個月后肮蛹,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡潦嘶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年涩嚣,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掂僵。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡航厚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出锰蓬,到底是詐尸還是另有隱情幔睬,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布芹扭,位于F島的核電站麻顶,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏冯勉。R本人自食惡果不足惜澈蚌,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望灼狰。 院中可真熱鬧宛瞄,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至杯活,卻和暖如春匆帚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背旁钧。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工吸重, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人歪今。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓嚎幸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親寄猩。 傳聞我的和親對象是個殘疾皇子嫉晶,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

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

  • 工作之余,想總結(jié)一下JVM相關(guān)知識田篇。 Java運行時數(shù)據(jù)區(qū): Java虛擬機在執(zhí)行Java程序的過程中會將其管理的...
    Huang遠閱讀 636評論 0 2
  • 介紹JVM中7個區(qū)域替废,然后把每個區(qū)域可能造成內(nèi)存的溢出的情況說明 程序計數(shù)器:看做當前線程所執(zhí)行的字節(jié)碼行號指示器...
    jemmm閱讀 2,232評論 0 9
  • 內(nèi)存溢出和內(nèi)存泄漏的區(qū)別 內(nèi)存溢出:out of memory,是指程序在申請內(nèi)存時泊柬,沒有足夠的內(nèi)存空間供其使用椎镣,...
    Aimerwhy閱讀 744評論 0 1
  • Java 虛擬機有自己完善的硬件架構(gòu), 如處理器、堆棧彬呻、寄存器等衣陶,還具有相應(yīng)的指令系統(tǒng)。JVM 屏蔽了與具體操作系...
    尹小凱閱讀 1,695評論 0 10
  • JVM有哪些分區(qū)闸氮?程序計數(shù)器剪况,java虛擬機棧,本地方法棧蒲跨,堆译断,方法區(qū)。 java棧中存放的是一個個棧幀或悲,每一個棧...
    irckwk1閱讀 697評論 0 0