楊靜,楊觀賜
(貴州大學(xué) 現(xiàn)代制造技術(shù)教育部重點(diǎn)實(shí)驗(yàn)室,貴陽(yáng) 550000)
?
基于Java程序的內(nèi)存空間布局規(guī)則研究*
楊靜,楊觀賜
(貴州大學(xué) 現(xiàn)代制造技術(shù)教育部重點(diǎn)實(shí)驗(yàn)室,貴陽(yáng) 550000)
針對(duì)在一定大小的內(nèi)存空間中Jave虛擬機(jī)在處理大型Jave程序時(shí),Jave對(duì)象之間頻繁交互導(dǎo)致內(nèi)存占用高、處理效率低的問(wèn)題,給出了減少Jave對(duì)象占用內(nèi)存空間的三種布局規(guī)則。該規(guī)則利用Jave虛擬機(jī)運(yùn)行機(jī)制,綜合對(duì)象屬性及影響內(nèi)存空間大小等指標(biāo),得出相同對(duì)象不同屬性之間按規(guī)則存放順序的最優(yōu)方法。結(jié)果表明,相對(duì)傳統(tǒng)的相同對(duì)象不同屬性之間無(wú)規(guī)則的存放方法,按規(guī)則存放順序的方法能夠大幅度節(jié)省內(nèi)存空間,并有效提高Jave虛擬機(jī)的運(yùn)行效率,程序?qū)ο笤蕉?,該方法?duì)內(nèi)存空間的節(jié)省和計(jì)算效率的提高效果就越明顯。
Jave程序;內(nèi)存空間;布局規(guī)則;屬性;對(duì)象
隨著社會(huì)經(jīng)濟(jì)的迅猛發(fā)展與市場(chǎng)對(duì)軟件性能要求的提高,基于Java的應(yīng)用程序因其具有“一次編譯,到處運(yùn)行”的特點(diǎn)而得到廣泛應(yīng)用。雖然Java開發(fā)人員對(duì)JDK虛擬機(jī)解釋器不斷進(jìn)行優(yōu)化處理,但是仍然不能完全令用戶滿意。目前國(guó)內(nèi)Java研究者主要將焦點(diǎn)放在提高現(xiàn)有的開發(fā)設(shè)備,但是面對(duì)大型的Java程序時(shí),效果并不明顯[1]。參考文獻(xiàn)[2]中提出在Java虛擬機(jī)中使用垃圾回收機(jī)制。在虛擬監(jiān)控之下,垃圾收集器將定期進(jìn)行清除行動(dòng),當(dāng)所有對(duì)象是非常穩(wěn)定的,此時(shí)垃圾回收器的效率會(huì)非常低,切換到“標(biāo)記-掃描”模式,當(dāng)堆空間存在很多碎片,切換到“標(biāo)記-清理”模式,但如果想撤銷內(nèi)存之外的清理工作,就只能調(diào)用Java的某個(gè)方法,這樣做只是對(duì)虛擬機(jī)中產(chǎn)生的垃圾進(jìn)行清理,從而減少內(nèi)存占用。參考文獻(xiàn)[3]介紹了Java的多線程機(jī)制的應(yīng)用,從而減少內(nèi)存消耗,提高程序運(yùn)行速度,但是Java的多線程機(jī)制容易造成死鎖和資源分配不均等問(wèn)題。參考文獻(xiàn)[4]采用三種基于硬件的代碼緩存策略,采用動(dòng)態(tài)方式寫入和讀取Java代碼,這種方式過(guò)于依賴CPU執(zhí)行性能。參考文獻(xiàn)[5]主要研究了內(nèi)存池空間調(diào)度數(shù)據(jù)的研究,通過(guò)內(nèi)存池解決了內(nèi)外存之間頻繁的交付問(wèn)題。參考文獻(xiàn)[6]、[7]介紹了一種Java虛擬機(jī)將類的全限定名分離為不同的結(jié)點(diǎn),減少整個(gè)類的字符串在常量池中所占據(jù)的大小,這使得在內(nèi)存有限的系統(tǒng)中裝載.class文件后,能減少對(duì)存儲(chǔ)空間的占用。
本文主要從Java對(duì)象占用空間的角度分析,對(duì)Java對(duì)象空間做優(yōu)化處理,首先分析了Java虛擬機(jī)以及Java類的加載過(guò)程,其次研究了Java對(duì)象的性質(zhì),提出了Java對(duì)象的使用規(guī)則,從而達(dá)到降低內(nèi)存消耗,提高運(yùn)行效率的目的。
Java虛擬機(jī)(Java virtual machine,JVM)是運(yùn)行 Java 程序必不可少的機(jī)制。JVM實(shí)現(xiàn)了Java語(yǔ)言最重要的特征,即平臺(tái)無(wú)關(guān)性。其原理為編譯后的 Java 程序指令并不直接在硬件系統(tǒng)的 CPU 上執(zhí)行,而是由 JVM 執(zhí)行。JVM屏蔽了與具體平臺(tái)相關(guān)的信息,使Java語(yǔ)言編譯程序只需要生成在JVM上運(yùn)行的目標(biāo)字節(jié)碼(.class),就可以在多種平臺(tái)上不加修改地運(yùn)行。Java 虛擬機(jī)在執(zhí)行字節(jié)碼時(shí),把字節(jié)碼解釋成具體平臺(tái)上的機(jī)器指令執(zhí)行,因此實(shí)現(xiàn)Java平臺(tái)的無(wú)關(guān)性。它是 Java 程序能在多平臺(tái)間進(jìn)行無(wú)縫移植的可靠保證,同時(shí)也是 Java 程序的安全檢驗(yàn)引擎(還進(jìn)行安全檢查)。JVM 是 編譯后的Java 程序(.class文件)和硬件系統(tǒng)之間的接口(編譯后,Javac 是收錄于JDK中的Java 語(yǔ)言編譯器,該工具可以將后綴名為.Java的源文件編譯為后綴名為.class的可以運(yùn)行于Java虛擬機(jī)的字節(jié)碼)。Java內(nèi)存空間工作原理如圖1所示。
圖1 Java內(nèi)存空間工作原理
JVM=類加載器classloader+執(zhí)行引擎execution engine+運(yùn)行時(shí)數(shù)據(jù)區(qū)域runtime data area classloader,把硬盤上的.class文件加載到JVM中運(yùn)行時(shí)數(shù)據(jù)區(qū)域,但是它不負(fù)責(zé)這個(gè)類文件能否執(zhí)行,這個(gè)是執(zhí)行引擎負(fù)責(zé)的[8]。classloader作用是裝載.class文件,classloader有兩種裝載.class的方式:第一種:隱式,運(yùn)行過(guò)程中,碰到new方式生成對(duì)象,隱式調(diào)用classloader到JVM;第二種:顯示,通過(guò)class.forname()動(dòng)態(tài)加載[9]。
類的加載過(guò)程采用雙親委托機(jī)制[10],這種機(jī)制能更好地保證Java平臺(tái)的安全。該模型要求除了頂層的Bootstrap Class Loader啟動(dòng)類加載器外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器。子類加載器和父類加載器不是以繼承(interface)關(guān)系來(lái)復(fù)用父類加載器的代碼。每個(gè)類加載器都有自己的命名空間(由該加載器及所有父類加載器所加載的類組成,在同一個(gè)命名空間中,不會(huì)出現(xiàn)類的完整名字(包括類的包名)相同的兩個(gè)類;在不同的命名空間中,有可能會(huì)出現(xiàn)類的完整名字(包括類的包名)相同的兩個(gè)類)。雙親委托機(jī)制具體的工作過(guò)程為:classloader首先從自己已經(jīng)加載的類中查詢是否此類已經(jīng)加載,如果已經(jīng)加載,則直接返回原來(lái)已經(jīng)加載的類。每個(gè)類加載器都有自己的加載緩存,當(dāng)一個(gè)類被加載了以后就會(huì)放入緩存,等下次加載的時(shí)候就可以直接返回。如果沒(méi)有找到被加載的類,則委托父類加載器去加載,父類加載器采用同樣的策略,首先查看自己的緩存,然后委托父類的父類去加載,一直到Bookstrap Class Loader。當(dāng)所有父類加載器都沒(méi)有加載的時(shí)候,再由當(dāng)前的類加載器加載,并將其放入它自己的緩存中,以便下次有加載請(qǐng)求的時(shí)候直接返回。使用這種模型來(lái)組織類加載器之間的關(guān)系,主要是為了安全性,避免用戶自己編寫的類動(dòng)態(tài)替換Java的一些核心類(比如String),同時(shí)也避免了重復(fù)加載,因?yàn)镴VM中區(qū)分不同類,不僅僅是根據(jù)類名,相同的.class文件被不同的classloader加載就是不同的兩個(gè)類,如果相互轉(zhuǎn)型的話,會(huì)拋Java.lang.ClassCaseException。類加載器[11]classloader是具有層次架構(gòu)的,也就是父子關(guān)系。其中,Boolstrap是所有類加載器的父親。具體關(guān)系如圖2所示。
圖2 Java類的加載過(guò)程
2.1 對(duì)象所占空間分析
Java對(duì)象內(nèi)存布局:對(duì)象頭(header)、實(shí)例數(shù)據(jù)(Instance Data)和對(duì)齊填充(padding)[12]。所在環(huán)境為32位的windows系統(tǒng),對(duì)象頭在32位系統(tǒng)中占用8字節(jié)。原生類型[13](Primitive Type)的內(nèi)存占用如表1所列。
表1 原生類型的內(nèi)存占用
Reference在32位系統(tǒng)上每個(gè)占用4字節(jié)。對(duì)齊填充,Hotspot的對(duì)齊方式為8字節(jié)對(duì)齊:(對(duì)象頭+實(shí)例數(shù)據(jù)+padding)%8=0,且0≤padding<8。首先,任何對(duì)象都是8字節(jié)對(duì)齊,對(duì)象占用內(nèi)存大小的計(jì)算公式為:
對(duì)象占用字節(jié)數(shù)=基本的8字節(jié)+基本數(shù)據(jù)類型所占用的內(nèi)存空間(累加后對(duì)齊到8的倍數(shù))+對(duì)象引用所占用的空間(累加后對(duì)齊到8的倍數(shù))
2.2 對(duì)象所占空間的分配規(guī)則
經(jīng)過(guò)前面對(duì)Java對(duì)象相關(guān)屬性分析以及Java虛擬機(jī)的工作原理和Java類的加載過(guò)程的分析研究,可以知道Sun公司的Jave虛擬機(jī)并沒(méi)有按照屬性聲明的順序來(lái)進(jìn)行內(nèi)存布局,而是按照下面的順序規(guī)則來(lái)進(jìn)行內(nèi)存布局:[long,double]、[int,float]、[char,short]、[byte,boolean]、reference(引用類型),這樣可以節(jié)約Java運(yùn)行時(shí)的內(nèi)存空間。舉例如下:
Public class Test{
Byte a;
Boolean b;
Char c;
Short d;
Float e;
Double f;
Object g;
}
如果這個(gè)對(duì)象的屬性按照無(wú)規(guī)則的順序存放的話,要占用的空間為:head(8)+a(1)+b(1)+c(2)+d(2)+padding(2)+e(4)+padding(4)+f(8)+g(4)+padding(4)=40,但是按照這個(gè)規(guī)則得到:head(8)+f(8)+e(4)+d(2)+c(2)+a(1)+b(1)+e(4)+padding(2)=32,可以看到節(jié)省了不少空間。
2.3 Java繼承其他子類的內(nèi)存布局規(guī)則
Java虛擬機(jī)將遵循以下規(guī)則來(lái)組織父類中的類成員以及子類和父類中類成員的關(guān)系,規(guī)則如下:不同類繼承關(guān)系中的成員不能混合排列。首先按照規(guī)則1處理父類中的成員,接著才是子類的成員。舉例:
Class A{
Long a;
Int b;
Int c;
}
Class B extends A{
Long d;
}
這樣存放的順序及占用空間如下:head(8)+a(8)+b(4)+c(4)+d(8)=32。這是比較理想的情況,父類成員和子類成員剛好滿足對(duì)其的填充規(guī)則,但在實(shí)際編程中,如果父類中的屬性不夠8個(gè)字節(jié),就有了新的一條內(nèi)存布局規(guī)則:父類中最后一個(gè)成員與子類的第一個(gè)成員的間隔如果不夠4個(gè)字節(jié),此時(shí)需要擴(kuò)展到4個(gè)字節(jié)的基本單位,舉例:
Class A{
Byte a;
}
Class B extends A{
Byte b;
}
那么此時(shí)占用的空間如下:head(8)+a(1)+padding(3)+b(1)+padding(3)=16,顯然這種方式比較浪費(fèi)空間。
當(dāng)子類的第一個(gè)成員是Double或者Long,并且父類并沒(méi)有用完8個(gè)字節(jié),此時(shí)JVM會(huì)破壞規(guī)則,將較小的數(shù)據(jù)填充到該空間,舉例:
Class A{
Byte a;
}
Class B extends A{
Long b;
Short c;
Byte d;
}
此時(shí)占用的空間如下:head(8)+a(1)+padding(3)+c(2)+d(1)+padding(1)+b(8)=24。
2.4 內(nèi)存使用情況
針對(duì)Java程序進(jìn)行內(nèi)存空間優(yōu)化處理之前,必須對(duì)被優(yōu)化的目標(biāo)進(jìn)行有效分析。開發(fā)人員只有通過(guò)內(nèi)存測(cè)試過(guò)程發(fā)現(xiàn)程序中哪部分代碼需要進(jìn)行優(yōu)化,才能夠針對(duì)實(shí)際情況選擇相應(yīng)的優(yōu)化策略。有多種方式可以用于發(fā)現(xiàn)Java程序中的內(nèi)存泄漏現(xiàn)象。最簡(jiǎn)單的方法就是使用一個(gè)操作系統(tǒng)進(jìn)程監(jiān)視器,它可以提供一個(gè)正在運(yùn)行的進(jìn)程所使用的內(nèi)存數(shù)t,也可以使用JavaRuntime類中提供的totalMemory()和freeMemory()等方法來(lái)得到虛擬機(jī)所控制的連續(xù)內(nèi)存空間的內(nèi)存容量t,以及在特定時(shí)刻未使用的內(nèi)存容量t,通過(guò)將兩個(gè)方法捆綁在一起使用,可以計(jì)算出當(dāng)前運(yùn)行的Java程序所使用的內(nèi)存量。大多數(shù)商業(yè)用的Java集成開發(fā)環(huán)境并沒(méi)有提供虛擬機(jī)級(jí)的控制,因此通常可以通過(guò)JDK來(lái)完成對(duì)內(nèi)存使用狀況的測(cè)試。
在Java虛擬機(jī)中優(yōu)化Java程序設(shè)計(jì),就是充分利用軟硬件資源,根據(jù)Java對(duì)象分配規(guī)則,采取相應(yīng)的
Layout Rules of Memory Space Based on Java Program
Yang Jing,Yang Guanci
(Key Laboratory of Ministry of Education for Advanced Manufacturing Technology,Guizhou University,Guiyang 550000,China)
Frequent interaction between Java objects will cause high memory occupancy and low processing efficiency when the Java virtual machine in the treatment of the large Java program in memory space of a certain size.A new method to reduce the memory space occupancy is given.The method uses the object allocation rules,comprehensive the object properties and influence of memory size and other indicators,obtains the optimal method between the different attributes of the same object by the rules of order.The experiment results show that the method can greatly save memory space and effectively improve the operating efficiency of the Java virtual machine.The more program objects,the effect is more obvious.
Java program;memory space;layout rules;property;object
貴州省優(yōu)秀青年科技人才培養(yǎng)對(duì)象專項(xiàng)資金項(xiàng)目(黔科合人字(2015)13號(hào))。
TP311.1
A