1 Java基礎(chǔ)
1.1 編譯型語(yǔ)言VS解釋型語(yǔ)言
編譯型語(yǔ)言:程序在執(zhí)行之前需要一個(gè)專(zhuān)門(mén)的編譯過(guò)程,把程序編譯成為機(jī)器語(yǔ)言的文件,運(yùn)行時(shí)不需要重新翻譯,直接使用編譯的結(jié)果就行了。因此效率比較高。比如 C 語(yǔ)言。
解釋型語(yǔ)言:程序不需要編譯,程序在運(yùn)行時(shí)才翻譯成機(jī)器語(yǔ)言,每執(zhí)行一次都要翻譯一次。因此效率比較低。比如Basic語(yǔ)言,專(zhuān)門(mén)有一個(gè)解釋器能夠直接執(zhí)行Basic程序,每個(gè)語(yǔ)句都是執(zhí)行的時(shí)候才翻譯。
C語(yǔ)言是編譯型的。C程序——>機(jī)器語(yǔ)言(編譯)
Java比較特殊,Java程序也需要編譯,但是沒(méi)有直接編譯成機(jī)器語(yǔ)言,而是編譯成字節(jié)碼,然后用解釋方式執(zhí)行字節(jié)碼。 Java程序—— >字節(jié)碼(編譯)—— >機(jī)器語(yǔ)言(解釋?zhuān)?/p>
1.2 JVM工作原理
JVM 主要由 ClassLoader 和 執(zhí)行引擎 兩子系統(tǒng)組成,運(yùn)行數(shù)據(jù)區(qū)分為五個(gè)部分: 方法區(qū)、堆、棧、程序計(jì)數(shù)器、本地方法棧。其中的方法區(qū)和堆是所有線程共享的,JVM將臨時(shí)變量放在棧中,每個(gè)線程都有自己獨(dú)立的??臻g和程序計(jì)數(shù)器。
任何一個(gè)Java類(lèi)的main函數(shù)運(yùn)行都會(huì)創(chuàng)建一個(gè)JVM實(shí)例,JVM實(shí)例啟動(dòng)時(shí)默認(rèn)啟動(dòng)幾個(gè)守護(hù)線程,比如:垃圾回收的線程,而 main 方法的執(zhí)行是在一個(gè)單獨(dú)的非守護(hù)線程中執(zhí)行的。只要非守護(hù)線程結(jié)束JVM實(shí)例就銷(xiāo)毀了。
那么在Java類(lèi)main函數(shù)運(yùn)行過(guò)程中,JVM的工作原理如下:
- 根據(jù)系統(tǒng)環(huán)境變量,創(chuàng)建裝載JVM的環(huán)境與配置;
- 尋找JRE目錄,尋找jvm.dll,并裝載jvm.dll;
- 根據(jù)JVM的參數(shù)配置,如:內(nèi)存參數(shù),初始化jvm實(shí)例;
- JVM實(shí)例產(chǎn)生一個(gè) 引導(dǎo)類(lèi)加載器實(shí)例(Bootstrap Loader),加載Java核心庫(kù),然后引導(dǎo)類(lèi)加載器自動(dòng)加載 擴(kuò)展類(lèi)加載器(Extended Loader),加載Java擴(kuò)展庫(kù),最后擴(kuò)展類(lèi)加載器自動(dòng)加載 系統(tǒng)類(lèi)加載器(AppClass Loader),加載當(dāng)前的Java類(lèi);
- 當(dāng)前Java類(lèi)加載至內(nèi)存后,會(huì)經(jīng)過(guò) 驗(yàn)證、準(zhǔn)備、解析 三步,將Java類(lèi)中的 類(lèi)型信息、屬性信息、常量池 存放在方法區(qū)內(nèi)存中,方法指令直接保存到棧內(nèi)存中,如:main函數(shù);
- 執(zhí)行引擎開(kāi)始執(zhí)行棧內(nèi)存中指令,由于main函數(shù)是靜態(tài)方法,所以不需要傳入實(shí)例,在類(lèi)加載完畢之后,直接執(zhí)行main方法指令;
- main函數(shù)執(zhí)行主線程結(jié)束,隨之守護(hù)線程銷(xiāo)毀,最后JVM實(shí)例被銷(xiāo)毀;
1.3 JVM類(lèi)加載機(jī)制
類(lèi)加載是Java程序運(yùn)行的第一步,在java.lang包里有個(gè)ClassLoader類(lèi),ClassLoader 的基本目標(biāo)是 對(duì)類(lèi)的請(qǐng)求提供服務(wù),按需動(dòng)態(tài)裝載類(lèi)和資源 ,只有當(dāng)一個(gè)類(lèi)要使用 (1. Class.forName();2. 調(diào)用類(lèi)的靜態(tài)方法;3. 使用 new 關(guān)鍵字來(lái)實(shí)例化一個(gè)類(lèi)) 的時(shí)候,類(lèi)加載器才會(huì)加載這個(gè)類(lèi)并初始化。當(dāng)我們自定義類(lèi)加載器加載類(lèi)文件時(shí)(繼承自ClassLoader類(lèi),只需覆蓋 findClass方法,即可),其類(lèi)加載機(jī)制如下:
當(dāng)自定義類(lèi)加載器加載類(lèi)時(shí),會(huì)調(diào)用loadClass方法加載類(lèi),而由于類(lèi)加載的雙親委托模式,會(huì)將類(lèi)的加載代理給父類(lèi)加載器:系統(tǒng)類(lèi)加載器來(lái)完成,依次類(lèi)推至最頂層引導(dǎo)類(lèi)加載器加載,如果父類(lèi)加載器沒(méi)有加載到類(lèi),則最終返回由自定義類(lèi)加載器加載類(lèi),通過(guò)雙親委托模式,對(duì)于 Java 核心庫(kù)的類(lèi)的加載工作由引導(dǎo)類(lèi)加載器來(lái)統(tǒng)一完成,保證了 Java 應(yīng)用所使用的都是同一個(gè)版本的 Java 核心庫(kù)的類(lèi)。
需要說(shuō)明一下Java虛擬機(jī)是如何判定兩個(gè)Java 類(lèi)是相同的。Java 虛擬機(jī)不僅要看類(lèi)的全名是否相同,還要看加載此類(lèi)的類(lèi)加載器是否一樣。只有兩者都相同的情況,才認(rèn)為兩個(gè)類(lèi)是相同的。即便是同樣的字節(jié)代碼,被不同的類(lèi)加載器加載之后所得到的類(lèi),也是不同的。然而,類(lèi)加載器又分初始類(lèi)加載器和定義類(lèi)加載器,由于類(lèi)加載的代理模式,初始類(lèi)加載器并不一定是定義類(lèi)加載器,所以確切的說(shuō),判定兩個(gè) Java 類(lèi)是否相同的, 哪個(gè)類(lèi)加載器啟動(dòng)類(lèi)的加載過(guò)程并不重要,重要的是最終定義這個(gè)類(lèi)的加載器。
1.4 JVM內(nèi)存分配策略
JVM內(nèi)存主要是指運(yùn)行數(shù)據(jù)區(qū),粗略的分為:堆、棧,細(xì)致區(qū)分五部分:方法區(qū)、堆、棧、程序計(jì)數(shù)器、本地方法棧。
程序計(jì)數(shù)器:
線程私有,記錄線程所執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;如果正在執(zhí)行的是native方法,這個(gè)計(jì)數(shù)值則為空。唯一一個(gè)在Java虛擬機(jī)規(guī)范中沒(méi)有規(guī)定任何OutOfMemoryError情況的區(qū)域。Java虛擬機(jī)棧:
線程私有,描述Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀,用于存儲(chǔ) 局部變量表,操作數(shù)棧,動(dòng)態(tài)鏈接,方法出口 等信息。每一個(gè)方法從調(diào)用直至執(zhí)行完成的過(guò)程,就對(duì)應(yīng)這一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過(guò)程。棧幀是方法運(yùn)行時(shí)的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)。如果線程請(qǐng)求棧深度大于虛擬機(jī)所允許的深度,拋出StackOverflowError異常;如果虛擬機(jī)棧動(dòng)態(tài)擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存,拋出 OutOfMemoryError異常。本地方法棧:
線程私有,描述native方法執(zhí)行的內(nèi)存模型。有的虛擬機(jī)(如Sun HotSpot虛擬機(jī))直接就把 本地方法棧和虛擬機(jī)棧 合二為一。Java堆:
線程共享,存放對(duì)象實(shí)例及數(shù)組,是垃圾收集器管理的主要區(qū)域,采用分代收集策略,所以Java堆會(huì)細(xì)分為新生代和老年代。根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定,Java堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上連續(xù)的即可。如果堆中沒(méi)有內(nèi)存完成實(shí)例分配,并且堆也無(wú)法再擴(kuò)展時(shí),拋出OutOfMemoryError異常。-
方法區(qū):
線程共享,存儲(chǔ)已被虛擬機(jī)加載的類(lèi)信息、常量池、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分,但是它卻有一個(gè)別名叫Non-Heap(非堆),目的應(yīng)該是與Java堆區(qū)分開(kāi)來(lái)。如果方法區(qū)無(wú)法滿足內(nèi)存分配需求,拋出OutOfMemoryError異常。對(duì)于HotSpot虛擬機(jī),方法區(qū)被稱(chēng)為永久代,本質(zhì)上兩者不等價(jià),僅僅是因?yàn)镠otSpot虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)選擇把GC分代收集擴(kuò)至方法區(qū),或者說(shuō)使用永久代來(lái)實(shí)現(xiàn)方法區(qū)而已。JVM堆一般又可以分為以下三部分:Young 新生代、Tenured 老年代、Perm 永久代;
Perm永久代主要保存class,method,filed對(duì)象,這部分的空間一般不會(huì)溢出,除非一次性加載了很多的類(lèi),不過(guò)在涉及到熱部署的應(yīng)用服務(wù)器的時(shí)候,有時(shí)候會(huì)遇到java.lang.OutOfMemoryError : PermGen space 的錯(cuò)誤。
Tenured老年代主要保存生命周期長(zhǎng)的對(duì)象,多次未被GC掉的對(duì)象;
Young新生代主要保存新生成對(duì)象,根據(jù)JVM的策略,在經(jīng)過(guò)幾次垃圾收集后,而沒(méi)有被垃圾回收的對(duì)象將被移動(dòng)到Tenured區(qū)間。有時(shí)候該區(qū)經(jīng)常會(huì)遇到java.lang.OutOfMemoryError :Java heap space的錯(cuò)誤。
-Xms:指定了JVM初始啟動(dòng)以后 初始化內(nèi)存;
-Xmx:指定JVM堆得 最大內(nèi)存,在JVM啟動(dòng)以后,會(huì)分配-Xmx參數(shù)指定大小的內(nèi)存給JVM,但是不一定全部使用,JVM會(huì)根據(jù)-Xms參數(shù)來(lái)調(diào)節(jié)真正用于JVM的內(nèi)存;
-Xmn:參數(shù)設(shè)置了新生代的大??;老年代等于-Xmx減去-Xmn;
-XX:Xss:參數(shù)設(shè)置了永久代的大?。?/p>
直接內(nèi)存:
不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是Java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域。但是這部分內(nèi)存也被頻繁的使用,而且也可能導(dǎo)致OutOfMemoryError異常出現(xiàn)。例如:NIO類(lèi),引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的IO方式,它可以使用Native函數(shù)庫(kù)直接分配堆外內(nèi)存,然后通過(guò)一個(gè)存儲(chǔ)在Java堆中的DirectByteBuffer對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作。這樣能在一些場(chǎng)景中顯著提高性能,因?yàn)楸苊饬薐ava堆和Native堆中來(lái)回復(fù)制數(shù)據(jù)。直接內(nèi)存雖然不會(huì)受到Java堆大小的限制,但是,既然是內(nèi)存,仍會(huì)受到本機(jī)總內(nèi)存大小及處理器尋址空間的限制。-
JVM參數(shù):
-XX:+HeapDumpOnOutOfMemoryError:可讓虛擬機(jī)在內(nèi)存溢出異常時(shí)Dump出當(dāng)前的內(nèi)存堆轉(zhuǎn)儲(chǔ)快照以便事后分析;
-Xss:設(shè)置線程棧大小;
-XX:PermSize:設(shè)置永久代初始大小;
-XX:MaxPermSize:設(shè)置永久代最大大?。?/p>
-XX:MaxDirectMemorySize:設(shè)置直接內(nèi)存大小,默認(rèn)與Java堆最大值一樣;
1.5 對(duì)象是否可回收
-
引用計(jì)數(shù)算法
存儲(chǔ)對(duì)特定對(duì)象的所有引用數(shù),也就是說(shuō),當(dāng)應(yīng)用程序創(chuàng)建引用以及引用超出范圍時(shí),JVM必須適當(dāng)增減引用數(shù)。當(dāng)某對(duì)象的引用數(shù)為0時(shí),便可以進(jìn)行垃圾收集。優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單、效率高;
缺點(diǎn):很難解決對(duì)象之間相互引用問(wèn)題;
可達(dá)性分析算法
通過(guò)一系列的稱(chēng)為“GC Roots”的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)開(kāi)始向下搜索,搜索所走的路徑稱(chēng)為引用鏈,當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連時(shí),則證明此對(duì)象是不可用的。
可作為GC Roots的對(duì)象包括:
- 虛擬機(jī)棧(棧幀的本地變量表)中引用的對(duì)象;
- 方法區(qū)中類(lèi)靜態(tài)屬性引用的對(duì)象;
- 方法區(qū)中常量引用的對(duì)象;
- 本地方法棧中JNI(即一般說(shuō)的是native方法)引用的對(duì)象;
1.6 四種引用
- 強(qiáng)引用:只要強(qiáng)引用還存在,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象。
- 軟引用:對(duì)軟引用關(guān)聯(lián)著的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會(huì)把這些對(duì)象列進(jìn)回收范圍之中進(jìn)行第二次回收。
- 弱引用:對(duì)弱引用關(guān)聯(lián)著的對(duì)象,只能生存到下一次垃圾收集發(fā)生之前。
- 虛引用:對(duì)象是否有虛引用,完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響,也無(wú)法通過(guò)虛引用來(lái)取得對(duì)象實(shí)例。關(guān)聯(lián)虛引用唯一目的就是能在對(duì)象被收集器回收時(shí)收到系統(tǒng)通知。
1.7 finalize()方法
任何一個(gè)對(duì)象的finalize()方法都僅會(huì)被系統(tǒng)自動(dòng)調(diào)用一次。如果對(duì)象面臨下一次回收,它的finalize()方法不會(huì)被再次執(zhí)行。建議避免使用該方法。
1.8 JVM垃圾回收策略
GC即垃圾收集機(jī)制是指JVM用于釋放那些不再使用的對(duì)象所占用的內(nèi)存。常用機(jī)制:
-
標(biāo)記-清除算法【適用于老年代】
首先根據(jù)可達(dá)性分析算法,標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收所有標(biāo)記的對(duì)象。缺點(diǎn):效率問(wèn)題:標(biāo)記、清除兩個(gè)過(guò)程效率都低;空間問(wèn)題:標(biāo)記清除之后產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會(huì)導(dǎo)致以后在程序運(yùn)行過(guò)程中,需要分配大對(duì)象時(shí),無(wú)法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動(dòng)作。
-
復(fù)制算法【空間換時(shí)間,適用于對(duì)象存活率低的新生代】
將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)一塊的內(nèi)存用完了,就將還存活著的對(duì)象復(fù)制到另一塊上面,然后再把已使用過(guò)的內(nèi)存空間一次清理掉。優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單、效率高、無(wú)內(nèi)存碎片;
缺點(diǎn):內(nèi)存使用率低,太浪費(fèi)。
由于新生代中的對(duì)象98%是朝生夕死的,所以 將內(nèi)存分為一塊較大的內(nèi)存、兩塊較小的內(nèi)存。每次使用一塊大內(nèi)存和一塊小內(nèi)存,當(dāng)垃圾回收時(shí),會(huì)將該大內(nèi)存和小內(nèi)存中存活的對(duì)象一次性的復(fù)制到另外一塊小內(nèi)存中,最后清理掉該大內(nèi)存、小內(nèi)存。但是如果對(duì)象的存活率較高,那么當(dāng)復(fù)制對(duì)象至另一塊小內(nèi)存時(shí),該小內(nèi)存空間會(huì)不夠用,則需要依賴(lài)其他內(nèi)存(老年代)進(jìn)行分配擔(dān)保。
當(dāng)對(duì)象的存活率較高時(shí),復(fù)制算法要進(jìn)行較多的復(fù)制操作,效率會(huì)變低。
-
標(biāo)記-整理算法【適用于老年代】
標(biāo)記過(guò)程與“標(biāo)記-清除”算法一樣,唯一區(qū)別是在后續(xù)步驟不是直接對(duì)內(nèi)存進(jìn)行清除,而是先讓所有活著的對(duì)象都向一端移動(dòng),然后直接清除掉端邊界以外的內(nèi)存。
-
分代收集策略
一般是把Java堆分成 新生代和老年代,這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴ā?/p>新生代中對(duì)象存活率低,采用復(fù)制算法,有老年代對(duì)它進(jìn)行分配擔(dān)保。
老年代中對(duì)象存活率高,無(wú)額外空間對(duì)它進(jìn)行分配擔(dān)保,必須采用 “標(biāo)記-清除”或“標(biāo)記-整理”算法。
-
回收方法區(qū)(永久代)
很多人認(rèn)為方法區(qū)(永久代)是沒(méi)有垃圾收集的,Java虛擬機(jī)規(guī)范中確實(shí)說(shuō)過(guò)可以不要求虛擬機(jī)在方法區(qū)實(shí)現(xiàn)垃圾收集,況且在方法區(qū)中進(jìn)行垃圾收集性?xún)r(jià)比很低;永久代的垃圾收集主要回收兩方面: 廢棄常量和無(wú)用的類(lèi);
廢棄常量:判斷常量池的對(duì)象是否還存在任何引用;
無(wú)用的類(lèi):(1)該類(lèi)的所有實(shí)例都被回收;(2)該類(lèi)的ClassLoader已被回收;(3)該類(lèi)的Class對(duì)象沒(méi)有任何引用;
1.9 垃圾收集器
JDK1.7 Update14之后的HotSpot虛擬機(jī)正式提供了G1收集器;
Serial收集器:
單線程、Stop The Wold、Client模式默認(rèn)新生代收集器、 復(fù)制算法;-
ParNew收集器:
Serial多線程版、并行、Stop The Wold、Server模式默認(rèn)新生代收集器、只有該收集器能與CMS收集器配合、 復(fù)制算法;ParNew收集器也是使用:
-XX:+UseConcMarkSweepGC 選項(xiàng)后的默認(rèn)新生代收集器;
-XX:+UserParNewGC 選項(xiàng)來(lái)強(qiáng)制指定它;
-XX:ParallelGCThreads 參數(shù)來(lái)限制垃圾收集的線程數(shù),默認(rèn)開(kāi)啟的線程數(shù)與CPU的數(shù)量相等; -
Parallel Scavenge收集器:
多線程、并行、Stop The Wold、 新生代收集器、Server模式、 復(fù)制算法; CMS等收集器的關(guān)注點(diǎn)是 盡可能地縮短垃圾收集時(shí)用戶線程的停頓時(shí)間,而 該收集器關(guān)注的是達(dá)到一個(gè)可控制的吞吐量;所謂吞吐量就是CPU用于運(yùn)行用戶代碼的時(shí)間與CPU總消耗時(shí)間的比值,即吞吐量=運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間+垃圾收集時(shí)間),吞吐量與垃圾收集時(shí)間成反比;
停頓時(shí)間越短就越適合需要與用戶交互的程序,而高吞吐量則可以高效率的利用CPU,盡快完成程序的運(yùn)算任務(wù),主要適合在后臺(tái)運(yùn)算而不需要太多交互的任務(wù);
-XX:MaxGCPauseMillis:設(shè)置最大垃圾收集停頓時(shí)間;
-XX:GCTimeRatio:設(shè)置吞吐量大小,大于0且小于100的整數(shù),默認(rèn)為99;
-XX:UseAdaptiveSizePolicy:設(shè)置打開(kāi)GC自適應(yīng)的調(diào)節(jié)策略,以達(dá)到最大的吞吐量; Serial Old收集器:
單線程、Stop The Wold、 老年代、 標(biāo)記-整理算法、Client模式;作為CMS收集器的備選方案,在并發(fā)收集發(fā)生Concurrent Mode Failure時(shí)使用;Parallel Old收集器:
多線程、Stop The Wold、 老年代、 標(biāo)記-整理算法、Server模式、并行;適合與Parallel Scavenge配合使用;-
CMS收集器:
多線程、Stop The Wold、 老年代、 標(biāo)記-清除算法、Server模式、并發(fā); 缺點(diǎn):內(nèi)存碎片;-XX:+UseCMSCompactAtFullCollection:默認(rèn)開(kāi)啟,用于在CMS收集器頂不住進(jìn)行Full GC時(shí)開(kāi)啟內(nèi)存碎片的合并整理過(guò)程,內(nèi)存整理的過(guò)程是無(wú)法并發(fā),會(huì)導(dǎo)致停頓時(shí)間變長(zhǎng);
-XX:CMSFullGCsBeforeCompaction:用于設(shè)置執(zhí)行多少次不壓縮的Full GC后,跟著來(lái)一次帶壓縮的GC,默認(rèn)為0,表示每次進(jìn)行Full GC時(shí)都進(jìn)行碎片整理;
-
G1收集器:
多線程、新老年代、復(fù)制+標(biāo)記-整理算法、并發(fā)、Server模式、GC整個(gè)堆; 特點(diǎn):并行與并發(fā)、分代收集、空間整理、可預(yù)測(cè)的停頓;G1雖然保存了新老年代的概念,但已經(jīng)不是物理分割了,他們都是一部分Region(不需要連續(xù))的集合;
args="-Dfile.encoding=UTF-8 -J-server -J-Xss128k -J-XX:ThreadStackSize=128 -J-XX:PermSize=64m -J-XX:MaxPermSize=256m -J-verbose:gc -J-XX:+PrintGCDetails -J-XX:+PrintGCTimeStamps -Djava.library.path=${RESIN_HOME}/libexec:/opt/j2sdk/lib:/usr/lib64 -Djmagick.systemclassloader=false -DNO_TIMEOUT" args="$args -Xdebug - Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=9090" args="$args -Xmn5g -Xms10g -Xmx10g" args="$args -J-XX:+UseParNewGC -J-XX:+UseConcMarkSweepGC"Client模式:默認(rèn)-XX:+UseSerialGC,Serial + Serial Old;
Server模式:默認(rèn)-XX:+UseParallelGC,Parallel Scavenge + Serial Old;-XX:+PrintGC 打印GC信息 -XX:+PrintGCDetails 打印較為詳細(xì)的GC信息 -XX:+PrintGCTimeStamps 打印GC時(shí)間戳,相對(duì)于應(yīng)用程序啟動(dòng)的時(shí)間Serial:串行收集器,當(dāng)進(jìn)行垃圾收集時(shí),會(huì)暫停所有線程;
Parallel:并行收集器,是串行收集器的多線程版本,多CPU下;
ParallelOld:老年代的Parallel版本;
ConcMarkSweep:簡(jiǎn)稱(chēng)CMS,是并發(fā)收集器,將部分操作與用戶線程并發(fā)執(zhí)行;
CMSIncrementalMode:CMS收集器變種,屬增量式垃圾收集器,在并發(fā)標(biāo)記和并發(fā)清理時(shí)交替運(yùn)行垃圾收集器和用戶線程;
G1:面向服務(wù)器端應(yīng)用的垃圾收集器,計(jì)劃未來(lái)替代CMS收集器;

