?JVM
內(nèi)存參數(shù)調(diào)優(yōu)
我們前面所提到的堆內(nèi)存(heap)是由Java虛擬機控制管理的,因此,這些參數(shù)對JVM而言都有一個默認值,但在某些情況下這些參數(shù)的默認值并不是最優(yōu)的,這就需要我們通過調(diào)整這些參數(shù)的值來提高JVM的性能,最終提高應用的性能指標。
在實際的應用開發(fā)中,如果應用所使用的系統(tǒng)內(nèi)存較大,經(jīng)常會引發(fā)內(nèi)存溢出的錯誤:
…
java.lang.OutOfMemoryError <<no stack trace available>>
java.lang.OutOfMemoryError <<no stack trace available>>
? ? Exception in thread "main"
…
這可能是因為應用要使用的堆內(nèi)存(heap)超過了JVM所管理內(nèi)存范圍,如果我們適當追加內(nèi)存值有時就可以避免這種致命錯誤的出現(xiàn)。
在WINDOWS系統(tǒng)上你可以通過參數(shù)-verbosegc查看JVM回收內(nèi)存的信息,在HP UNIX系統(tǒng)上你可以通過-Xverbosegc:file=/tmp/gc$$.out參數(shù)將信息重定向到一個文件中。然后查看相應的信息,例如下面的這個類。
public class A {
??? ???public static void main(String args[]) {
??? ???? for (int i =0 ;i < 100000;++i) {
??????????? ???A a = new A();
??? ???? }
??? ???? System.out.println("this is a GC test");
??? ???}
}
在類A的main方法中創(chuàng)建了100 000個A對象,然后我們看一下JVM回收內(nèi)存的情況,編譯并執(zhí)行這個類:
>java -verbosegc A
[GC 512K->91K(1984K), 0.0027537 secs]
this is a ?GC test
從輸出信息中可以看出總共有1984KB的內(nèi)存被回收,耗時0.002 753 7秒。現(xiàn)在我們將類A添加一行清除對象引用的代碼:
public class A {
??? ???public static void main(String args[]) {
??? ???? for (int i =0 ;i < 100000;++i) {
???????????? ??A a = new A();
? ?????????????a = null;
??? ???? }
??? ???? System.out.println("this is a GC test");
??? ???}
}
編譯并執(zhí)行這個類:
>java -verbosegc A
[GC 512K->91K(1 984K), 0.0 027 450 secs]
this is a ?GC test
我們 看到被回收內(nèi)存的數(shù)量并沒有變化,但是回收所需要的時間卻變成了0.002 745 0秒,后者比前者節(jié)省了0.000 008 7秒,千萬不要小看這0.000 008 7秒,當你的應用足夠復雜時這個時間就會成指數(shù)級增長,看來我們主動清除對象引用的方法,確實可以加速JVM對垃圾內(nèi)存的回收。
如果再在類A中加入一行強制系統(tǒng)內(nèi)存回收的代碼,結(jié)果又會怎樣呢?如下所示:
public class A {
?? ?public static void main(String args[]) {
??? ???? for (int i =0 ;i < 100000;++i) {
????????? ?????A a = new A();
??? ???????????a = null;
??? ???? }
??? ???? System.gc();
??? ???? System.out.println("this is a GC test");
??? }
}
編譯并執(zhí)行這個類:
>java -verbosegc A
[GC 512K->91K(1984K), 0.0 027 272 secs]
[Full GC 487K->91K(1984K), 0.0 070 730 secs]
this is a ?GC test
系統(tǒng) 這次做了兩次內(nèi)存回收,第一次是程序中強制系統(tǒng)內(nèi)存回收的代碼System.gc()導致的內(nèi)存回收,而后者是系統(tǒng)最終的內(nèi)存回收操作,我們看到強制內(nèi)存回收耗時不長,可是卻導致了系統(tǒng)最終垃圾回收的時間加長了很多,因此我們在采用強制系統(tǒng)垃圾回收(通過顯式調(diào)用方法System.gc())的辦法來回收系統(tǒng)垃圾內(nèi)存的辦法,還是存在一些弊端的,應盡量少用,或者說只在必要的時候應用。
上面我們提到的內(nèi)存回收操作就是回收JVM所 管理的堆內(nèi)存(heap)。當系統(tǒng)連續(xù)申請內(nèi)存并且超過JVM所管理的堆內(nèi)存(heap)的最大值時,就會產(chǎn)生系統(tǒng)內(nèi)存溢出的致命異常,下面我們來看一下 怎樣通過設(shè)置JVM的內(nèi)存參數(shù)來優(yōu)化JVM對內(nèi)存的管理,避免內(nèi)存溢出異常的發(fā)生。表2-1所示的就是與JVM內(nèi)存相關(guān)的參數(shù)及其說明。
表2-1? 與JVM內(nèi)存相關(guān)的參數(shù)及其說明
JVM
堆內(nèi)存(heap)設(shè)置選項
|
參數(shù)格式
|
說??? 明
|
設(shè)置新對象生產(chǎn)堆內(nèi)存(Setting the Newgeneration heap size)
|
-XX:NewSize
|
通過這個選項可以設(shè)置Java新對象生產(chǎn)堆內(nèi)存。在通常情況下這個選項的數(shù)值為1 024的整數(shù)倍并且大于1MB。這個值的取值規(guī)則為,一般情況下這個值-XX:NewSize是最大堆內(nèi)存(maximum heap size)的四分之一。增加這個選項值的大小是為了增大較大數(shù)量的短生命周期對象
增加Java新對象生產(chǎn)堆內(nèi)存相當于增加了處理器的數(shù)目。并且可以并行地分配內(nèi)存,但是請注意內(nèi)存的垃圾回收卻是不可以并行處理的
|
續(xù)表?
JVM
堆內(nèi)存(heap)設(shè)置選項
|
參數(shù)格式
|
說??? 明
|
設(shè)置最大新對象生產(chǎn)堆內(nèi)存(Setting the maximum New generation heap size)
|
-XX:MaxNewSize
|
通過這個選項可以設(shè)置最大Java新對象生產(chǎn)堆內(nèi)存。通常情況下這個選項的數(shù)值為1?024的整數(shù)倍并且大于1MB
其功用與上面的設(shè)置新對象生產(chǎn)堆內(nèi)存-XX:NewSize相同
|
設(shè)置新對象生產(chǎn)堆內(nèi)存的比例(Setting New heap size ratios)
|
-XX:SurvivorRatio
|
新對象生產(chǎn)區(qū)域通常情況下被分為3個子區(qū)域:伊甸園,與兩個殘存對象空間,這兩個空間的大小是相同的。通過用-XX:SurvivorRatio=X選項配置伊甸園與殘存對象空間(Eden/survivor)的大小的比例。你可以試著將這個值設(shè)置為8,然后監(jiān)控、觀察垃圾回收的工作情況
|
設(shè)置堆內(nèi)存池的最小值
(Setting minimum heap size)
|
-Xms
|
通過這個選項可以要求系統(tǒng)為堆內(nèi)存池分配內(nèi)存空間的最小值。通常情況下這個選項的數(shù)值為1?024的整數(shù)倍并且大于1MB。這個值的取值規(guī)則為,一般情況下這個值(-Xms)與最大堆內(nèi)存相同,以降低垃圾回收的頻度
|
設(shè)置堆內(nèi)存池的最大值(Setting maximum heap size)
|
-Xmx
|
通過這個選項可以要求系統(tǒng)為堆內(nèi)存池分配內(nèi)存空間的最大值。通常情況下這個選項的數(shù)值為1?024的整數(shù)倍并且大于1 MB
一般情況下這個值(-Xmx)與最小堆內(nèi)存(minimum heap size –Xms)相同,以降低垃圾回收的頻度
|
取消垃圾回收
|
-Xnoclassgc
|
這個選項用來取消系統(tǒng)對特定類的垃圾回收。它可以防止當這個類的所有引用丟失之后,這個類仍被引用時不會再一次被重新裝載,因此這個選項將增大系統(tǒng)堆內(nèi)存的空間
|
設(shè)置棧內(nèi)存的大小
|
-Xss
|
這個選項用來控制本地線程棧的大小,當這個選項被設(shè)置的較大(>2MB)時將會在很大程度上降低系統(tǒng)的性能。因此在設(shè)置這個值時應該格外小心,調(diào)整后要注意觀察系統(tǒng)的性能,不斷調(diào)整以期達到最優(yōu)
|
根據(jù)表2-1中所描述的參數(shù)意義,我們可以在啟動應用時為JVM設(shè)置相應的參數(shù)值以提高系統(tǒng)的性能,例如下面的例子:
java -XX:NewSize=
-Xmx
類文件(.class)的大小
由Java源文件.java文件編譯成JVM 可解釋執(zhí)行的Java字節(jié)文件.class。因所采用的編譯方式的不同而大小也不同。通常.class文件的大小也存在是否占用較大內(nèi)存的問題。通過降 低.class文件的大小,不但可以降低系統(tǒng)內(nèi)存的開銷,還可以節(jié)省網(wǎng)絡(luò)開銷,雖然這部分內(nèi)容與JVM內(nèi)存管理聯(lián)系不大,但是我覺得還是有必要提一下,因 為這在你開發(fā)Applet應用時會有幫助(注:在本書后續(xù)的章節(jié)中,將會對如何減小Java類尺寸的技術(shù)話題做更為深入的探討)。因為一般來說, Applet應用都是靠網(wǎng)絡(luò)分布式傳輸由客戶端瀏覽器裝載運行的,如果類文件較大,無疑將會增大網(wǎng)絡(luò)開銷,降低傳輸速度無法滿足用戶的需求,并且如果類文件較大,無疑也會消耗客戶端內(nèi)存資源。我們可以通過在Java編譯器javac中添加相應的參數(shù),來縮小類文件的大小,解決上面的問題。
通常有三種編譯方式會影響類文件的大小。
(1)默認編譯方式:? javac ??A.java。
(2)調(diào)試編譯方式:? javac ?–g A.java。
(3)代碼編譯方式:? javac ?–g:none A.java。
例如如下所示的簡單的類A:
public class A {
? ?????public static void main(String args[]) {
? ????????for (int i =0 ;i < 100000;++i) {
?? ???????A a = new A();
?? ???????}
?? ????}
}
通過上面這三種方式編譯后的類文件的大小分別為:
默認編譯方式:291字節(jié)。
調(diào)試編譯方式:422字節(jié)。
代碼編譯方式:207字節(jié)。
采用三種不同的方式,編譯產(chǎn)生的類文件的大小差異非常大,這是什么原因?qū)е碌哪兀吭瓉碓谟?span lang="EN-US">.class文件中包含多個不同的部分或?qū)傩浴?span lang="EN-US">
代碼(Code)屬性包含實際的方法字節(jié)碼。 源文件信息(SourceFile Information)包含用于生成.class的源文件名稱。代碼行序號表(LineNumberTable)用來映射源文件中的代碼行序號與字節(jié)碼 文件中的序號偏移。本地變量表(LocalVariableTable)用來映射本地變量與棧楨的偏移。
&
注意? 如果你想了解字節(jié)碼文件.class的文件結(jié)構(gòu)詳細信息,請參考相關(guān)的技術(shù)資料,這里就不詳細講解了。
正是由于上面這三種編譯方式生成的類文件所包含的信息不同,才導致了類文件的大小差異較大,其包含的信息分別如下所示。
默認編譯方式:代碼(Code)、源文件信息(SourceFile Information)、代碼行序號表(LineNumberTable)。
調(diào)試編譯方式:代碼(Code)、源文件信息(SourceFile Information)、代碼行序號表(LineNumberTable)、本地變量表(LocalVariableTable)。
代碼編譯方式:代碼(Code)。
這就是三種編譯方式產(chǎn)生類文件大小不同的根本原因。而這三種編譯方式在程序開發(fā)的不同階段卻都起著非常重要的作用,例如,調(diào)試編譯方式在程序的調(diào)試開發(fā)過程中應采用,以獲取更為詳細的調(diào)試信息。因此具體應用上面的三種編譯方式中的哪一種,應該適時而定。