一 概述
眾所周知c語言是鼻祖.而讓c語言的特點(diǎn)之一就是指針.那在Java中是沒有指針這個(gè)概念的.但是沒有并不表示不存在,在Java中,每次new一個(gè)對(duì)象的時(shí)候,其實(shí)就是在內(nèi)存中開辟了一塊控件,對(duì)象的引用實(shí)際上就是指針.只不過java中把對(duì)內(nèi)存的操作交給了JVM.
c語言的指針讓你可以操作內(nèi)存,但是同時(shí)你也要去維護(hù)這個(gè)指針.而java中操作內(nèi)存的工作交給了JVM.所以一定程度上減輕了程序猿的負(fù)擔(dān).當(dāng)然若是在這種情況下出現(xiàn)內(nèi)存泄漏和內(nèi)存溢出問題.如果沒有了解JVM是怎樣使用內(nèi)存的,將會(huì)導(dǎo)致異常排除變得非常困難.
二 JVM內(nèi)存分布
如圖.JVM的內(nèi)存主要包括兩個(gè)子系統(tǒng)和兩個(gè)組件.兩個(gè)子系統(tǒng)分別是CLASS LOADER(類加載器)和EXECUTION ENGINE(執(zhí)行引擎).兩個(gè)組件分別是RUNTIME DATA AREA(運(yùn)行數(shù)據(jù)區(qū)域)和NATIVE INTERFACE(本地接口庫)組件.
1.CLASS LOADER。java運(yùn)行的時(shí)候并不是直接運(yùn)行代碼,而是要通過類加載器把java class加載到JVM中然后運(yùn)行.負(fù)責(zé)加載的這部分就是CLASS LOADER.我們可以通過重寫ClassLoader來拓展程序的功能.例如:1)在執(zhí)行非置信代碼之前,自動(dòng)驗(yàn)證數(shù)字簽名.2)動(dòng)態(tài)地創(chuàng)建符合用戶特定需要的定制化構(gòu)建類.3)從特定的場(chǎng)所取得java class昨悼,例如數(shù)據(jù)庫中.4) 等等
2.EXECUTION ENGINE。執(zhí)行classes中的指令遂跟。任何JVM specification實(shí)現(xiàn)(JDK)的核心都是Execution engine,不同的JDK例如Sun 的JDK 和IBM的JDK好壞主要就取決于他們各自實(shí)現(xiàn)的Execution engine的好壞。
3.NATIVE INTERFACE .用作與其他語言編程的接口.可以通過這個(gè)組件調(diào)用其他語言的程序.
4.Runtime Data Area組件 這就是我們常說的java內(nèi)存了
? ? ?? 1顶燕、Heap (堆):一個(gè)Java虛擬實(shí)例中只存在一個(gè)堆空間,是Java內(nèi)存管理中最大的一塊.也是被所有的線程所共享的一塊.在虛擬機(jī)啟動(dòng)的時(shí)候創(chuàng)建.
??????? 堆內(nèi)存中存儲(chǔ)著基本上所有的對(duì)象.因?yàn)榧夹g(shù)的更新 現(xiàn)在也不是絕對(duì)的存放在堆內(nèi)存中,java中的gc也是主要對(duì)堆內(nèi)存進(jìn)行操作,所以堆內(nèi)存也可以稱為GC內(nèi)存.gc采用的算法是分代收集算法,所以在堆中又可以分為,年青代(Young)穗慕、年老代(Tenured).更細(xì)致的可以劃分為 Eden空間,From Survivor空間和To Survivor空間.不論怎么劃分.堆都是用來存放對(duì)象.
??? ? ? 根據(jù)Java虛擬機(jī)規(guī)范規(guī)定,java堆可以處于物理上不連續(xù)的內(nèi)存空間中(多條內(nèi)存條),只要邏輯連續(xù)即可.在創(chuàng)建堆的時(shí)候我們可以固定堆的大小,也可以使用可拓展的堆,如果堆中沒有足夠的內(nèi)存來分配對(duì)象就會(huì)報(bào)出OutOfMemory的錯(cuò)誤
? ? ? ? 2饿敲、Method Area(方法區(qū)域):Method Area和Heap一樣,也是被所有線程所共享的一塊,它是用于存儲(chǔ)已被虛擬機(jī)加載的類的信息,常量,靜態(tài)變量,即時(shí)編譯器編譯后的代碼等數(shù)據(jù),java對(duì)這個(gè)區(qū)域的管理比較輕松,除了和堆一樣可以選擇固定內(nèi)存和拓展以外,還可以對(duì)方法區(qū)選擇不進(jìn)行垃圾回收機(jī)制,相對(duì)而言,gc在這里觸發(fā)的概率相對(duì)較小,但也不意味著不會(huì)被回收,這個(gè)區(qū)域的回收工作主要是針對(duì)常量池的回收和類型的卸載.當(dāng)方法區(qū)無法滿足內(nèi)存分配的時(shí)候,會(huì)拋出OutOfMemory的錯(cuò)誤,
? ? ? ? 3、JavaStack(java的棧):Java Virtual Machine Stacks 是屬于線程私有的,他的生命周期和線程的相同.每個(gè)方法被執(zhí)行的時(shí)候都會(huì)在棧中創(chuàng)建一個(gè)棧幀.棧幀中包括局部變量表,操作棧,動(dòng)態(tài)鏈接,方法出口等.
局部變量表用于存儲(chǔ)方法的參數(shù)和局部變量.
操作棧,也成為"基于棧的執(zhí)行引擎",主要是運(yùn)行過程中的算數(shù)計(jì)算和調(diào)用其他方法參數(shù)的傳遞.
動(dòng)態(tài)鏈接是每個(gè)棧幀在執(zhí)行時(shí)常量池中都有一個(gè)引用,Class文件中會(huì)有大量的符號(hào)引用,字節(jié)碼中方法調(diào)用就是使用這些符號(hào)引用,這些符號(hào)引用在第一次加載的時(shí)候就轉(zhuǎn)換為直接引用的成為靜態(tài)解析(靜態(tài)方法),和每一次調(diào)用的時(shí)候才轉(zhuǎn)換為直接引用的成為動(dòng)態(tài)鏈接.
方法出口,當(dāng)方法運(yùn)行的時(shí)候只有兩種情況會(huì)退出方法,一種是異常退出,另一種是執(zhí)行到方法出口(return).當(dāng)方法執(zhí)行完可能進(jìn)行的操作是1.恢復(fù)上層方法的局部變量表和操作數(shù)棧.2.把返回值壓入調(diào)用者調(diào)用者棧幀的操作數(shù)棧.3.調(diào)整 PC 計(jì)數(shù)器的值以指向方法調(diào)用指令后面的一條指令.
? ? ? ? 4逛绵、Program Counter(程序計(jì)數(shù)器):每一個(gè)線程都有它自己的PC寄存器怀各,也是該線程啟動(dòng)時(shí)創(chuàng)建的倔韭。PC寄存器的內(nèi)容總是指向下一條將被執(zhí)行指令的餓地址,這里的地址可以是一個(gè)本地指針瓢对,也可以是在方法區(qū)中相對(duì)應(yīng)于該方法起始指令的偏移量寿酌。由于Java虛擬機(jī)的多線程是通過線程輪流切換并分配處理器執(zhí)行時(shí)間的方式來實(shí)現(xiàn)的,在任何一個(gè)確定的時(shí)刻沥曹,一個(gè)處理器(對(duì)于多核處理器來說是一個(gè)內(nèi)核)只會(huì)執(zhí)行一條線程中的指令份名。因此,為了線程切換后能恢復(fù)到正確的執(zhí)行位置妓美,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器僵腺,各條線程之間的計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ)壶栋,我們稱這類內(nèi)存區(qū)域?yàn)椤熬€程私有”的內(nèi)存辰如。這塊內(nèi)存不會(huì)拋出OutOfMemory
? ? ? ? 5、Native method stack(本地方法棧):保存native方法進(jìn)入?yún)^(qū)域的地址
三 Java對(duì)象調(diào)用
介紹完java內(nèi)存的分布,現(xiàn)在來說說java的對(duì)象是怎樣的一個(gè)存在.最普通創(chuàng)建對(duì)象的方法是
Object object=new Object();
這里面包含了前面所說的三塊內(nèi)存的使用.Object object 這句話就是值在棧中保存了Object對(duì)象的引用,new Object();這段話則表示在堆內(nèi)存中開辟了一段內(nèi)存存儲(chǔ)Object的對(duì)象.Object() 這個(gè)方法是存在于方法區(qū)中.
在Java中reference類型引用對(duì)象有兩種主流的方式,一種是通過句柄訪問,一種是直接訪問.
1.句柄訪問會(huì)在堆內(nèi)存中劃分出一塊區(qū)域用于存儲(chǔ)句柄,reference只需要指向這個(gè)句柄,而句柄會(huì)指向?qū)ο蠛瓦@個(gè)對(duì)象的方法
2.直接引用 reference中直接存儲(chǔ)對(duì)象的地址
這兩種方式各有優(yōu)缺點(diǎn),采用句柄的形式,是在對(duì)象發(fā)生變化的時(shí)候(被回收了)只需要改變句柄的指向?qū)ο?不需要修改reference的指向.
采用直接指針訪問的時(shí)候,因?yàn)樯倭艘粚又羔樀闹赶?所以速度更快.
四 對(duì)象的回收
說完對(duì)象的調(diào)用,接下來就是對(duì)象的回收,在Java中g(shù)c的回收機(jī)制還是比較常見的.一下以堆內(nèi)存中的分代收集算法做舉例.
Sun的JVM Generational Collecting(垃圾回收)原理是這樣的:把對(duì)象分為年青代(Young)贵试、年老代(Tenured)琉兜、持久代(Perm),對(duì)不同生命周期的對(duì)象使用不同的算法毙玻。(基于對(duì)對(duì)象生命周期分析)
如上圖所示豌蟋,為Java堆中的各代分布。
1. Young(年輕代)
年輕代分三個(gè)區(qū)桑滩。一個(gè)Eden區(qū)梧疲,兩個(gè)Survivor區(qū)。大部分對(duì)象在Eden區(qū)中生成运准。當(dāng)Eden區(qū)滿時(shí)幌氮,還存活的對(duì)象將被復(fù)制到Survivor區(qū)(兩個(gè)中的一個(gè)),當(dāng)這個(gè)Survivor區(qū)滿時(shí)胁澳,此區(qū)的存活對(duì)象將被復(fù)制到另外一個(gè)Survivor區(qū)该互,當(dāng)這個(gè)Survivor去也滿了的時(shí)候,從第一個(gè)Survivor區(qū)復(fù)制過來的并且此時(shí)還存活的對(duì)象韭畸,將被復(fù)制年老區(qū)(Tenured宇智。需要注意,Survivor的兩個(gè)區(qū)是對(duì)稱的胰丁,沒先后關(guān)系普筹,所以同一個(gè)區(qū)中可能同時(shí)存在從Eden復(fù)制過來 對(duì)象,和從前一個(gè)Survivor復(fù)制過來的對(duì)象隘马,而復(fù)制到年老區(qū)的只有從第一個(gè)Survivor去過來的對(duì)象。而且妻顶,Survivor區(qū)總有一個(gè)是空的酸员。
2. Tenured(年老代)
年老代存放從年輕代存活的對(duì)象蜒车。一般來說年老代存放的都是生命期較長的對(duì)象。
3. Perm(持久代)
用于存放靜態(tài)文件幔嗦,如今Java類酿愧、方法等。持久代對(duì)垃圾回收沒有顯著影響邀泉,但是有些應(yīng)用可能動(dòng)態(tài)生成或者調(diào)用一些class嬉挡,例如hibernate等,在這種時(shí)候需要設(shè)置一個(gè)比較大的持久代空間來存放這些運(yùn)行過程中新增的類汇恤。持久代大小通過-XX:MaxPermSize=進(jìn)行設(shè)置庞钢。
舉個(gè)例子:當(dāng)在程序中生成對(duì)象時(shí),正常對(duì)象會(huì)在年輕代中分配空間因谎,如果是過大的對(duì)象也可能會(huì)直接在年老代生成(據(jù)觀測(cè)在運(yùn)行某程序時(shí)候每次會(huì)生成一個(gè)十兆的空間用收發(fā)消息基括,這部分內(nèi)存就會(huì)直接在年老代分配)。年輕代在空間被分配完的時(shí)候就會(huì)發(fā)起內(nèi)存回收财岔,大部分內(nèi)存會(huì)被回收风皿,一部分幸存的內(nèi)存會(huì)被拷貝至Survivor的from區(qū),經(jīng)過多次回收以后如果from區(qū)內(nèi)存也分配完畢匠璧,就會(huì)也發(fā)生內(nèi)存回收然后將剩余的對(duì)象拷貝至to區(qū)桐款。等到to區(qū)也滿的時(shí)候,就會(huì)再次發(fā)生內(nèi)存回收然后把幸存的對(duì)象拷貝至年老區(qū)夷恍。
通常我們說的JVM內(nèi)存回收總是在指堆內(nèi)存回收魔眨,確實(shí)只有堆中的內(nèi)容是動(dòng)態(tài)申請(qǐng)分配的,所以以上對(duì)象的年輕代和年老代都是指的JVM的Heap空間裁厅,而持久代則是之前提到的Method Area冰沙,不屬于Heap。
了解完這些之后执虹,以下的轉(zhuǎn)載一熱衷于鉆研技術(shù)的哥們Richen Wang關(guān)于內(nèi)存管理的一些建議——
1拓挥、手動(dòng)將生成的無用對(duì)象,中間對(duì)象置為null袋励,加快內(nèi)存回收侥啤。
2、對(duì)象池技術(shù) 如果生成的對(duì)象是可重用的對(duì)象茬故,只是其中的屬性不同時(shí)盖灸,可以考慮采用對(duì)象池來較少對(duì)象的生成。如果有空閑的對(duì)象就從對(duì)象池中取出使用磺芭,沒有再生成新的對(duì)象赁炎,大大提高了對(duì)象的復(fù)用率。
3钾腺、JVM調(diào)優(yōu) 通過配置JVM的參數(shù)來提高垃圾回收的速度徙垫,如果在沒有出現(xiàn)內(nèi)存泄露且上面兩種辦法都不能保證內(nèi)存的回收時(shí)讥裤,可以考慮采用JVM調(diào)優(yōu)的方式來解決,不過一定要經(jīng)過實(shí)體機(jī)的長期測(cè)試姻报,因?yàn)椴煌膮?shù)可能引起不同的效果己英。如-Xnoclassgc參數(shù)等。