1.10 JVM參數(shù)配置
-
跟 Java 堆大小相關(guān)的 JVM 內(nèi)存參數(shù)
下面三個(gè) JVM 參數(shù)用來(lái)指定堆的初始大小和最大值以及堆棧大?。?/p>
-Xms 設(shè)置 Java 堆的初始化大小
-Xmx 設(shè)置最大的 Java 堆大小
-Xss 設(shè)置Java線程棧大小 -
關(guān)于打印垃圾收集器詳情的 JVM 參數(shù)
-verbose:gc 記錄 GC 運(yùn)行以及運(yùn)行時(shí)間,一般用來(lái)查看 GC 是否是應(yīng)用的瓶頸
-XX:+PrintGCDetails 記錄 GC 運(yùn)行時(shí)的詳細(xì)數(shù)據(jù)信息,包括新生成對(duì)象的占用內(nèi)存大小以及耗費(fèi)時(shí)間等
-XX:+PrintGCTimeStamps 打印垃圾收集的時(shí)間戳 -
設(shè)置 Java 垃圾收集器行為的 JVM 參數(shù)
-XX:+UseParallelGC 使用并行垃圾收集
-XX:+UseConcMarkSweepGC 使用并發(fā)標(biāo)志掃描收集 (Introduced in 1.4.1)
-XX:+UseSerialGC 使用串行垃圾收集 (Introduced in 5.0.)需要提醒的是,但你的應(yīng)用是非常關(guān)鍵的、交易非常頻繁應(yīng)用時(shí),應(yīng)該謹(jǐn)慎使用 GC 參數(shù),因?yàn)?GC 操作是耗時(shí)的,你需要在這之中找到平衡點(diǎn)。
-
JVM調(diào)試參數(shù),用于遠(yuǎn)程調(diào)試
-Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000 -
用于修改 Perm Gen 大小的 JVM 參數(shù)
下面的這三個(gè)參數(shù)主要用來(lái)解決 JVM 錯(cuò)誤:java.lang.OutOfMemoryError:Perm Gen Space
-XX:PermSize and MaxPermSize -XX:NewRatio=2 Ratio of new/old generation sizes. -XX:MaxPermSize=64m Size of the Permanent Generation. -
用來(lái)跟蹤類(lèi)加載和卸載的信息
-XX:+TraceClassLoading 和 -XX:+TraceClassUnloading 用來(lái)打印類(lèi)被加載和卸載的過(guò)程信息,這個(gè)用來(lái)診斷應(yīng)用的內(nèi)存泄漏問(wèn)題非常有用。
-
用于調(diào)試目的的 JVM 開(kāi)關(guān)參數(shù)
-XX:HeapDumpPath=./java_pid.hprof Path to directory or file name for heap dump. -XX:+PrintConcurrentLocks Print java.util.concurrent locks in Ctrl-Break thread dump. -XX:+PrintCommandLineFlags Print flags that appeared on the command line.如果你的應(yīng)用追求低停頓,那G1現(xiàn)在已經(jīng)可以作為一個(gè)可嘗試的選擇;如果你的應(yīng)用追求吞吐量,那G1并不會(huì)為你帶來(lái)什么特別的好處;
1.11 Stop The Wold
可達(dá)性分析必須在一個(gè)能確保一致性的快照中進(jìn)行,一致性是指在整個(gè)分析過(guò)程中整個(gè)執(zhí)行系統(tǒng)必須凍結(jié),不可以出現(xiàn)分析過(guò)程中對(duì)象引用關(guān)系還在不斷變化的情況,該點(diǎn)不滿足的話可達(dá)性分析結(jié)果不準(zhǔn)確。所以這點(diǎn)是導(dǎo)致GC進(jìn)行時(shí)必須停頓所有線程的原因。
1.12 新生代GC、老年代GC
新生代GC:Minor GC,指發(fā)生在新生代的垃圾收集動(dòng)作;
老年代GC:Major GC/Full GC,指發(fā)生在老年代垃圾收集動(dòng)作,出現(xiàn)了Major GC,經(jīng)常會(huì)伴隨至少一次的Minor GC。會(huì)發(fā)生Stop The Wold。
1.13 對(duì)象進(jìn)入老年代
大對(duì)象:通過(guò)參數(shù) -XX:PretenureSizeThreshold=3145287,令大于這個(gè)設(shè)置值的對(duì)象直接在老年代分配。以避免大對(duì)象在新生代分配,從而觸發(fā)新生代GC。
多次GC仍存活的對(duì)象:當(dāng)對(duì)象每”熬過(guò)“一次Minor GC,對(duì)象年齡就增加1歲,當(dāng)對(duì)象年齡增加到一定程度(默認(rèn)15歲),就會(huì)晉升到老年代中。對(duì)象晉升老年代的年齡閥值,可以通過(guò)參數(shù)-XX:MaxTenurigThreshold設(shè)置。
動(dòng)態(tài)對(duì)象年齡判定:虛擬機(jī)并不是永遠(yuǎn)地要求對(duì)象年齡必須達(dá)到MaxTenurigThreshold閥值才能晉升到老年代,如果在Survivor空間中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代,無(wú)須等到MaxTenurigThreshold閥值。
新生代—分配擔(dān)保:由于新生代采用復(fù)制收集算法,當(dāng)新生代Minor GC 時(shí),如果存活對(duì)象的大小大于Survivor,依據(jù)分配擔(dān)保策略會(huì)將Survivor無(wú)法容納的對(duì)象直接存入老年代。如果老年代仍不能夠存放剩余的對(duì)象,則會(huì)發(fā)生Major GC/Full GC,就會(huì)Stop The Wold。
1.14 JVM常量池機(jī)制
常量池其實(shí)就是方法區(qū)一個(gè)內(nèi)存空間,虛擬機(jī)必須為每個(gè)被裝載的類(lèi)型維護(hù)一個(gè)常量池。常量池就是該類(lèi)型所用到常量的一個(gè)有序集和。以字符串為例,在Java源代碼中的每一個(gè)字面值字符串,都會(huì)在編譯成class文件階段,形成標(biāo)志號(hào)為 8(CONSTANT_String_info)的常量表 。當(dāng)JVM加載 class文件的時(shí)候,會(huì)為對(duì)應(yīng)的常量池建立一個(gè)內(nèi)存數(shù)據(jù)結(jié)構(gòu),并存放在方法區(qū)中。
如下代碼:
public class Test{
private String str="我們"。
}
將Test編譯之后形成class文件,那么在class文件中"我們"會(huì)以一種
CONSTANT_UTF8_info 表的形式存在,字節(jié)序列如下:1 0 6 230 136 145 228 187 172 1表示常量表的類(lèi)型,0 6表示有6個(gè)字節(jié)的長(zhǎng)度。后面6個(gè)字節(jié)是UTF-8編碼的“我們”。當(dāng)JVM運(yùn)行的時(shí)候會(huì)將這些常量池的信息加載進(jìn)方法區(qū)。也就是說(shuō)在運(yùn)行過(guò)程中內(nèi)存存儲(chǔ)的"我們"是UTF-8編碼的。
1.15 為什么使用JVM
Java語(yǔ)言的一個(gè)非常重要的特點(diǎn)就是 平臺(tái)無(wú)關(guān)性。而使用JVM是實(shí)現(xiàn)這一特點(diǎn)的關(guān)鍵。一般的高級(jí)語(yǔ)言如果要在不同的平臺(tái)上運(yùn)行,至少需要編譯成不同的目標(biāo)代碼。而引入JVM后,Java語(yǔ)言在不同平臺(tái)上運(yùn)行時(shí)不需要重新編譯。Java語(yǔ)言使用JVM屏蔽了與具體平臺(tái)相關(guān)的信息,使得Java語(yǔ)言編譯程序只需生成在JVM虛擬機(jī)上運(yùn)行的目標(biāo)代碼(字節(jié)碼),就可以在多種平臺(tái)上不加修改地運(yùn)行。JVM在執(zhí)行字節(jié)碼時(shí),把字節(jié)碼解釋成具體平臺(tái)上的機(jī)器指令執(zhí)行。
1.16 java.lang.OutOfMemoryError: unable to create new native thread
這個(gè)異常問(wèn)題本質(zhì)原因是我們 創(chuàng)建了太多的線程,而能創(chuàng)建的線程數(shù)是有限制的,導(dǎo)致了異常的發(fā)生。能創(chuàng)建的線程數(shù)的具體計(jì)算公式如下:
(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads
MaxProcessMemory:指的是一個(gè)進(jìn)程的最大內(nèi)存
JVMMemory:JVM內(nèi)存
ReservedOsMemory:保留的操作系統(tǒng)內(nèi)存
ThreadStackSize:線程棧的大小
在java語(yǔ)言里, 當(dāng)你創(chuàng)建一個(gè)線程的時(shí)候,虛擬機(jī)會(huì)在JVM內(nèi)存創(chuàng)建一個(gè)Thread對(duì)象同時(shí)創(chuàng)建一個(gè)操作系統(tǒng)線程,而這個(gè)系統(tǒng)線程的內(nèi)存用的不是JVMMemory,而是系統(tǒng)中剩下的內(nèi)存(MaxProcessMemory - JVMMemory - ReservedOsMemory),可以下面方法解決問(wèn)題:
(1) 通過(guò)設(shè)置 -Xmx512m 減少JVM Heap size;
(2) 通過(guò)設(shè)置 -Xss64k 減少線程占用的Stack size;
(3) 增加操作系統(tǒng)內(nèi)存;