1.數(shù)據(jù)類型
java虛擬機(jī)中,數(shù)據(jù)類型可以分為兩類:基本類型和引用類型。
基本類型的變量保存原始值,即:它代表的值就是數(shù)值本身,而引用類型的變量保存引用值。
2. 堆(heap)與棧(stack)
概括: ?
1.棧是運(yùn)行時(shí)的單位 ,?而堆是存儲(chǔ)的單元。
2.棧解決程序的運(yùn)行問題,即程序如何執(zhí)行,或者說如何處理數(shù)據(jù),堆解決的是數(shù)據(jù)存儲(chǔ)的問題,即數(shù)據(jù)怎么放,放在哪兒。
在java中一個(gè)線程就會(huì)相應(yīng)有一個(gè)線程棧與之對應(yīng),這點(diǎn)很容易理解,因?yàn)椴煌木€程執(zhí)行邏輯有所不同,因此需要一個(gè)獨(dú)立的線程棧。而堆則是所有線程共享的。
?疑問一:為什么要把堆和棧區(qū)分出來呢?棧中不是也可以存儲(chǔ)數(shù)據(jù)嗎?
???? 1. 從軟件設(shè)計(jì)的角度看,棧代表了處理邏輯,而堆代表了數(shù)據(jù)。這樣分開,使得處理邏輯更為清晰。分而治之的思想。這種隔離、模塊化的思想在軟件設(shè)計(jì)的方方面面都有體現(xiàn)。
???? 2.堆與棧的分離,使得堆中的內(nèi)容可以被多個(gè)棧共享(也可以理解為多個(gè)線程訪問同一個(gè)對象)。
? ? ? ? 好處: ?a.提供了一種有效的數(shù)據(jù)交互方式(如:共享內(nèi)存)
????????????????????b.堆中的共享常量和緩存可以被所有棧訪問,節(jié)省了空間。
???? 3. 棧因?yàn)檫\(yùn)行時(shí)的需要,比如保存系統(tǒng)運(yùn)行的上下文,需要進(jìn)行地址段的劃分。由于棧只能向上增長,因此就會(huì)限制住棧存儲(chǔ)內(nèi)容的能力,而堆不同,堆中的對象是可以根據(jù)需要?jiǎng)討B(tài)增長的,
因此棧和堆的拆分使得動(dòng)態(tài)增長成為可能,相應(yīng)棧中只需記錄堆中的一個(gè)地址即可。
4.?面向?qū)ο缶褪嵌押蜅5耐昝澜Y(jié)合。
? ? ? ? 其實(shí),面向?qū)ο蠓绞降某绦蚺c以前結(jié)構(gòu)化的程序在執(zhí)行上沒有任何區(qū)別。
? ? ? ? 但是,面向?qū)ο蟮囊?,使得對待問題的思考方式發(fā)生了改變,而更接近于自然方式的思考。
? ? ? ? 當(dāng)我們把對象拆開,你會(huì)發(fā)現(xiàn),對象的屬性其實(shí)就是數(shù)據(jù),存放在堆中;
? ? ? ? 而對象的行為(方法),就是運(yùn)行邏輯,放在棧中。
? ? ? ? 我們在編寫對象的時(shí)候,其實(shí)就是編寫了數(shù)據(jù)結(jié)構(gòu),也編寫了處理數(shù)據(jù)的邏輯。不得不承認(rèn),面向?qū)ο蟮脑O(shè)計(jì),確實(shí)很美。
?疑問二:堆中存什么?棧中存什么?
1. 棧存儲(chǔ)的信息都是跟當(dāng)前線程(或程序)相關(guān)的信息。(局部變量、程序運(yùn)行狀態(tài)、方法、方法返回值)等,棧中存的是基本數(shù)據(jù)類型和堆中對象的引用。一個(gè)對象的大小是不可估計(jì)的,或者說是可以動(dòng)態(tài)變化的,但是在棧中,一個(gè)對象只對應(yīng)了一個(gè)4byte的引用(堆棧分離的好處)。
2. 堆只負(fù)責(zé)存儲(chǔ)對象信息。
疑問三:? 為什么不把基本類型放堆中呢?
1. 其占用的空間一般是1~8個(gè)字節(jié)---需要空間比較少,
2.而且因?yàn)槭腔绢愋停圆粫?huì)出現(xiàn)動(dòng)態(tài)增長的情況---長度固定,因此棧中存儲(chǔ)就夠了,如果把它存在堆中是沒有什么意義的(還會(huì)浪費(fèi)空間,后面說明??)。
3.什么情況下觸發(fā)垃圾回收
由于對象進(jìn)行了分代處理,因此垃圾回收區(qū)域、時(shí)間也不一樣。GC有兩種類型:Scavenge GC 和 Full GC
Scavenge GC
一般情況下,當(dāng)新對象生成,并且在Eden申請空間失敗時(shí),就會(huì)觸發(fā)Scavenge GC,對Eden區(qū)域進(jìn)行GC,清除非存活對象,并且把尚且存活的對象移動(dòng)到Survivor區(qū)。然后整理Survivor的兩個(gè)區(qū)。這種方式的GC是對年輕代的Eden區(qū)進(jìn)行,不會(huì)影響到年老代。因?yàn)榇蟛糠謱ο蠖际菑腅den區(qū)開始的,同時(shí)Eden區(qū)不會(huì)分配的很大,所以Eden區(qū)的GC會(huì)頻繁進(jìn)行。因而,一般在這里需要使用速度快、效率高的算法,使Eden區(qū)能盡快空閑出來。
Full GC
對整個(gè)堆進(jìn)行整理,包括Young、Tenured 和 Perm。Full GC 因?yàn)樾枰獙φ麄€(gè)堆進(jìn)行回收,所以比 Scavenge GC 要慢,因此應(yīng)該盡可能減少 Full GC 的次數(shù)。在對JVM調(diào)優(yōu)的過程中,很大一部分工作就是對于 Full GC 的調(diào)節(jié)。
有如下原因可能導(dǎo)致Full GC:
.年老代(Tenured)被寫滿
???????? . 持久代(Perm)被寫滿
?????????. System.gc()被顯式調(diào)用
?????????. 上一次GC之后Heap的各域分配策略動(dòng)態(tài)變化
4.回收器選擇
JVM給了三種選擇:串行收集器、并行收集器、并發(fā)收集器,但是串行收集器只適用于小數(shù)據(jù)量的情況,所以這里的選擇主要針對并行收集器和并發(fā)收集器。默認(rèn)情況下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在啟動(dòng)時(shí)加入相應(yīng)參數(shù)。JDK5.0以后,JVM會(huì)根據(jù)當(dāng)前系統(tǒng)配置進(jìn)行判斷。
吞吐量優(yōu)先的并行收集器
如上文所述,并行收集器主要以到達(dá)一定的吞吐量為目標(biāo),適用于科學(xué)技術(shù)和后臺(tái)處理等。
典型配置:
java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20-XX:+UseParallelGC:選擇垃圾收集器為并行收集器。此配置僅對年輕代有效。即上述配置下,年輕代使用并發(fā)收集,而年老代仍舊使用串行收集。-XX:ParallelGCThreads=20:配置并行收集器的線程數(shù),即:同時(shí)多少個(gè)線程一起進(jìn)行垃圾回收。此值最好配置與處理器數(shù)目相等。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20-XX:+UseParallelOldGC-XX:+UseParallelOldGC:配置年老代垃圾收集方式為并行收集。JDK6.0支持對年老代并行收集。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC? -XX:MaxGCPauseMillis=100-XX:MaxGCPauseMillis=100:設(shè)置每次年輕代垃圾回收的最長時(shí)間,如果無法滿足此時(shí)間,JVM會(huì)自動(dòng)調(diào)整年輕代大小,以滿足此值。
n java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC? -XX:MaxGCPauseMillis=100-XX:+UseAdaptiveSizePolicy-XX:+UseAdaptiveSizePolicy:設(shè)置此選項(xiàng)后,并行收集器會(huì)自動(dòng)選擇年輕代區(qū)大小和相應(yīng)的Survivor區(qū)比例,以達(dá)到目標(biāo)系統(tǒng)規(guī)定的最低相應(yīng)時(shí)間或者收集頻率等,此值建議使用并行收集器時(shí),一直打開。
響應(yīng)時(shí)間優(yōu)先的并發(fā)收集器
如上文所述,并發(fā)收集器主要是保證系統(tǒng)的響應(yīng)時(shí)間,減少垃圾收集時(shí)的停頓時(shí)間。適用于應(yīng)用服務(wù)器、電信領(lǐng)域等。
典型配置:
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20-XX:+UseConcMarkSweepGC -XX:+UseParNewGC-XX:+UseConcMarkSweepGC:設(shè)置年老代為并發(fā)收集。測試中配置這個(gè)以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此時(shí)年輕代大小最好用-Xmn設(shè)置。-XX:+UseParNewGC:設(shè)置年輕代為并行收集??膳cCMS收集同時(shí)使用。JDK5.0以上,JVM會(huì)根據(jù)系統(tǒng)配置自行設(shè)置,所以無需再設(shè)置此值。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5-XX:+UseCMSCompactAtFullCollection-XX:CMSFullGCsBeforeCompaction:由于并發(fā)收集器不對內(nèi)存空間進(jìn)行壓縮、整理,所以運(yùn)行一段時(shí)間以后會(huì)產(chǎn)生“碎片”,使得運(yùn)行效率降低。此值設(shè)置運(yùn)行多少次GC以后對內(nèi)存空間進(jìn)行壓縮、整理。-XX:+UseCMSCompactAtFullCollection:打開對年老代的壓縮。可能會(huì)影響性能,但是可以消除碎片
5.常見配置匯總
堆設(shè)置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:設(shè)置年輕代大小
-XX:NewRatio=n:設(shè)置年輕代和年老代的比值。如:為3,表示年輕代與年老代比值為1:3,年輕代占整個(gè)年輕代年老代和的1/4
-XX:SurvivorRatio=n:年輕代中Eden區(qū)與兩個(gè)Survivor區(qū)的比值。注意Survivor區(qū)有兩個(gè)。如:3,表示Eden:Survivor=3:2,一個(gè)Survivor區(qū)占整個(gè)年輕代的1/5
-XX:MaxPermSize=n:設(shè)置持久代大小
收集器設(shè)置
-XX:+UseSerialGC:設(shè)置串行收集器
-XX:+UseParallelGC:設(shè)置并行收集器
-XX:+UseParalledlOldGC:設(shè)置并行年老代收集器
-XX:+UseConcMarkSweepGC:設(shè)置并發(fā)收集器
垃圾回收統(tǒng)計(jì)信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并行收集器設(shè)置
-XX:ParallelGCThreads=n:設(shè)置并行收集器收集時(shí)使用的CPU數(shù)。并行收集線程數(shù)。
-XX:MaxGCPauseMillis=n:設(shè)置并行收集最大暫停時(shí)間
-XX:GCTimeRatio=n:設(shè)置垃圾回收時(shí)間占程序運(yùn)行時(shí)間的百分比。公式為1/(1+n)
并發(fā)收集器設(shè)置
-XX:+CMSIncrementalMode:設(shè)置為增量模式。適用于單CPU情況。
-XX:ParallelGCThreads=n:設(shè)置并發(fā)收集器年輕代收集方式為并行收集時(shí),使用的CPU數(shù)。并行收集線程數(shù)。
6.JVM調(diào)優(yōu)工具
Jconsole,jProfile,VisualVM
Jconsole : jdk自帶,功能簡單,但是可以在系統(tǒng)有一定負(fù)荷的情況下使用。對垃圾回收算法有很詳細(xì)的跟蹤。
JProfiler:商業(yè)軟件,需要付費(fèi)。功能強(qiáng)大。
VisualVM:JDK自帶,功能強(qiáng)大,與JProfiler類似。推薦。
7.內(nèi)存泄漏檢查
內(nèi)存泄漏是比較常見的問題,而且解決方法也比較通用,這里可以重點(diǎn)說一下,而線程、熱點(diǎn)方面的問題則是具體問題具體分析了。
內(nèi)存泄漏一般可以理解為系統(tǒng)資源(各方面的資源,堆、棧、線程等)在錯(cuò)誤使用的情況下,導(dǎo)致使用完畢的資源無法回收(或沒有回收),從而導(dǎo)致新的資源分配請求無法完成,引起系統(tǒng)錯(cuò)誤。
內(nèi)存泄漏對系統(tǒng)危害比較大,因?yàn)樗梢灾苯訉?dǎo)致系統(tǒng)的崩潰。
需要區(qū)別一下,內(nèi)存泄漏和系統(tǒng)超負(fù)荷兩者是有區(qū)別的,雖然可能導(dǎo)致的最終結(jié)果是一樣的。內(nèi)存泄漏是用完的資源沒有回收引起錯(cuò)誤,而系統(tǒng)超負(fù)荷則是系統(tǒng)確實(shí)沒有那么多資源可以分配了(其他的資源都在使用)。
年老代堆空間被占滿
異常: java.lang.OutOfMemoryError: Java heap space
說明:

