一.類加載機(jī)制
1.類的加載:類的加載指的是將類的.class 文件中的二進(jìn)制數(shù)據(jù)讀取到內(nèi)存中遂鹊,并放在方法去內(nèi)勘纯,然后堆區(qū)創(chuàng)建一個(gè) java.lang.Class對(duì)象址芯,用來封裝在方法去內(nèi)的數(shù)據(jù)結(jié)構(gòu)。類加載的最終產(chǎn)品就是Class 對(duì)象鞠评,向程序員提供了訪問方法的數(shù)據(jù)結(jié)構(gòu)接口脱茉。
2.類的生命周期:類加載的過程包括了加載剪芥、驗(yàn)證、準(zhǔn)備琴许、解析(運(yùn)行時(shí)綁定粗俱,就是說可能在初始化之后解析)、初始化五個(gè)階段虚吟。
3.類的加載三種方式:
- 命令行啟動(dòng)應(yīng)用時(shí)候由JVM初始化加載
- 通過Class.forName()方法動(dòng)態(tài)加載
- 通過ClassLoader.loadClass()方法動(dòng)態(tài)加載
package com.neo.classloader;
public class loaderTest {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader loader = HelloWorld.class.getClassLoader();
System.out.println(loader);
//使用ClassLoader.loadClass()來加載類寸认,不會(huì)執(zhí)行初始化塊
loader.loadClass("Test2");
//使用Class.forName()來加載類,默認(rèn)會(huì)執(zhí)行初始化塊
//Class.forName("Test2");
//使用Class.forName()來加載類串慰,并指定ClassLoader偏塞,初始化時(shí)不執(zhí)行靜態(tài)塊
//Class.forName("Test2", false, loader);
}
}
Class.forName()和ClassLoader.loadClass()區(qū)別
Class.forName():將類的.class文件加載到j(luò)vm中之外,還會(huì)對(duì)類進(jìn)行解釋邦鲫,執(zhí)行類中的static塊灸叼;
ClassLoader.loadClass():只干一件事情,就是將.class文件加載到j(luò)vm中庆捺,不會(huì)執(zhí)行static中的內(nèi)容,只有在newInstance才會(huì)去執(zhí)行static塊古今。
Class.forName(name, initialize, loader)帶參函數(shù)也可控制是否加載static塊。并且只有調(diào)用了newInstance()方法采用調(diào)用構(gòu)造函數(shù)滔以,創(chuàng)建類的對(duì)象 捉腥。
4.雙親委派模型:雙親委派模型的工作流程是:如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類你画,而是把請(qǐng)求委托給父加載器去完成抵碟,依次向上,因此坏匪,所有的類加載請(qǐng)求最終都應(yīng)該被傳遞到頂層的啟動(dòng)類加載器中拟逮,只有當(dāng)父加載器在它的搜索范圍中沒有找到所需的類時(shí),即無(wú)法完成該加載适滓,子加載器才會(huì)嘗試自己去加載該類敦迄。
雙親委派機(jī)制:
1、當(dāng)AppClassLoader加載一個(gè)class時(shí)凭迹,它首先不會(huì)自己去嘗試加載這個(gè)類罚屋,而是把類加載請(qǐng)求委派給父類加載器ExtClassLoader去完成。
2蕊苗、當(dāng)ExtClassLoader加載一個(gè)class時(shí)沿后,它首先也不會(huì)自己去嘗試加載這個(gè)類,而是把類加載請(qǐng)求委派給BootStrapClassLoader```去完成朽砰。
3尖滚、如果BootStrapClassLoader加載失敽砹酢(例如在$JAVA_HOME/jre/lib里未查找到該class),會(huì)使用ExtClassLoader來嘗試加載漆弄;
4睦裳、若ExtClassLoader也加載失敗,則會(huì)使用AppClassLoader來加載撼唾,如果AppClassLoader也加載失敗廉邑,則會(huì)報(bào)出異常ClassNotFoundException。
ClassLoader源碼分析:
public Class<?> loadClass(String name)throws ClassNotFoundException {
return loadClass(name, false);
}
protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
// 首先判斷該類型是否已經(jīng)被加載
Class c = findLoadedClass(name);
if (c == null) {
//如果沒有被加載倒谷,就委托給父類加載或者委派給啟動(dòng)類加載器加載
try {
if (parent != null) {
//如果存在父類加載器蛛蒙,就委派給父類加載器加載
c = parent.loadClass(name, false);
} else {
//如果不存在父類加載器,就檢查是否是由啟動(dòng)類加載器加載的類渤愁,通過調(diào)用本地方法native Class findBootstrapClass(String name)
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// 如果父類加載器和啟動(dòng)類加載器都不能完成加載任務(wù)牵祟,才調(diào)用自身的加載功能
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
雙親委派模型意義:
系統(tǒng)類防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼
保證Java程序安全穩(wěn)定運(yùn)行
二.JVM 內(nèi)存結(jié)構(gòu)
JVM內(nèi)存結(jié)構(gòu)主要有三大塊:堆內(nèi)存、方法區(qū)和棧抖格。堆內(nèi)存是JVM中最大的一塊由++年輕代和老年代++組成诺苹,而年輕代內(nèi)存又被分成++三部分++,++Eden空間雹拄、From Survivor空間收奔、To Survivor空間++,默認(rèn)情況下年輕代按照8:1:1的比例來分配;
方法區(qū)存儲(chǔ)類信息滓玖、常量坪哄、靜態(tài)變量等數(shù)據(jù),是線程共享的區(qū)域呢撞,為與Java堆區(qū)分损姜,方法區(qū)還有一個(gè)別名Non-Heap(非堆)饰剥;棧又分為java虛擬機(jī)棧和本地方法棧主要用于方法的執(zhí)行殊霞。
程序計(jì)數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間,它的作用可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器汰蓉。
控制參數(shù)
-Xms設(shè)置堆的最小空間大小绷蹲。
-Xmx設(shè)置堆的最大空間大小。
-XX:NewSize設(shè)置新生代最小空間大小顾孽。
-XX:MaxNewSize設(shè)置新生代最大空間大小祝钢。
-XX:PermSize設(shè)置永久代最小空間大小。
-XX:MaxPermSize設(shè)置永久代最大空間大小若厚。
-Xss設(shè)置每個(gè)線程的堆棧大小
對(duì)象分配規(guī)則
對(duì)象優(yōu)先分配在Eden區(qū)拦英,如果Eden區(qū)沒有足夠的空間時(shí),虛擬機(jī)執(zhí)行一次Minor GC测秸。
大對(duì)象直接進(jìn)入老年代(大對(duì)象是指需要大量連續(xù)內(nèi)存空間的對(duì)象)疤估。這樣做的目的是避免在Eden區(qū)和兩個(gè)Survivor區(qū)之間發(fā)生大量的內(nèi)存拷貝(新生代采用復(fù)制算法收集內(nèi)存)灾常。
長(zhǎng)期存活的對(duì)象進(jìn)入老年代。虛擬機(jī)為每個(gè)對(duì)象定義了一個(gè)年齡計(jì)數(shù)器铃拇,如果對(duì)象經(jīng)過了1次Minor GC那么對(duì)象會(huì)進(jìn)入Survivor區(qū)钞瀑,同時(shí)年齡+1,之后每經(jīng)過一次Minor GC那么對(duì)象的年齡加1慷荔,知道達(dá)到閥值對(duì)象進(jìn)入老年區(qū)雕什。
動(dòng)態(tài)判斷對(duì)象的年齡。如果Survivor區(qū)中相同年齡的所有對(duì)象大小的總和大于Survivor空間的一半显晶,年齡大于或等于該年齡的對(duì)象可以直接進(jìn)入老年代贷岸。
空間分配擔(dān)保。每次進(jìn)行Minor GC時(shí)磷雇,JVM會(huì)計(jì)算Survivor區(qū)移至老年區(qū)的對(duì)象的平均大小凰盔,如果這個(gè)值大于老年區(qū)的剩余值大小則進(jìn)行一次Full GC,如果小于檢查HandlePromotionFailure設(shè)置倦春,如果true則只進(jìn)行Monitor GC,如果false則進(jìn)行Full GC户敬。
GC 算法:
判斷對(duì)象是否存活一般有兩種方式:
引用計(jì)數(shù):每個(gè)對(duì)象有一個(gè)引用計(jì)數(shù)屬性,新增一個(gè)引用時(shí)計(jì)數(shù)加1睁本,引用釋放時(shí)計(jì)數(shù)減1尿庐,計(jì)數(shù)為0時(shí)可以回收。此方法簡(jiǎn)單呢堰,無(wú)法解決對(duì)象相互循環(huán)引用的問題抄瑟。
可達(dá)性分析(Reachability Analysis):從GC Roots開始向下搜索,搜索所走過的路徑稱為引用鏈枉疼。當(dāng)一個(gè)對(duì)象到GC Roots沒有任何引用鏈相連時(shí)皮假,則證明此對(duì)象是不可用的,不可達(dá)對(duì)象骂维。
GC最基礎(chǔ)的算法有三種:標(biāo)記 -清除算法惹资、復(fù)制算法、標(biāo)記-壓縮算法航闺,我們常用的垃圾回收器一般都采用分代收集算法褪测。
詳細(xì)介紹:
標(biāo)記 -清除算法,“標(biāo)記-清除”(Mark-Sweep)算法潦刃,如它的名字一樣侮措,算法分為“標(biāo)記”和“清除”兩個(gè)階段:首先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收掉所有被標(biāo)記的對(duì)象乖杠。
復(fù)制算法分扎,“復(fù)制”(Copying)的收集算法,它將可用內(nèi)存按容量劃分為大小相等的兩塊胧洒,每次只使用其中的一塊畏吓。當(dāng)這一塊的內(nèi)存用完了环揽,就將還存活著的對(duì)象復(fù)制到另外一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉庵佣。
標(biāo)記-壓縮算法歉胶,標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣,但后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理巴粪,而是讓所有存活的對(duì)象都向一端移動(dòng)通今,然后直接清理掉端邊界以外的內(nèi)存
分代收集算法,“分代收集”(Generational Collection)算法肛根,把Java堆分為新生代和老年代辫塌,這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴ā?/h6>
垃圾回收器了解:
Serial收集器,串行收集器是最古老派哲,最穩(wěn)定以及效率高的收集器臼氨,可能會(huì)產(chǎn)生較長(zhǎng)的停頓,只使用一個(gè)線程去回收芭届。
ParNew收集器储矩,ParNew收集器其實(shí)就是Serial收集器的多線程版本。
Parallel收集器褂乍,Parallel Scavenge收集器類似ParNew收集器持隧,Parallel收集器更關(guān)注系統(tǒng)的吞吐量。
Parallel Old 收集器逃片,Parallel Old是Parallel Scavenge收集器的老年代版本屡拨,使用多線程和“標(biāo)記-整理”算法
CMS收集器,CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器褥实。
G1 收集器呀狼,是一款面向服務(wù)器的垃圾收集器,主要針對(duì)配備多顆處理器及大容量?jī)?nèi)存的機(jī)器损离,以極高概率滿足 GC 停頓時(shí)間要求的同時(shí)哥艇,還具備高吞吐量性能特征。
三.JVM 調(diào)優(yōu)
jvm監(jiān)控分析工具一般分為兩類草冈,一種是jdk自帶的工具她奥,一種是第三方的分析工具。jdk自帶工具一般在jdk bin目錄下面怎棱,jconsole.exe和jvisualvm.exe;第三方的分析工具有很多绷跑,各自的側(cè)重點(diǎn)不同拳恋,比較有代表性的:MAT(Memory Analyzer Tool)、GChisto等砸捏。
Sun JDK監(jiān)控和故障處理命令有jps jstat jmap jhat jstack jinfo
例子:
JPS:JVM Process Status Tool,顯示指定系統(tǒng)內(nèi)所有的HotSpot虛擬機(jī)進(jìn)程谬运。
$ jps -l -m
28920 org.apache.catalina.startup.Bootstrap start
11589 org.apache.catalina.startup.Bootstrap start
25816 sun.tools.jps.Jps -l -m
jstat(JVM statistics Monitoring)是用于監(jiān)視虛擬機(jī)運(yùn)行時(shí)狀態(tài)信息的命令隙赁,它可以顯示出虛擬機(jī)進(jìn)程中的類裝載、內(nèi)存梆暖、垃圾收集伞访、JIT編譯等運(yùn)行數(shù)據(jù)。
$ jstat -class 11589
Loaded Bytes Unloaded Bytes Time
7035 14506.3 0 0.0 3.67
Loaded : 加載class的數(shù)量
Bytes : class字節(jié)大小
Unloaded : 未加載class的數(shù)量
Bytes : 未加載class的字節(jié)大小
Time : 加載時(shí)間
jmap(JVM Memory Map)命令用于生成heap dump文件轰驳,如果不使用這個(gè)命令厚掷,還闊以使用-XX:+HeapDumpOnOutOfMemoryError參數(shù)來讓虛擬機(jī)出現(xiàn)OOM的時(shí)候·自動(dòng)生成dump文件。 jmap不僅能生成dump文件级解,還闊以查詢finalize執(zhí)行隊(duì)列冒黑、Java堆和永久代的詳細(xì)信息,如當(dāng)前使用率勤哗、當(dāng)前使用的是哪種收集器等抡爹。