這是最典型的內(nèi)存泄漏方式,簡單說就是所有堆空間都被無法回收的垃圾對象占滿,虛擬機(jī)無法再在分配新空間。
如上圖所示,這是非常典型的內(nèi)存泄漏的垃圾回收情況圖。所有峰值部分都是一次垃圾回收點(diǎn),所有谷底部分表示是一次垃圾回收后剩余的內(nèi)存。連接所有谷底的點(diǎn),可以發(fā)現(xiàn)一條由底到高的線,這說明,隨時(shí)間的推移,系統(tǒng)的堆空間被不斷占滿,最終會(huì)占滿整個(gè)堆空間。因此可以初步認(rèn)為系統(tǒng)內(nèi)部可能有內(nèi)存泄漏。(上面的圖僅供示例,在實(shí)際情況下收集數(shù)據(jù)的時(shí)間需要更長,比如幾個(gè)小時(shí)或者幾天)
解決:
這種方式解決起來也比較容易,一般就是根據(jù)垃圾回收前后情況對比,同時(shí)根據(jù)對象引用情況(常見的集合對象引用)分析,基本都可以找到泄漏點(diǎn)。
持久代被占滿
異常:java.lang.OutOfMemoryError: PermGen space
說明:
Perm空間被占滿。無法為新的class分配存儲(chǔ)空間而引發(fā)的異常。這個(gè)異常以前是沒有的,但是在Java反射大量使用的今天這個(gè)異常比較常見了。主要原因就是大量動(dòng)態(tài)反射生成的類不斷被加載,最終導(dǎo)致Perm區(qū)被占滿。
更可怕的是,不同的classLoader即便使用了相同的類,但是都會(huì)對其進(jìn)行加載,相當(dāng)于同一個(gè)東西,如果有N個(gè)classLoader那么他將會(huì)被加載N次。因此,某些情況下,這個(gè)問題基本視為無解。當(dāng)然,存在大量classLoader和大量反射類的情況其實(shí)也不多。
解決:
-XX:MaxPermSize=16m
換用JDK。比如JRocket。
堆棧溢出
異常:java.lang.StackOverflowError
說明:這個(gè)就不多說了,一般就是遞歸沒返回,或者循環(huán)調(diào)用造成
線程堆棧滿
異常:Fatal: Stack size too small
說明:java中一個(gè)線程的空間大小是有限制的。JDK5.0以后這個(gè)值是1M。與這個(gè)線程相關(guān)的數(shù)據(jù)將會(huì)保存在其中。但是當(dāng)線程空間滿了以后,將會(huì)出現(xiàn)上面異常。
解決:增加線程棧大小。-Xss2m。但這個(gè)配置無法解決根本問題,還要看代碼部分是否有造成泄漏的部分。
系統(tǒng)內(nèi)存被占滿
異常:java.lang.OutOfMemoryError: unable to create new native thread
說明:
這個(gè)異常是由于操作系統(tǒng)沒有足夠的資源來產(chǎn)生這個(gè)線程造成的。系統(tǒng)創(chuàng)建線程時(shí),除了要在Java堆中分配內(nèi)存外,操作系統(tǒng)本身也需要分配資源來創(chuàng)建線程。因此,當(dāng)線程數(shù)量大到一定程度以后,堆中或許還有空間,但是操作系統(tǒng)分配不出資源來了,就出現(xiàn)這個(gè)異常了。
分配給Java虛擬機(jī)的內(nèi)存愈多,系統(tǒng)剩余的資源就越少,因此,當(dāng)系統(tǒng)內(nèi)存固定時(shí),分配給Java虛擬機(jī)的內(nèi)存越多,那么,系統(tǒng)總共能夠產(chǎn)生的線程也就越少,兩者成反比的關(guān)系。同時(shí),可以通過修改-Xss來減少分配給單個(gè)線程的空間,也可以增加系統(tǒng)總共內(nèi)生產(chǎn)的線程數(shù)。
解決:
重新設(shè)計(jì)系統(tǒng)減少線程數(shù)量。
線程數(shù)量不能減少的情況下,通過-Xss減小單個(gè)線程大小。以便能生產(chǎn)更多的線程。
8.垃圾回收的悖論
所謂“成也蕭何敗蕭何”。Java的垃圾回收確實(shí)帶來了很多好處,為開發(fā)帶來了便利。但是在一些高性能、高并發(fā)的情況下,垃圾回收確成為了制約Java應(yīng)用的瓶頸。目前JDK的垃圾回收算法,始終無法解決垃圾回收時(shí)的暫停問題,因?yàn)檫@個(gè)暫停嚴(yán)重影響了程序的相應(yīng)時(shí)間,造成擁塞或堆積。這也是后續(xù)JDK增加G1算法的一個(gè)重要原因。
當(dāng)然,上面是從技術(shù)角度出發(fā)解決垃圾回收帶來的問題,但是從系統(tǒng)設(shè)計(jì)方面我們就需要問一下了:
我們需要分配如此大的內(nèi)存空間給應(yīng)用嗎?
我們是否能夠通過有效使用內(nèi)存而不是通過擴(kuò)大內(nèi)存的方式來設(shè)計(jì)我們的系統(tǒng)呢?
我們的內(nèi)存中都放了什么
內(nèi)存中需要放什么呢?個(gè)人認(rèn)為,內(nèi)存中需要放的是你的應(yīng)用需要在不久的將來再次用到到的東西。想想看,如果你在將來不用這些東西,何必放內(nèi)存呢?放文件、數(shù)據(jù)庫不是更好?這些東西一般包括:
系統(tǒng)運(yùn)行時(shí)業(yè)務(wù)相關(guān)的數(shù)據(jù)。比如web應(yīng)用中的session、即時(shí)消息的session等。這些數(shù)據(jù)一般在一個(gè)用戶訪問周期或者一個(gè)使用過程中都需要存在。
緩存。緩存就比較多了,你所要快速訪問的都可以放這里面。其實(shí)上面的業(yè)務(wù)數(shù)據(jù)也可以理解為一種緩存。
線程。
因此,我們是不是可以這么認(rèn)為,如果我們不把業(yè)務(wù)數(shù)據(jù)和緩存放在JVM中,或者把他們獨(dú)立出來,那么Java應(yīng)用使用時(shí)所需的內(nèi)存將會(huì)大大減少,同時(shí)垃圾回收時(shí)間也會(huì)相應(yīng)減少。
我認(rèn)為這是可能的。
解決之道
數(shù)據(jù)庫、文件系統(tǒng)
把所有數(shù)據(jù)都放入數(shù)據(jù)庫或者文件系統(tǒng),這是一種最為簡單的方式。在這種方式下,Java應(yīng)用的內(nèi)存基本上等于處理一次峰值并發(fā)請求所需的內(nèi)存。數(shù)據(jù)的獲取都在每次請求時(shí)從數(shù)據(jù)庫和文件系統(tǒng)中獲取。也可以理解為,一次業(yè)務(wù)訪問以后,所有對象都可以進(jìn)行回收了。
這是一種內(nèi)存使用最有效的方式,但是從應(yīng)用角度來說,這種方式很低效。
內(nèi)存-硬盤映射
上面的問題是因?yàn)槲覀兪褂昧宋募到y(tǒng)帶來了低效。但是如果我們不是讀寫硬盤,而是寫內(nèi)存的話效率將會(huì)提高很多。
數(shù)據(jù)庫和文件系統(tǒng)都是實(shí)實(shí)在在進(jìn)行了持久化,但是當(dāng)我們并不需要這樣持久化的時(shí)候,我們可以做一些變通——把內(nèi)存當(dāng)硬盤使。
內(nèi)存-硬盤映射很好很強(qiáng)大,既用了緩存又對Java應(yīng)用的內(nèi)存使用又沒有影響。Java應(yīng)用還是Java應(yīng)用,他只知道讀寫的還是文件,但是實(shí)際上是內(nèi)存。
這種方式兼得的Java應(yīng)用與緩存兩方面的好處。memcached的廣泛使用也正是這一類的代表。
同一機(jī)器部署多個(gè)JVM
這也是一種很好的方式,可以分為縱拆和橫拆??v拆可以理解為把Java應(yīng)用劃分為不同模塊,各個(gè)模塊使用一個(gè)獨(dú)立的Java進(jìn)程。而橫拆則是同樣功能的應(yīng)用部署多個(gè)JVM。
通過部署多個(gè)JVM,可以把每個(gè)JVM的內(nèi)存控制一個(gè)垃圾回收可以忍受的范圍內(nèi)即可。但是這相當(dāng)于進(jìn)行了分布式的處理,其額外帶來的復(fù)雜性也是需要評估的。另外,也有支持分布式的這種JVM可以考慮,不要要錢哦:)
程序控制的對象生命周期
這種方式是理想當(dāng)中的方式,目前的虛擬機(jī)還沒有,純屬假設(shè)。即:考慮由編程方式配置哪些對象在垃圾收集過程中可以直接跳過,減少垃圾回收線程遍歷標(biāo)記的時(shí)間。
這種方式相當(dāng)于在編程的時(shí)候告訴虛擬機(jī)某些對象你可以在*時(shí)間后在進(jìn)行收集或者由代碼標(biāo)識可以收集了(類似C、C++),在這之前你即便去遍歷他也是沒有效果的,他肯定是還在被引用的。
這種方式如果JVM可以實(shí)現(xiàn),個(gè)人認(rèn)為將是一個(gè)飛躍,Java即有了垃圾回收的優(yōu)勢,又有了C、C++對內(nèi)存的可控性。
線程分配
Java的阻塞式的線程模型基本上可以拋棄了,目前成熟的NIO框架也比較多了。阻塞式IO帶來的問題是線程數(shù)量的線性增長,而NIO則可以轉(zhuǎn)換成為常數(shù)線程。因此,對于服務(wù)端的應(yīng)用而言,NIO還是唯一選擇。不過,JDK7中為我們帶來的AIO是否能讓人眼前一亮呢?我們拭目以待。
其他的JDK
本文說的都是Sun的JDK,目前常見的JDK還有JRocket和IBM的JDK。其中JRocket在IO方面比Sun的高很多,不過Sun JDK6.0以后提高也很大。而且JRocket在垃圾回收方面,也具有優(yōu)勢,其可設(shè)置垃圾回收的最大暫停時(shí)間也是很吸引人的。不過,系統(tǒng)Sun的G1實(shí)現(xiàn)以后,在這方面會(huì)有一個(gè)質(zhì)的飛躍。