jvm研究綜述

C:\Users\ADMINI~1\AppData\Local\Temp\enhtmlclip\Image(3).png 

jvm組成

1、類加載器,class loader subsystem,加載類型,賦予唯一名字。
2、執行引擎,execution engine,執行被加載類中包含的指令。
3、數據區,data area,保存字節碼、加載類的其他信息、對象、方法、參數、返回值、變量等。
4、本地方法接口,其他編程語言交互的接口。
5、垃圾回收,GC。

jvm結構

C:\Users\ADMINI~1\AppData\Local\Temp\enhtmlclip\Image(4).png
兩個子系統:類加載子系統、執行引擎子系統
兩個組件:運行時數據區域、本地接口

數據區

1、分程序共享和線程單獨控制。
2、方法區和堆,屬於程序共享。類加載解析出來的信息保存在方法區,程序執行時創建的對象則保存在堆中。
3、棧保存線程調用方法時的狀態,本地變量、參數、中間變量、返回值等。
4、PC寄存器保存線程執行的下一條指令的地址,指針或偏移量,線程啓動時創建,。
5、線程創建時,會被分配只屬於它自己的PC寄存器和棧。
6、棧有棧幀,當線程調用一個方法時,會產生壓入一個新的棧幀,當方法調用結束時,將棧幀彈出並拋棄。只有壓棧和出棧兩個操作。
7、棧幀有局部變量區和操作數棧兩部分組成,局部變量區用於存放方法中的局部變量和參數,操作數棧中用於存放方法執行過程中產生的中間結果。
8、調用本地方法時的狀態保存在本地方法棧中,或PC寄存器,或其他非平臺獨立的內存中。
9、直接內存,就是jvm外的內存。jdk有一種基於通道(channel)和緩衝區(buffer)的內存分配方式,將由c語言實現的native函數庫分配在直接內存中,用存儲在jvm堆中的DirectByteBuffer來引用。由於受到機器內存的限制,也可能出現outofmemory。
C:\Users\ADMINI~1\AppData\Local\Temp\enhtmlclip\Image(5).png
C:\Users\ADMINI~1\AppData\Local\Temp\enhtmlclip\Image(6).png
C:\Users\ADMINI~1\AppData\Local\Temp\enhtmlclip\Image(7).png
方法區
1、程序中所有線程共享一個方法區,所以訪問方法區的方法必須是線程安全的,阻塞式。
2、程序運行時,方法區的大小是可變的,可擴展,可以初始化方法區的初始值、最小值、最大值。
3、方法區可以GC,當類變成沒有被引用的狀態的時候。
4、方法區保存的類型信息有:類全名、父類型全名、是類或接口、修飾符、父接口列表,常量池、字段、方法、靜態變量、類加載器引用、Class引用,及方法列表。
5、方法區即持久化代。
6、除class文件中描述的符號引用外,還會把翻譯出來的直接引用也存儲在運行時池中。符號引用就是編碼是用字符串表示某個變量、接口的地址。直接引用就是根據符號引用翻譯過來的地址,將類鏈接階段完成翻譯。
引用
Jdk1.2後對象引用分爲四類:強引用、軟引用、弱引用和虛引用
強引用,平時我們編程的時候例如:Object object=new Object();那object就是一個強引用了。如果一個對象具有強引用,那就類似於必不可少的生活用品,垃圾回收器絕不會回收它。當內存空間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用對象來解決內存不足問題。
軟引用(SoftReference),如果一個對象只具有軟引用,那就類似於可有可物的生活用品。如果內存空間足夠,垃圾回收器就不會回收它,如果內存空間不足了,就會回收這些對象的內存。軟引用可用來實現內存敏感的高速緩存。 軟引用可以和一個引用隊列(ReferenceQueue)聯 合使用,如果軟引用所引用的對象被垃圾回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
弱引用(WeakReference),如果一個對象只具有弱引用,那就類似於可有可物的生活用品。弱引用與軟引用的區別在於:只具有弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它 所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由於垃圾回收器是一個優先級很低的線程, 因此不一定會很快發現那些只具有弱引用的對象。  弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回 收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。
虛引用(PhantomReference),顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命週期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在 任何時候都可能被垃圾回收。 虛引用主要用來跟蹤對象被垃圾回收的活動。虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用隊列 (ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之 關聯的引用隊列中。程序可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。程序如果發現某個虛引用已經被加入到引用隊 列,那麼就可以在所引用的對象的內存被回收之前採取必要的行動。
1、程序創建對象時,在堆中分配內存。所有線程共享堆。
2、當堆中對象變爲未引用狀態時,GC。
3、堆存儲的內容主要是:變量和引用。
4、異常:如果堆中沒有內存完成實例分配,堆無法擴展,拋outfomemory異常。
1、jvm是基於棧的體系結構來執行class字節碼的。線程創建後,都會產生程序計數器(PC)和棧(Stack),程序計數器存放下一條要執行的指令在方法內的偏移量,棧中存放一個個棧幀,每個棧幀對應着每個方法每次調用,而棧幀又是有局部變量區和操作數棧兩部分組成,局部變量區用於存放方法中的局部變量和參數,操作數棧中用於存放方法執行過程中產生的中間結果。
2、jvm的多線程是通過線程輪流切換並分配cpu時間片來實現的,在任何一個確定的時刻,只會執行一條線程中的指令。因此爲了線程切換後能恢復到正確的執行位置,每條線程都需要有一個獨立的pc寄存器來計數,各條線程之間的計數互不影響,獨立存儲,是線程私有內存。
3、異常:如果請求的棧深度大於jvm所允許的深度,拋stackoverflow異常。如果jvm可以動態擴展,但擴展時無法申請足夠的內存拋outofmemory異常。
存儲實例
1、一般來說,一個java的引用訪問涉及到三個內存區域:堆、棧、方法區。
2、以最簡單的本地變量引用:Object obj = new Object(); 爲例如下:
     1)Object obj表示一個本地引用,存在棧的本地變量表中,表示一個reference類型數據。
     2)new Object()作爲實例對象數據存儲在堆中。
     3)堆中還記錄了Object類的類型信息的地址,存儲在方法區中。
     4)對於通過reference類型應用訪問具體對象的方式有兩種:通過句柄訪問方式、通過直接指針方式。
     5)sun hotspot採用指針方式,如下:
C:\Users\ADMINI~1\AppData\Local\Temp\enhtmlclip\Image(1).png

類加載

1、類加載器分兩種:原始類加載器(primordial class loader)和類加載器對象(class loader objects)。
2、原始類加載器是jvm實現的一部分,而類加載器對象是運行中的程序的一部分。不同類加載器加載的類被不同的命名空間所分割。
3、類加載器對象的子類可以訪問類加載機制,該對象和其他對象一樣保存在堆中,被加載的信息保存在方法區中。
類加載過程:加載、連接、初始化
1、加載:尋找並導入指定類型的二進制信息。
2、連接:驗證(確保導入類型的正確性)、準備(爲類型分配內存並初始化默認值)、解析(將字符引用改爲直接引用)。
3、初始化:調用代碼,初始化類變量爲合適的值。
原始類加載器
1、加載遵守類文件格式且被信任的類。
類加載器對象
1、三個方法可以訪問類加載子系統。
2、defineClass(),輸入一個字節數組,定義一個新類型。
3、findSystemClass(),加載指定類。
4、resoleClass(),defineClass只是加載一個類,resoleClass負責後續的連接、初始化。
5、多個類加載器加載了同一個類時,需要在類名前加上類加載器的標識。
類加載過程
加載過程中會先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已加載就視爲已加載此類,保證此類只所有ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。JVM在加載類時默認採用的是雙親委派機制,就是某個特定的類加載器在接到加載類的請求時,首先將加載任務委託給父類加載器,依次遞歸,如果父類加載器可以完成類加載任務,就成功返回;只有父類加載器無法完成此加載任務時,才自己去加載。
1、隱式加載:程序在運行過程中碰到new等方式生成對象的,通過類加載器加載類到jvm中。
2、顯式加載:通過class.forName()等方法,顯式加載類。
類加載的動態性:程序總是由很多類組成的,java程序啓動時,並不是一次把所有的類全部加載後再運行,而是先把保證程序運行的基礎類一次性加載到jvm中,其他類等到jvm用到時再加載,這樣的好處是節省了內存的開銷。因爲java最早就是爲嵌入式系統開發的,內存寶貴。
C:\Users\ADMINI~1\AppData\Local\Temp\enhtmlclip\Image(2).png
1)Bootstrap ClassLoader /啓動類加載器
     $JAVA_HOME中jre/lib/rt.jar裏所有的class,由C++實現,不是ClassLoader子類
2)Extension ClassLoader/擴展類加載器
     負責加載java平臺中擴展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包
3)App ClassLoader/ 系統類加載器
     負責記載classpath中指定的jar包及目錄中class
4)Custom ClassLoader/用戶自定義類加載器(java.lang.ClassLoader的子類)
     屬於應用程序根據自身需要自定義的ClassLoader,如tomcat、jboss都會根據j2ee規範自行實現ClassLoader
熱部署
1、一個實例是通過本身的類名+加載它的ClassLoader識別的,也就是說不同的ClassLoader加載同一個類在JVM是不同的。
2、同一個ClassLoader是不允許多次加載一個類的,否則會報java.lang.LinkageError: attempted  duplicate class definition for name XXX。
3、既然JVM不支持熱部署,那麼要實現熱部署,就必須自定義ClassLoader,當類被修改過後,重新實例ClassLoader後加載該類。

類執行

C:\Users\ADMINI~1\AppData\Local\Temp\enhtmlclip\Image.png
1、編譯過程
2、類加載過程
3、執行過程

類編譯

C:\Users\ADMINI~1\AppData\Local\Temp\enhtmlclip\Image(8).png
1、分析和輸入到符號表
2、註解處理
3、語義分析和生成Class文件
C:\Users\ADMINI~1\AppData\Local\Temp\enhtmlclip\Image(9).png
class文件組成:
1、結構信息,class文件格式版本號、各部分數量與大小。
2、元數據,對應源碼中聲明與常量信息,類/父類/接口的聲明信息、字段/方法的聲明信息、常量池。
3、方法信息,對應源碼中語句和表達式對應的信息,字節碼、異常處理器表、求值棧與局部變量區大小、求值棧的類型記錄、調試符號信息等。
class文件結構
1、java語言跨平臺,原因在於定義了一套與操作硬件無關、jvm識別的字節碼格式,unicode編碼,用class文件表示。同時,jvm定義了一套指令,用於解析執行class文件,翻譯成機器語言。
2、class文件由jvm規範規定。組成如下:
ClassFile {                                                       //U4 代表由無符號四個字節組成
u4 magic;                                                       //是一個固定的數值,java虛擬機裏面稱爲魔數 ,主要是用來標識是否爲java虛擬機所支持的文件結構,目前是0xCAFEBABE
u2 minor_version;                                             //代表次版本號和主版本號
u2 major_version;
u2 constant_pool_count;                                   //這裏面代表常量池個數以及常量池信息
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;                                             //代表class訪問標記,例如:public protected
u2 this_class;                                                  //代表這個類的名稱 例如 java.lang.Object
u2 super_class;                                                  //代表父類名稱
u2 interfaces_count;                                        //實現的接口格式以及接口類名
u2 interfaces[interfaces_count];
u2 fields_count; field_info fields[fields_count];     //字段個數以及字段信息
u2 methods_count; method_info methods[methods_count];          //方法個數以及方法信息
u2 attributes_count;attribute_info attributes[attributes_count];     //java class文件內部屬性信息,和java語言定義的屬性沒有關係,純粹就是給java虛擬機用的
}
C:\Users\ADMINI~1\AppData\Local\Temp\enhtmlclip\Image(10).png
C:\Users\ADMINI~1\AppData\Local\Temp\enhtmlclip\Image(11).png
3、直接看class文件可對應出上述結構,也可以用javap反編譯看到更清楚些。舉例如下:
.java文件如下
public class TestClass {
    private int m;
        public int inc(){
            return m+1;
        }
}
.class文件如下:
00000000  ca fe ba be 00 00 00 32  00 13 0a 00 04 00 0f 09  |.......2........|
00000010  00 03 00 10 07 00 11 07  00 12 01 00 01 6d 01 00  |.............m..|
00000020  01 49 01 00 06 3c 69 6e  69 74 3e 01 00 03 28 29  |.I...<init>...()|
00000030  56 01 00 04 43 6f 64 65  01 00 0f 4c 69 6e 65 4e  |V...Code...LineN|
00000040  75 6d 62 65 72 54 61 62  6c 65 01 00 03 69 6e 63  |umberTable...inc|
00000050  01 00 03 28 29 49 01 00  0a 53 6f 75 72 63 65 46  |...()I...SourceF|
00000060  69 6c 65 01 00 0e 54 65  73 74 43 6c 61 73 73 2e  |ile...TestClass.|
00000070  6a 61 76 61 0c 00 07 00  08 0c 00 05 00 06 01 00  |java............|
00000080  18 6f 72 67 2f 6b 61 6b  61 2f 63 6c 61 7a 7a 2f  |.org/kaka/clazz/|
00000090  54 65 73 74 43 6c 61 73  73 01 00 10 6a 61 76 61  |TestClass...java|
000000a0  2f 6c 61 6e 67 2f 4f 62  6a 65 63 74 00 21 00 03  |/lang/Object.!..|
000000b0  00 04 00 00 00 01 00 02  00 05 00 06 00 00 00 02  |................|
000000c0  00 01 00 07 00 08 00 01  00 09 00 00 00 1d 00 01  |................|
000000d0  00 01 00 00 00 05 2a b7  00 01 b1 00 00 00 01 00  |......*.........|
000000e0  0a 00 00 00 06 00 01 00  00 00 03 00 01 00 0b 00  |................|
000000f0  0c 00 01 00 09 00 00 00  1f 00 02 00 01 00 00 00  |................|
00000100  07 2a b4 00 02 04 60 ac  00 00 00 01 00 0a 00 00  |.*....`.........|
00000110  00 06 00 01 00 00 00 07  00 01 00 0d 00 00 00 02  |................|
00000120  00 0e                                             |..|
00000122
人眼對應如下:
step 1) java magic number
     首先的四個字節0x ca fe ba be 即class文件的magic number
step2) java version
     接下來的四個字節0x 00 00 00 32 即class文件的版本號(可參看class文件的版本號列表)
step3) 常量池
接下來是描述常量表的長度0x 00 13,一共是(19-1)項,人肉分析如下
第1項
tag: 0x 0a ,CONSTANT_Methodref_info即方法聲明
index: 0x 00 04,指向常量池中CONSTANT_Class_info,見常量池第4項
index: 0x 00 0f,指向常量池中CONSTANT_NameAndType_info見常量池第15項
...
step4) 訪問標誌
接下來的兩個字節0x 00 21是由下標中的0x 00 01 | 0x 0020計算出來的,表類是public 的,且是jdk1.2以後的編譯器編譯出來的訪問標誌  標誌名稱 標誌值 含義
ACC_PUBLIC 0x0001 是否爲public類型
ACC_FINAL 0x0010 是否被聲明爲final,只有類可以設置,接口不能設置該標誌
ACC_SUPER 0x0020 是否允許使用invokespecial字節碼指令(查了一下該命令的作用爲"調用超類的構造方法,實例的構造方法,私有方法"),JDK1.2以後編譯器編譯出來的class文件
該標誌都爲真
ACC_INTERFACE 0x0200 標識這是一個接口
ACC_ABSTRACT 0x0400  是否被聲明爲abstract類型,對於接口和抽象類來說此標誌爲真,其他類爲假
ACC_SYNTHETIC 0x1000  標識這個類並非由用戶代碼生成
ACC_ANNOTATION 0x2000  標識這是一個註解
ACC_ENUM 0x4000  標識這是一個枚舉
step5)  類信息
頭兩個字節0x 00 03,指向常量池中的第3項,即類名
接下來的兩個字節 0x 00 04 指向常量池中的第4項,即父類名
接下來的兩個字節 0x 00 00 表示類實現的接口的個數,本例子中爲0,如果有的n個話,後面還會有2n個字節的常量池索引
step 6)字段信息
頭兩個字節 0x 00 01 字段的個數,本例中爲1
接下來就是1個field_info結構
頭兩個字節 0x 00 02 表示accss_flags,即private
接下來0x 00 05表示字段名,指向常量池的第5項,即本例中"m"
接下來的0x 00 06 表示字段的描述信息,指向常量池的第6項,即本例中"I" ,表示是int類型
接下來的0x 00 00 表示字段的屬性信息(用於擴展或者補充說明字段信息)的個數,本例中爲0,如果有n個的話,後面還會有n個attribute_info結構
step 7) 方法信息
頭兩個字節爲0x 00 02,表示方法的個數,本例中爲2
接下來是兩個method_info結構
第1個method_info結構
頭兩個字節0x 00 01 表示accss_flags,即public 方法
接下來0x 00 07表示方法名,指向常量池的第7項,即本例中"<init>"
接下來的0x 00 08 表示方法的描述信息,指向常量池的第8項,即本例中"()V" ,表示是一個沒有參數,返回類型爲void的方法
接下來的0x 00 01表示方法的屬性信息的個數,本例中爲1
接下是1個attribute_info結構
0x 00 09表示屬性名,指向常量池的第9項,即Code,表示方法的代碼
0x 00 00 00 1d 表示該屬性的長度,即29個字節
0x 00 01 表示max_stack
0x 00 01 表示max_locals
0x 00 00 00 05 表示code_length, 即該方法的代碼爲編譯後爲5個字節
0x 2a b7  00 01 b1 即代碼
0x 00 00 表示 沒有異常信息
0x 00 01 表示有一個屬性信息
step 8) 屬性信息
頭兩個字節爲0x 00 01,表示有一個屬性
接下是1個attribute_info結構
0x 00 0d表示屬性名,指向常量池的第13項,即SourceFile,表示類的源文件
0x 00 00 00 02,屬性長度,即接下來的字節個數
0x 00 0e 表示源文件名,指向常量池的第14項,即"TestClass.java"
javap對應如下:
public class org.kaka.clazz.TestClass extends java.lang.Object
  SourceFile: "TestClass.java"
  minor version: 0
  major version: 50
  Constant pool:
const #1 = Method   #4.#15; //  java/lang/Object."<init>":()V
const #2 = Field    #3.#16; //  org/kaka/clazz/TestClass.m:I
const #3 = class    #17;    //  org/kaka/clazz/TestClass
const #4 = class    #18;    //  java/lang/Object
const #5 = Asciz    m;
const #6 = Asciz    I;
const #7 = Asciz    <init>;
const #8 = Asciz    ()V;
const #9 = Asciz    Code;
const #10 = Asciz   LineNumberTable;
const #11 = Asciz   inc;
const #12 = Asciz   ()I;
const #13 = Asciz   SourceFile;
const #14 = Asciz   TestClass.java;
const #15 = NameAndType #7:#8;//  "<init>":()V
const #16 = NameAndType #5:#6;//  m:I
const #17 = Asciz   org/kaka/clazz/TestClass;
const #18 = Asciz   java/lang/Object;                                                                                       
{
public org.kaka.clazz.TestClass();
  Code:
   Stack=1, Locals=1, Args_size=1
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return
  LineNumberTable:
   line 3: 0                                                                                     
public int inc();
  Code:
   Stack=2, Locals=1, Args_size=1
   0:   aload_0
   1:   getfield    #2; //Field m:I
   4:   iconst_1
   5:   iadd
   6:   ireturn
  LineNumberTable:
   line 7: 0
}
.class文件瘦身
1、javac A.java,大小291字節,默認編譯方式,包含代碼、源文件信息、代碼行序號表。
2、javac -g A.java,大小422字節,調試編譯方式,包含代碼、源文件信息、代碼行序號表、本地變量表。
3、javac -g:none A.java,大小207字節,代碼編譯方式,包含代碼。

jvm指令

1、裝載和存儲指令。這類指令在局部變量和操作數棧之間傳遞值,iload、istore、bipush、iconst、lconst等。
2、運算指令。這類指令計算一個通常是操作數棧上的兩個值的函數的結果,並把結果壓回到操作數棧,iadd、isub等。
3、類型轉換指令。這類指令用於JVM數值類型之間的轉換,i2l、i2f、f2d等。
4、對象創建和操縱指令。這類指令用於創建類實例、創建數組、訪問類域(static)和類實例的域(非static)、數組與操作數棧傳遞值、數組長度、類型檢查等,new、newarray、anewarray、multianewarray、getfield、putfield、getstatic、putstatic、iaload、iastore、arraylength、instanceof、checkcast等。
5、操作數棧管理指令。這類指令直接操作操作數棧的值,pop、dup、swap等。
6、控制轉移指令。這類指令有條件或無條件的使JVM執行一條不是控制轉移指令後面的指令,ifeq、ifltif_icmpeq、lcmp、tableswitch、lookupswitch、goto、jsr、ret等。
7、方法調用和返回指令。invokevirtual調用對象的實現方法,invokeinterface調用接口的方法,invokespecial調用特殊處理的實例方法,invokestatic調用類方法;返回指令有ireturn、lreturn、freturn、dreturn、areturn和return。
解讀舉例如下:
35    0:   new     #1; //class com/asiainfo/dryr/test1/Test1Class1
36    3:   dup
37    4:   invokespecial   #31; //Method "<init>":()V
38    7:   astore_1
39    8:   aload_1
第35行。new指令新創建一個對象並將對象引用壓入操作數棧,#1是常數池索引,表示這個對象的類是常數池中的第1個表項中的類常量(class com/asiainfo/dryr/test1/Test1Class1)。這個指令佔3個字節。
第36行。dup指令複製操作數棧頂一個字(JVM自定義存儲單元,一般在32位機器上是32 bit),並將複製結果壓入棧頂。這裏爲什麼要dup一下是有理由的,因爲JVM新創建一個對象後,首先需要調用<init>實例初始化方法,初始化完成後再會做保存到局部變量或調用方法的處理(類似代碼:Object obj = new Object();),這樣就需要在操作數棧上保留兩個相同的對象引用,所以需要dup。這個指令佔1個字節。
第37行。從操作數棧頂彈出對象引用,調用這個對象的實例初始化方法<init>以初始化com/asiainfo/dryr/test1/Test1Class1類的這個對象,這個初始化方法必須使用invokespecial指令調用,#31表示這個方法是常數池中的第31個表項中的方法常量。幀2被創建,併成爲當前幀。這個指令佔3個字節。
第39行。aload_1將第1個局部變量this(對象引用)裝載到操作數棧。這個指令佔1個字節。

gc收集器1

http://images.cnitblog.com/blog/406312/201312/31174951-5fbdb6c81c75430eada33d6e208e094b.jpg
Serial收集器:Serial收集器是在client模式下默認的新生代收集器,其收集效率大約是100M左右的內存需要幾十到100多毫秒;在client模式下,收集桌面應用的內存垃圾,基本上不影響用戶體驗。所以,一般的Java桌面應用中,直接使用Serial收集器(不需要配置參數,用默認即可)。
ParNew收集器:Serial收集器的多線程版本,這種收集器默認開通的線程數與CPU數量相同,-XX:ParallelGCThreads可以用來設置開通的線程數。可以與CMS收集器配合使用,事實上用-XX:+UseConcMarkSweepGC選擇使用CMS收集器時,默認使用的就是ParNew收集器,所以不需要額外設置-XX:+UseParNewGC,設置了也不會衝突,因爲會將ParNew+Serial Old作爲一個備選方案;如果單獨使用-XX:+UseParNewGC參數,則選擇的是ParNew+Serial Old收集器組合收集器。一般情況下,在server模式下,如果選擇CMS收集器,則優先選擇ParNew收集器。
Parallel Scavenge收集器:關注的是吞吐量,可以這麼理解,關注吞吐量,意味着強調任務更快的完成,而如CMS等關注停頓時間短的收集器,強調的是用戶交互體驗。在需要關注吞吐量的場合,比如數據運算服務器等,就可以使用Parallel Scavenge收集器。
Serial Old收集器:在1.5版本及以前可以與 Parallel Scavenge結合使用(事實上,也是當時Parallel Scavenge唯一能用的版本),另外就是在使用CMS收集器時的備用方案,發生 Concurrent Mode Failure時使用。如果是單獨使用,Serial Old一般用在client模式中。
Parallel Old收集器:在1.6版本之後,與 Parallel Scavenge結合使用,以更好的貫徹吞吐量優先的思想,如果是關注吞吐量的服務器,建議使用Parallel Scavenge + Parallel Old 收集器。
CMS收集器:這是當前階段使用很廣的一種收集器,國內很多大的互聯網公司線上服務器都使用這種垃圾收集器,筆者公司的收集器也是這種,CMS收集器以獲取最短回收停頓時間爲目標,非常適合對用戶響應比較高的B/S架構服務器。CMSIncrementalMode: CMS收集器變種,屬增量式垃圾收集器,在併發標記和併發清理時交替運行垃圾收集器和用戶線程。
G1收集器:面向服務器端應用的垃圾收集器,計劃未來替代CMS收集器。
1)一般來說,如果是Java桌面應用,建議採用Serial+Serial Old收集器組合,即:-XX:+UseSerialGC(-client下的默認參數)
2)在開發/測試環境,可以採用默認參數,即採用Parallel Scavenge+Serial Old收集器組合,即:-XX:+UseParallelGC(-server下的默認參數)
3)在線上運算優先的環境,建議採用Parallel Scavenge+Serial Old收集器組合,即:-XX:+UseParallelGC
4)在線上服務響應優先的環境,建議採用ParNew+CMS+Serial Old收集器組合,即:-XX:+UseConcMarkSweepGC
另外在選擇了垃圾收集器組合之後,還要配置一些輔助參數,以保證收集器可以更好的工作。
1)選用了ParNew收集器,你可能需要配置4個參數: -XX:SurvivorRatio, -XX:PretenureSizeThreshold, -XX:+HandlePromotionFailure,-XX:MaxTenuringThreshold;
2)選用了 Parallel Scavenge收集器,你可能需要配置3個參數: -XX:MaxGCPauseMillis,-XX:GCTimeRatio, -XX:+UseAdaptiveSizePolicy ;
3)選用了CMS收集器,你可能需要配置3個參數: -XX:CMSInitiatingOccupancyFraction, -XX:+UseCMSCompactAtFullCollection, -XX:CMSFullGCsBeforeCompaction;

gc收集器2

Serial收集器:新生代收集器,使用停止複製算法,使用一個線程進行GC,其它工作線程暫停。使用-XX:+UseSerialGC可以使用Serial+Serial Old模式運行進行內存回收(這也是虛擬機在Client模式下運行的默認值)。
Serial收集算法:(標記)算法的第一步是標記老年代中依然存活對象。(清理)從頭開始檢查堆內存空間,並且只留下依然倖存的對象。(壓縮)從頭開始,順序地填滿堆內存空間,並且將對內存空間分成兩部分:一個保存着對象,另一個空着,壓縮。
ParNew收集器:新生代收集器,使用停止複製算法,Serial收集器的多線程版,用多個線程進行GC,其它工作線程暫停,關注縮短垃圾收集時間。使用-XX:+UseParNewGC開關來控制使用ParNew+Serial Old收集器組合收集內存;使用-XX:ParallelGCThreads來設置執行內存回收的線程數。
Parallel Scavenge 收集器:新生代收集器,使用停止複製算法,關注CPU吞吐量,即運行用戶代碼的時間/總時間,比如:JVM運行100分鐘,其中運行用戶代碼99分鐘,垃 圾收集1分鐘,則吞吐量是99%,這種收集器能最高效率的利用CPU,適合運行後臺運算(關注縮短垃圾收集時間的收集器,如CMS,等待時間很少,所以適 合用戶交互,提高用戶體驗)。使用-XX:+UseParallelGC開關控制使用 Parallel Scavenge+Serial Old收集器組合回收垃圾(這也是在Server模式下的默認值);使用-XX:GCTimeRatio來設置用戶執行時間佔總時間的比例,默認99,即 1%的時間用來進行垃圾回收。使用-XX:MaxGCPauseMillis設置GC的最大停頓時間(這個參數只對Parallel Scavenge有效)
Serial Old收集器:老年代收集器,單線程收集器,使用標記整理(整理的方法是Sweep(清理)和Compact(壓縮),清理是將廢棄的對象幹掉,只留倖存 的對象,壓縮是將移動對象,將空間填滿保證內存分爲2塊,一塊全是對象,一塊空閒)算法,使用單線程進行GC,其它工作線程暫停(注意,在老年代中進行標 記整理算法清理,也需要暫停其它線程),在JDK1.5之前,Serial Old收集器與ParallelScavenge搭配使用。
Parallel Old收集器:老年代收集器,多線程,多線程機制與Parallel Scavenge差不錯,使用標記整理(與Serial Old不同,這裏的整理是Summary(彙總)和Compact(壓縮),彙總的意思就是將倖存的對象複製到預先準備好的區域,而不是像Sweep(清 理)那樣清理廢棄的對象)算法,在Parallel Old執行時,仍然需要暫停其它線程。Parallel Old在多核計算中很有用。Parallel Old出現後(JDK 1.6),與Parallel Scavenge配合有很好的效果,充分體現Parallel Scavenge收集器吞吐量優先的效果。使用-XX:+UseParallelOldGC開關控制使用Parallel Scavenge +Parallel Old組合收集器進行收集。
CMS(Concurrent Mark Sweep)收集器:老年代收集器,致力於獲取最短回收停頓時間,使用標記清除算法,多線程,優點是併發收集(用戶線程可以和GC線程同時工作),停頓小。使用-XX:+UseConcMarkSweepGC進行ParNew+CMS+Serial Old進行內存回收,優先使用ParNew+CMS(原因見後面),當用戶線程內存不足時,採用備用方案Serial Old收集。
CMS收集的方法:先3次標記,再1次清除,3次標記中前兩次是初始標記和重新標記(此時仍然需要停止(stop the world)), 初始標記(Initial Remark)是標記GC Roots能關聯到的對象(即有引用的對象),停頓時間很短;併發標記(Concurrent remark)是執行GC Roots查找引用的過程,不需要用戶線程停頓;重新標記(Remark)是在初始標記和併發標記期間,有標記變動的那部分仍需要標記,所以加上這一部分 標記的過程,停頓時間比並發標記小得多,但比初始標記稍長。在完成標記之後,就開始併發清除,不需要用戶線程停頓。所以在CMS清理過程中,只有初始標記和重新標記需要短暫停頓,併發標記和併發清除都不需要暫停用戶線程,因此效率很高,很適合高交互的場合。CMS也有缺點,它需要消耗額外的CPU和內存資源,在CPU和內存資源緊張,CPU較少時,會加重系統負擔(CMS默認啓動線程數爲(CPU數量+3)/4)。另外,在併發收集過程中,用戶線程仍然在運行,仍然產生內存垃圾,所以可能產生“浮動垃圾”,本次無法清理,只能下一次Full GC才清理,因此在GC期間,需要預留足夠的內存給用戶線程使用。所以使用CMS的收集器並不是老年代滿了才觸發Full GC,而是在使用了一大半(默認68%,即2/3,使用-XX:CMSInitiatingOccupancyFraction來設置)的時候就要進行Full GC,如果用戶線程消耗內存不是特別大,可以適當調高-XX:CMSInitiatingOccupancyFraction以降低GC次數,提高性能,如果預留的用戶線程內存不夠,則會觸發Concurrent Mode Failure,此時,將觸發備用方案:使用Serial Old 收集器進行收集,但這樣停頓時間就長了,因此-XX:CMSInitiatingOccupancyFraction不宜設的過大。還有,CMS採用的是標記清除算法,會導致內存碎片的產生,可以使用-XX:+UseCMSCompactAtFullCollection來設置是否在Full GC之後進行碎片整理,用-XX:CMSFullGCsBeforeCompaction來設置在執行多少次不壓縮的Full GC之後,來一次帶壓縮的Full GC。
G1收集器:在JDK1.7中正式發佈,與現狀的新生代、老年代概念有很大不同,目前使用較少,不做介紹。
說明:在新生代採用的停止複製算法中,“停 止(Stop-the-world)”的意義是在回收內存時,需要暫停其他所 有線程的執行。這個是很低效的,現在的各種新生代收集器越來越優化這一點,但仍然只是將停止的時間變短,並未徹底取消停止。

gc策略對比

類別
serial collector
parallel collector
( throughput collector )
concurrent collector
(concurrent low pause collector)
介紹
單線程收集器
使用單線程去完成所有的gc工作,沒有線程間的通信,這種方式會相對高效
並行收集器
使用多線程的方式,利用多CUP來提高GC的效率。主要以到達一定的吞吐量爲目標
併發收集器
使用多線程的方式,利用多CUP來提高GC的效率。併發完成大部分工作,使得gc pause短
試用場景
單處理器機器且沒有pause time的要求
適用於科學技術和後臺處理,有中規模/大規模數據集大小的應用且運行在多處理器上,關注吞吐量(throughput)
適合中規模/大規模數據集大小的應用,應用服務器,電信領域。關注response time,而不是throughput
使用
Client模式下默認
可用-XX:+UseSerialGC強制使用
優點:server應用沒什麼優點
缺點:,不能充分發揮硬件資源
Server模式下默認
--YGC:PS FGC:Parallel MSC
,可用-XX:+UseParallelGC-XX:+UseParallelOldGC強制指定,--ParallelGC代表FGCParallel MSC--ParallelOldGC代表FGCParallel Compacting。優點:高效。缺點:heap變大後,造成的暫停時間會變得比較長
可用-XX:+UseConcMarkSweepGC強制指定
優點:
對old進行回收時,對應用造成的暫停時間非常短,適合對latency要求比較高的應用
缺點:
1.內存碎片和浮動垃圾
2.old去的內存分配效率低
3.回收的整個耗時比較長
4.和應用爭搶CPU
內存回收觸發
YGC
eden
空間不足
FGC
old
空間不足
perm
空間不足
顯示調用System.gc() ,包括RMI等的定時觸發
YGC時的悲觀策略
dump live的內存信息時(jmap –dump:live)
YGC
eden
空間不足
FGC
old
空間不足
perm
空間不足
顯示調用System.gc() ,包括RMI等的定時觸發
YGC時的悲觀策略--YGC&YGC
dump live
的內存信息時(jmap –dump:live)
YGC
eden
空間不足
CMS GC
1.old Gen
的使用率大的一定的比率 默認爲92%
2.
配置了CMSClassUnloadingEnabled,Perm Gen的使用達到一定的比率 默認爲92%
3.Hotspot
自己根據估計決定是否要觸法
4.
在配置了ExplictGCInvokesConcurrent的情況下顯示調用了System.gc.
Full GC(Serial MSC)
promotion failed
  concurrent Mode Failure;
內存回收觸發時發生了什麼
YGC
清空eden+from中所有no ref的對象佔用的內存。將eden+from中的所有存活的對象copyto中。在這個過程中一些對象將晉升到old:--to放不下的,--存活次數超過tenuring threshold的,重新計算Tenuring Threshold。單線程做以上動作。全程暫停應用。
FGC
如果配置了CollectGen0First,則先觸發YGC。清空heapno ref的對象,permgen中已經被卸載的classloader中加載的class的信息。單線程做以上動作。全程暫停應用
YGC
serial動作基本相同,不同點:
1.
多線程處理。2.YGC的最後不僅重新計算Tenuring Threshold,還會重新調整EdenFrom的大小
FGC
1.
如配置了ScavengeBeforeFullGC(默認),則先觸發YGC2.MSC:清空heap中的no ref對象,permgen中已經被卸載的classloader中加載的class信息,並進行壓縮。3.Compacting:清空heap中部分no ref的對象,permgen中已經被卸載的classloader中加載的class信息,並進行部分壓縮。多線程做以上動作.
YGC
serial動作基本相同,不同點:
1.
多線程處理
CMSGC:
1.old gen
到達比率時只清除old genno ref的對象所佔用的空間
2.perm gen
到達比率時只清除已被清除的classloader加載的class信息
FGC
serial
細節參數
可用-XX:+UseSerialGC強制使用
-XX:SurvivorRatio=x,控制eden/s0/s1大小。-XX:MaxTenuringThreshold,用於控制對象在新生代存活的最大次數
-XX:PretenureSizeThreshold=x,控制超過多大的字節的對象就在old分配.
-XX:SurvivorRatio=x,控制eden/s0/s1的大小
-XX:MaxTenuringThreshold,
用於控制對象在新生代存活的最大次數
-XX:UseAdaptiveSizePolicy
 去掉YGC後動態調整eden from已經tenuringthreshold的動作
-XX:ParallelGCThreads
 設置並行的線程數
-XX:CMSInitiatingOccupancyFraction 設置old gen使用到達多少比率時觸發
-XX:CMSInitiatingPermOccupancyFraction,
設置Perm Gen使用到達多少比率時觸發
-XX:+UseCMSInitiatingOccupancyOnly
禁止hostspot自行觸發CMS GC
注:
1、throughput collector與concurrent low pause collector區別是throughput collector只在young area使用使用多線程,而concurrent low pause collector則在tenured generation也使用多線程。
2、根據官方文檔,他們倆個需要在多CPU的情況下,才能發揮作用。在一個CPU的情況下,會不如默認的serial collector,因爲線程管理需要耗費CPU資源。而在兩個CPU的情況下,也提高不大。只是在更多CPU的情況下,纔會有所提高。當然 concurrent low pause collector有一種模式可以在CPU較少的機器上,提供儘可能少的停頓的模式,見CMS GC Incremental mode。
3、當要使用throughput collector時,在java opt里加上-XX:+UseParallelGC,啓動throughput collector收集。也可加上-XX:ParallelGCThreads=<desired number>來改變線程數。還有兩個參數 -XX:MaxGCPauseMillis=<nnn>和 -XX:GCTimeRatio=<nnn>,MaxGCPauseMillis=<nnn>用來控制最大暫停時間,而-XX: GCTimeRatio可以提高GC說佔CPU的比,以最大話的減小heap。
4、gc選擇標準:
     1)重點考慮peak application performance(高性能),沒有pause time太嚴格要求,讓vm選擇或者UseParallelGC+UseParallelOldGC(optionally)。
     2)重點考慮response time,pause time要小,UseConcMarkSweepGC。

gc機制

1、方法區,在sun的hotspot中其實就是持久化代,也有gc,主要對常量池gc和已加載類的卸載,但很困難,一般不太做過多考慮。
2、年輕代(Young Generation):對象被創建時,內存的分配首先發生在年輕代(大對象可以直接 被創建在年老代),大部分的對象在創建後很快就不再使用,因此很快變得不可達,於是被年輕代的GC機制清理掉,這個GC機制被稱爲Minor GC或叫Young GC。注意,Minor GC並不代表年輕代內存不足,它事實上只表示在Eden區上的GC。
C:\Users\ADMINI~1\AppData\Local\Temp\enhtmlclip\Image(12).png
1)絕大多數剛創建的對象會被分配在Eden區,其中的大多數對象很快就會消亡。Eden區是連續的內存空間,因此在其上分配內存極快;
2)當Eden區滿的時候,執行Minor GC,將消亡的對象清理掉,並將剩餘的對象複製到一個存活區Survivor0(此時,Survivor1是空白的,兩個Survivor總有一個是空白的);
3)此後,每次Eden區滿了,就執行一次Minor GC,並將剩餘的對象都添加到Survivor0;
4)當Survivor0也滿的時候,將其中仍然活着的對象直接複製到Survivor1,以後Eden區執行Minor GC後,就將剩餘的對象添加Survivor1(此時,Survivor0是空白的)。
5)當兩個存活區切換了幾次(HotSpot虛擬機默認15次,用-XX:MaxTenuringThreshold控制,大於該值進入老年代)之後,仍然存活的對象(其實只有一小部分,比如,我們自己定義的對象),將被複制到老年代。
6)從上面的過程可以看出,Eden區是連續的空間,且Survivor總有一個爲空。經過一次GC和複製,一個Survivor中保存着當前還活 着的對象,而Eden區和另一個Survivor區的內容都不再需要了,可以直接清空,到下一次GC時,兩個Survivor的角色再互換。因此,這種方 式分配內存和清理內存的效率都極高,這種垃圾回收的方式就是著名的“停止-複製(Stop-and-copy)”清理法(將Eden區和一個Survivor中仍然存活的對象拷貝到另一個Survivor中),這不代表着停止複製清理法很高效,其實,它也只在這種情況下高效,如果在老年代採用停止複製,則挺悲劇的。
7)在Eden區,HotSpot虛擬機使用了兩種技術來加快內存分配。分別是bump-the-pointer和TLAB(Thread- Local Allocation Buffers),這兩種技術的做法分別是:由於Eden區是連續的,因此bump-the-pointer技術的核心就是跟蹤最後創建的一個對象,在對 象創建時,只需要檢查最後一個對象後面是否有足夠的內存即可,從而大大加快內存分配速度;而對於TLAB技術是對於多線程而言的,將Eden區分爲若干 段,每個線程使用獨立的一段,避免相互影響。TLAB結合bump-the-pointer技術,將保證每個線程都使用Eden區的一段,並快速的分配內 存。
3、年老代(Old Generation):對象如果在年輕代存活了足夠長的時間而沒有被清理掉(即在幾次 Young GC後存活了下來),則會被複制到年老代,年老代的空間一般比年輕代大,能存放更多的對象,在年老代上發生的GC次數也比年輕代少。當年老代內存不足時, 將執行Major GC,也叫 Full GC。  
1)可以使用-XX:+UseAdaptiveSizePolicy開關來控制是否採用動態控制策略,如果動態控制,則動態調整Java堆中各個區域的大小以及進入老年代的年齡。
2)如果對象比較大(比如長字符串或大數組),Young空間不足,則大對象會直接分配到老年代上(大對象可能觸發提前GC,應少用,更應避免使用短命的大對象)。用-XX:PretenureSizeThreshold來控制直接升入老年代的對象大小,大於這個值的對象會直接分配在老年代上。
3)可能存在年老代對象引用新生代對象的情況,如果需要執行Young GC,則可能需要查詢整個老年代以確定是否可以清理回收,這顯然是低效的。解決的方法是,年老代中維護一個512 byte的塊——”card table“,所有老年代對象引用新生代對象的記錄都記錄在這裏。Young GC時,只要查這裏即可,不用再去查全部老年代,因此性能大大提高。

gc機制

年輕代:
在新生代中,使用“停止-複製”算法進行清理,將新生代內存分爲2部分,1部分 Eden區較大,1部分Survivor比較小,並被劃分爲兩個等量的部分。每次進行清理時,將Eden區和一個Survivor中仍然存活的對象拷貝到 另一個Survivor中,然後清理掉Eden和剛纔的Survivor。
這裏也可以發現,停止複製算法中,用來複制的兩部分並不總是相等的(傳統的停止複製算法兩部分內存相等,但新生代中使用1個大的Eden區和2個小的Survivor區來避免這個問題)
由於絕大部分的對象都是短命的,甚至存活不到Survivor中,所以,Eden區與Survivor的比例較大,HotSpot默認是 8:1,即分別佔新生代的80%,10%,10%。如果一次回收中,Survivor+Eden中存活下來的內存超過了10%,則需要將一部分對象分配到 老年代。用-XX:SurvivorRatio參數來配置Eden區域Survivor區的容量比值,默認是8,代表Eden:Survivor1:Survivor2=8:1:1.
老年代:
老年代存儲的對象比年輕代多得多,而且不乏大對象,對老年代進行內存清理時,如果使用停止-複製算法,則相當低效。一般,老年代用的算法是標記-整理算法,即:標記出仍然存活的對象(存在引用的),將所有存活的對象向一端移動,以保證內存的連續。
在發生Minor GC時,虛擬機會檢查每次晉升進入老年代的大小是否大於老年代的剩餘空間大小,如果大於,則直接觸發一次Full GC,否則,就查看是否設 置了-XX:+HandlePromotionFailure(允許擔保失敗),如果允許,則只會進行MinorGC,此時可以容忍內存分配失敗;如果不 允許,則仍然進行Full GC(這代表着如果設置-XX:+Handle PromotionFailure,則觸發MinorGC就會同時觸發Full GC,哪怕老年代還有很多內存,所以,最好不要這樣做)。
方法區(永久代):
永久代的回收有兩種:常量池中的常量,無用的類信息,常量的回收很簡單,沒有引用了就可以被回收。對於無用的類進行回收,必須保證3點:
1)類的所有實例都已經被回收
2)加載類的ClassLoader已經被回收
3)類對象的Class對象沒有被引用(即沒有通過反射引用該類的地方)
永久代的回收並不是必須的,可以通過參數來設置是否對類進行回收。HotSpot提供-Xnoclassgc進行控制
使用-verbose,-XX:+TraceClassLoading、-XX:+TraceClassUnLoading可以查看類加載和卸載信息
-verbose、-XX:+TraceClassLoading可以在Product版HotSpot中使用;
-XX:+TraceClassUnLoading需要fastdebug版HotSpot支持

gc原理

1、原理:把對象分爲年青代(young)、年老代(tenured)、持久代(perm),對不同生命週期的對象使用不同的回收算法。(基於對對象生命週期的分析)。概括地說,對jvm內存進行標記,確定哪些需要gc,根據gc策略,確定什麼時候執行gc,以及如何gc。
2、年青代,分爲三個區。一個eden區,兩個survivor區。大部分對象都是在eden區生成的。當eden區滿時,還存活的對象將被複制到兩個survivor區中的一個,當這個survivor區滿時,此區存活的對象將被複制到另外一個survivor區,當第二個suervivor區滿時,從第一個survivor區複製過來並且存活的對象,將被複制到年老區。需要注意,sruvivor區是對稱的,沒有先後關係,所以同一個區中可能同時存在從eden複製過來的對象,和從前一個survivor區複製過來的對象,而複製到年老區的只有從第一個survivor區過來的對象。而且survivor區總有一個是空的。
3、年老代,存放從年青代過來的對象,生命週期較長。
4、持久代,存放靜態文件,如類、方法等。持久代對垃圾回收沒有顯著影響,但有些應用可能動態生成或者調用一些class,例如hibernate,在這種時候需要設置一個比較大的持久代來存放這些運行過程中新增的類。
5、舉例子:當程序中生成對象時,正常會在年青代中分配空間,如果是過大的對象也可能直接在年老代中生成(某程序運行時每次回生成一個十兆的空間用於收發消息,這部分內存就會直接在年老代中分配)。年青代在空間分配完的時候就會發起垃圾回收,大部分內存會被回收,一部分存活下來的內存會被拷貝到survivor區的from區,經過多次回收後,如果from區的內存也分配完畢,就會發生垃圾回收,然後將剩餘的存活對象拷貝到to區,等到to區也滿的時候,就會再次發生垃圾回收,然後將存活的對象拷貝到年老區。
6、通常的垃圾回收指堆內回收,確實只有堆內的內容是動態申請分配的,所以上述對象的年青代和年老代都是指的jvm的heap空間,而持久代則是在方法區,不屬於heap。
7、建議:手動將無用對象置null,加快回收。採用對象池減少對象數量。配置jvm參數提高垃圾回收速度,但要經過實體機的長期測試來看實際效果。
8、垃圾回收算法:停止-執行、標記-清除(複製)算法。
9、垃圾回收分爲三類:單線程的Serial算法、多線程的並行算法、非停止-執行的併發算法。
10、jvm會根據機器的硬件配置對每個內存帶選擇適合的回收算法,比如cpu內核多於一個,則會對年青代選擇多線程的並行算法。
11、並行算法使用多線程進行回收,並暫停程序執行。而併發算法也是多線程回收,但不停止程序執行。所以併發算法適用於交互性比較高的程序,經過觀察,併發算法會減少年青代的大小,其實就是使用了一個大的年老代,這反過來跟並行算法比的話,吞吐量要低,而並行算法吞吐量高。
12、垃圾回收的時機:
     1)當年青代滿時,會引發一次普通的gc,只回收年青代。這裏指eden滿,survivor滿不會引發gc。
     2)當年老代滿時,會引發full gc,同時回收年青代、年老代。
     3)當持久化代滿時,會引發full gc,將導致class、method元信息的卸載。
17、爲什麼崩潰前gc的時間越來越長?
     答:gc分爲兩部分:內存標記、清除(複製)。標記部分只要是內存大小固定則時間是不變的,變的是複製的部分,因爲每次gc都有一些回收不掉的內存,所以增加了複製量,導致時間延長,這是判斷泄漏的依據。
18、爲什麼full gc的次數越來越多?
     答:因爲內存的積累,逐漸耗盡了年老代的內存,導致新對象分配沒有更多的空間,從而導致頻繁的垃圾回收。
19、爲什麼年老代佔用的內存越來越大?
     答:因爲年老代的內存無法被回收,越來越多的被拷貝的年老代。
13、outfomemory出現時機:
     1)jvm的98%的時間都花費在內存回收上。
     2)每次回收的內存小於2%。
     3)滿足1)和2)將觸發outofmemory,留給系統微少的時間來做down之前的動作,如打印heap dump。
14、outofmemory前的現象:
     1)每次gc的時間越來越長,由之前的10ms延長到50ms,full gc的時間也由之前的0.5s延長到5s。
     2)full gc的次數越老越多,最頻繁的時候不到1分鐘就進行一次full gc。
     3)年老代的內存越來越大,並且每次full gc後年老代沒有內存釋放。
     4)最後系統無法響應新的請求,逐漸到達outofmemory的臨界值。
15、outofmemory處理辦法:
     1)生成dump文件:通過jmx的mbean生成當前heap信息,一般是整個堆大小的hprof文件,如果沒有啓動jmx則可以通過java的jmap命令來生成該文件。
     2)分析dump文件:可以通過visual vm、imb heapanalyzer、jdk自帶hprof工具來打開查看dump文件。最好是使用eclipse專門的靜態內存分析工具Mat。
     3)通過Mat可以看到,哪些對象被懷疑泄漏,哪些對象佔用的空間最大,及對象的調用關係。
     4)通過Mat或Jmx還可以分析線程狀態,可以觀察到線程被阻塞在哪個對象上,從而判斷系統的瓶頸。
16、崩潰分類:
     1)java.lang.OutOfMemoryError: PermGen space:像spring、hibernate等框架使用動態生成類,而這些類不能被gc自動釋放,導致崩潰。加大PermSize即可。
     2)java.lang.OutOfMemoryError: heap space:堆內存不足。
     3)java.lang.OutOfMemoryError: GC overhead limit exceeded:jdk6後新增,發生在gc時間過長但只釋放很小空間的時候,是一種保護機制。可通過-XX:-UseGCOverheadLimit來關閉該功能。
     4)java.lang.OutOfMemoryError: runtime constant pool:方法區內存不足。
     5)java.lang.StackOverflowError:棧內線程超過允許量。native method要求更大內存,可通過-XX:ThreadStackSize來提高

gc日誌格式

-XX:+PrintGCTimeStamps輸出格式:
289.556: [GC [PSYoungGen: 314113K->15937K(300928K)] 405513K->107901K(407680K), 0.0178568 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
293.271: [GC [PSYoungGen: 300865K->6577K(310720K)] 392829K->108873K(417472K), 0.0176464 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
293.271是從jvm啓動直到垃圾收集發生所經歷的時間,GC表示這是一次Minor GC(新生代垃圾收集);
[PSYoungGen: 300865K->6577K(310720K)] 提供了新生代空間的信息,PSYoungGen,表示新生代使用的是多線程垃圾收集器Parallel Scavenge。300865K表示垃圾收集之前新生代佔用空間,6577K表示垃圾收集之後新生代的空間。新生代又細分爲一個Eden區和兩個Survivor區,Minor GC之後Eden區爲空,6577K就是Survivor佔用的空間。括號裏的310720K表示整個年輕代的大小。392829K->108873K(417472K),表示垃圾收集之前(392829K)與之後(108873K)Java堆的大小(總堆417472K,堆大小包括新生代和年老代)
由新生代和Java堆佔用大小可以算出年老代佔用空間,如,Java堆大小417472K,新生代大小310720K那麼年老代佔用空間是417472K-310720K=106752k;垃圾收集之前老年代佔用的空間爲392829K-300865K=91964k 垃圾收集之後老年代佔用空間108873K-6577K=102296k.0.0176464 secs表示垃圾收集過程所消耗的時間。
[Times: user=0.06 sys=0.00, real=0.01 secs] 提供cpu使用及時間消耗,user是用戶模式垃圾收集消耗的cpu時間,實例中垃圾收集器消耗了0.06秒用戶態cpu時間,sys是消耗系統態cpu時間,real是指垃圾收集器消耗的實際時間。
-XX:+PrintGCDetails輸出格式:
293.289: [Full GC [PSYoungGen: 6577K->0K(310720K)]
[PSOldGen: 102295K->102198K(134208K)] 108873K->102198K(444928K)
[PSPermGen: 59082K->58479K(104192K)], 0.3332354 secs]
[Times: user=0.33 sys=0.00, real=0.33 secs]
Full GC表示執行全局垃圾回收
[PSYoungGen: 6577K->0K(310720K)] 提供新生代空間信息,解釋同上
[PSOldGen: 102295K->102198K(134208K)]提供了年老代空間信息;
108873K->102198K(444928K)整個堆空間信息;
[PSPermGen: 59082K->58479K(104192K)]提供了持久代空間信息;

jvm參數表達

在jvm的參數中,有3種表示方法
1、標準參數(-),所有的JVM實現都必須實現這些參數的功能,而且向後兼容;
2、非標準參數(-X),默認jvm實現這些參數的功能,但是並不保證所有jvm實現都滿足,且不保證向後兼容;
3、非Stable參數(-XX),此類參數各個jvm實現會有所不同,將來可能會隨時取消,需要慎重使用(但是,這些參數往往是非常有用的);分3類:
         1)性能參數( Performance Options):用於JVM的性能調優和內存分配控制,如初始化內存大小的設置;
2)行爲參數(Behavioral Options):用於改變JVM的基礎行爲,如GC的方式和算法的選擇;
3)調試參數(Debugging Options):用於監控、打印、輸出等jvm參數,用於顯示jvm更加詳細的信息;
對於非Stable參數,使用方法有4種:
1-XX:+<option> 啓用選項
2-XX:-<option> 不啓用選項
3-XX:<option>=<number> 給選項設置一個數字類型值,可跟單位,例如 32k, 1024m, 2g
4-XX:<option>=<string> 給選項設置一個字符串值,例如-XX:HeapDumpPath=./dump.core

jvm參數列表

jvm標準參數:
1、-client,設置jvm使用client模式,這是一般在pc機器上使用的模式,啓動很快,但性能和內存管理效率並不高;多用於桌面應用。
2、-server,使用server模式,啓動速度雖然慢(比client模式慢10%左右),但是性能和內存管理效率很高,適用於服務器,用於生成環境、開發環境或測試環境的服務端。
3、-classpath / -cp,JVM加載和搜索文件的目錄路徑,多個路徑用;分隔。注意,如果使用了-classpath,JVM就不會再搜索環境變量中定義的CLASSPATH路徑。
4、-DpropertyName=value,定義系統的全局屬性值,如配置文件地址等,如果value有空格,可以用-Dname="space string"這樣的形式來定義,用System.getProperty("propertyName")可以獲得這些定義的屬性值,在代碼中也可以用System.setProperty("propertyName","value")的形式來定義屬性。
5、-verbose:class,輸出jvm載入類的相關信息,當jvm報告說找不到類或者類衝突時可此進行診斷。
6、-verbose:gc,輸出每次GC的相關情況。
7、-verbose:jni,輸出native方法調用的相關情況,一般用於診斷jni調用錯誤信息。
http://images.cnitblog.com/blog/406312/201312/31173615-f034059f20564bdebdb71e10a3e39d09.png
jvm非標準參數
1、-Xms:堆內存初始由-Xms指定,默認爲物理內存的1/64。
2、-Xmx:堆內存最大由-Xmx指定,默認爲物理內存的1/4,最大不要超過物理內存的80%。默認空餘堆內存小於40%時,會增大堆直到-Xmx的最大限制,可由XX:MinHeapFreeRatio指定。默認空餘堆內存大約70%時,會減小堆直到-Xms的最小限制,可由XX:MaxHeapFreeRatio指定。所以,一般設置-Xms、-Xmx相等,以避免每次GC後調整堆的大小。
3、-Xmn:年青代大小,如果程序需要創建大量臨時對象,可調大。反之調小。通常-Xmn爲1/4-Xmx大小。只能使用在JDK1.4或之後的版本中。
4、-Xss:線程棧大小,web應用一般256就足夠。如果程序有大規模遞歸,經測試後可調大。
5、-Xnoclassgc,取消方法區的類回收,如弱引用、軟引用、虛引用等情況。因爲其阻止內存回收,所以可能會導致OutOfMemoryError錯誤,慎用;
6、-Xrs,減少JVM對操作系統信號(OS Signals)的使用(JDK1.3.1之後纔有效),當此參數被設置之後,jvm將不接收控制檯的控制handler,以防止與在後臺以服務形式運行的JVM衝突
7、-Xprof,跟蹤正運行的程序,並將跟蹤數據在標準輸出輸出;適合於開發環境調試。
8、-Xincgc,開啓增量gc(默認爲關閉);這有助於減少長時間GC時應用程序出現的停頓;但由於可能和應用程序併發執行,所以會降低CPU對應用的處理能力。
9、-Xloggc,與-verbose:gc功能類似,只是將每次GC事件的相關情況記錄到一個文件中,文件的位置最好在本地,以避免網絡的潛在問題。若與verbose命令同時出現在命令行中,則以-Xloggc爲準。
jvm非Stable參數
常用性能參數,性能參數往往用來定義內存分配的大小和比例,相比於行爲參數和調試參數,一個比較明顯的區別是性能參數後面往往跟的有數值。
1、-XX:NewSize,年青代內存初始值,即方法區,默認爲物理內存的1/64。
2、-XX:MaxNewSize,年青代內存的最大值,默認爲物理內存的1/4。JDK1.3/1.4版本中。
3、-XX:PermSize,持久代初始值,即方法區,默認爲物理內存的1/64。
4、-XX:MaxPermSize,持久代最大值,默認爲物理內存1/4。一般將PermSisze和MaxPermSize都設置爲128m,不必過大。但像spring、hibernate等框架使用動態生成類,需調大。
5、-XX:SurvivorRatio,年青代中,eden和一個survivor的比例。如果8,在年青代10m情況下,eden爲8m,每個survivor各1m。
6、-XX:NewRatio,年青代與年老代比例,默認1:2。在使用CMS gc時,此參數失效。
7、-XX:MinHeapFreeRatio,GC後堆中空閒量佔的最小比例,小於該值,則堆內存會增加。Xms==Xmx情況下,該設置無效。
8、-XX:MaxHeapFreeRatio,GC後堆中空閒量佔的最大比例,大於該值,則堆內存會減少。Xms==Xmx情況下,該設置無效。
9、-XX:ThreadStackSize,提高native method內存。也有的說與-Xss相同,但是jdk1.6以前的,1.6之後主線程以-Xss爲準,其他線程以-XX:ThreadStackSize爲準。
10、-XX:MaxTenuringThreshold。  在年青代最大存活次數。堅持過MinorGC的次數,每堅持過一次,該值就增加1,大於該值會進入老年代。
11、-XX:LargePageSizeInBytes,指定堆分頁頁面大小,如4m。
12、-XX:PretenureSizeThreshold,控制超過多大的字節的對象就直接在年老代中分配.
13、-XX:UseAdaptiveSizePolicy 去掉YGC後動態調整eden from已經tenuringthreshold的動作
14、-XX:ReservedCodeCacheSize,保留代碼佔用的內存容量。
15、-XX:MaxDirectMemorySize,nio的buffer分配內存比較特殊,讀寫流可共享內存,如果有大量數據交互,可能導致outofmemory。
常用行爲參數,主要用來選擇使用什麼樣的垃圾收集器組合,以及控制運行過程中的GC策略等。
1、-XX:+UseSerialGC,串行GC,即採用Serial+Serial Old模式。
2、-XX:-UseParallelGC,並行GC,即採用Parallel Scavenge+Serial Old收集器組合(-Server模式下的默認組合)。
3、-XX:+UseConcMarkSweepGC,使用ParNew+CMS+Serial Old組合併發收集,優先使用ParNew+CMS,當用戶線程內存不足時,採用備用方案Serial Old收集。
4、-XX:+UseParNewGC,建議年輕代使用並行回收,同時按照操作系統processer的個數設置並行線程數,理論上<=操作系統processer個數
5、-XX:+UseParalleOldGC,建議年老代使用併發回收,併發回收以降低中斷次數,減少中斷時間爲目標,適用於對系統響應時間有較高要求的服務
6、-XX:ParallelGCThreads,設置並行gc的線程數,在+UseParNewGC的情況下使用
7、-XX:GCTimeRatio=99,設置用戶執行時間佔總時間的比例(默認值99,即1%的時間用於GC)
8、-XX:MaxGCPauseMillis=time,設置GC的最大停頓時間(這個參數只對Parallel Scavenge有效)
9、-XX:-DisableExplicitGC,禁止調用System.gc();但jvm的gc仍然有效
10、-XX:+ScavengeBeforeFullGC,新生代GC優先於Full GC執行
11、-XX:CMSInitiatingOccupancyFraction,設置年老代使用到達多少比率時觸發,預留足夠的空間給年青代gc,防止從年青代gc過來一個較大的內存塊,而年老代無足夠預留空間,無法提供內存快用於回收後的分配,引起強制的full gc,造成較長時間的線程中斷。
12、-XX:CMSInitiatingPermOccupancyFraction,設置持久代使用到達多少比率時觸發
13、-XX:CMSFullGCsBeforeCompaction,設置多少次full gc後進行內存壓縮,由於併發收集器不對內存空間進行壓縮,整理,所以運行一段時間以後會產生"碎片",使得運行效率降低.此值設置運行多少次GC以後對內存空間進行壓縮,整理。
14、-XX:+UseCMSCompactAtFullCollection,設置在FULL GC的時候,對年老代的壓縮;CMS是不會移動內存的,因此這個非常容易產生碎片,導致內存不夠用,因此內存的壓縮這個時候就會被啓用。增加這個參數是個好習慣。可能會影響性能,但是可以消除碎片。
15、-XX:+UseCMSInitiatingOccupancyOnly,禁止hostspot自行觸發CMS GC
常用調試參數,主要用於監控和打印GC的信息。
1、-XX:ErrorFile=./hs_err_pid<pid>.log,保存錯誤日誌或者數據到文件中
2、-XX:-PrintGC     每次GC時打印相關信息
3、-XX:-PrintGCDetails  每次GC時打印詳細信息
4、-XX:-PrintGCTimeStamps   打印每次GC的時間戳
4、-XX:+PrintGCDateStamps
5、-XX:+PrintTenuringDistribution
6、-XX:+PrintGCApplicationStoppedTime
5、-XX:HeapDumpPath=./java_pid<pid>.hprof,指定導出堆信息時的路徑或文件名
6、-XX:-HeapDumpOnOutOfMemoryError,當首次遭遇OOM時導出此時堆中相關信息
7、-XX:-TraceClassLoading   跟蹤類的加載信息
8、-XX:-TraceClassLoadingPreorder   跟蹤被引用到的所有類的加載信息
9、-XX:-TraceClassResolution    跟蹤常量池
10、-XX:-TraceClassUnloading    跟蹤類的卸載信息
11、-XX:-TraceLoaderConstraints     跟蹤類加載器約束的相關信息
12、-XX:-CITime,打印消耗在JIT編譯的時間
13、-XX:-ExtendedDTraceProbes,開啓solaris特有的dtrace探針
14、-XX:OnError="<cmd args>;<cmd args>",出現致命ERROR之後運行自定義命令
15、-XX:OnOutOfMemoryError="<cmd args>;<cmd args>",當首次遭遇OOM時執行自定義命令
16、-XX:-PrintClassHistogram,遇到Ctrl-Break後打印類實例的柱狀信息,與jmap -histo功能相同
17、-XX:-PrintConcurrentLocks   遇到Ctrl-Break後打印併發鎖的相關信息,與jstack -l功能相同
18、-XX:-PrintCommandLineFlags  打印在命令行中出現過的標記
19、-XX:-PrintCompilation   當一個方法被編譯時打印相關信息

jvm調優原則

1、調優目標:
     1)gc時間足夠小
     2)gc次數足夠少
     3)發生full gc的週期足夠長
2、目標1)和2)是相悖的,要想gc時間小則堆要小。要想gc少則堆要大。
3、jvm堆通過-Xms和-Xmx設置,爲防止堆在最大值、最小值之間收縮,通常設置兩值相等。
4、年青代和年老代將根據默認比例1:2分配堆內存,可以通過NewRadio來調整。
5、年青代可以通過-XX:newSize和-XX:MaxNewSize來設置,通過爲防止在最大值、最小值之間收縮,通常設置兩值相等。
6、年青代和年老代設置多大才算合理呢?這個問題沒有答案,否則也就不會有調優了。觀察下二者大小變化有哪些影響。
     1)更大的年青代必然導致更小的年老代,大的年青代會延長普通gc週期,但會增加每次gc的時間;小的年老代會導致更頻繁的full gc。
     2)更小的年青代必然導致更大的年老代,小的年青代會導致普通gc頻繁,但每次gc的時間會更短;大的年老代會減少full gc的頻率。
     3)如何選擇應該依賴對象生命週期的分佈情況:
          (1)如果程序存在大量的臨時對象,應該選擇更大的年青代。
          (2)如果程序存在相對較多的持久對象,年老代應該適當增加。
     4)但是,大多數程序都沒有上述的這樣明顯的特徵,在抉擇時應該根據以下兩點:
          (1)本着full gc儘量少的原則,讓年老代儘量緩存常用對象,jvm默認比例1:2也是這個道理。
          (2)通過觀察程序一段時間,看在峯值時年老代會佔用多少內存。在不影響full gc的前提下,根據實際情況加大年青代,比如比例改爲1:1。但應該給年老代至少保留1/3的增長空間。
7、在配置較好的機器上,比如多核、大內存,可以爲年老代選擇並行收集算法:-XX:+UseParalleOldGC,默認爲Serial。
8、線程棧的設置:每個線程默認會開啓1M的棧,用於存放棧幀、參數、變量等,對大多數程序而言這個設置太大了,一般256k就足夠。理論上在內存不變的情況下,減少每個線程的棧,可以產生更多的線程,但這實際上還受限於操作系統。
9、可以通過下面的參數打印heap dump信息:
     -XX:HeapDumpPath
     -XX:+PrintGCDetails
     -XX:+PrintGCTimeStamps
     -Xloggc:/usr/aaa/dump/heap_trace.txt
     可以通過下面的參數打印outofmemory信息:
     -XX:+HeapDumpOnOutOfMemoryError

jvm調優考量

1)-XX:PermSize儘量比-XX:MaxPermSize小,-XX:MaxPermSize>= 2 * -XX:PermSize, -XX:PermSize> 64m,一般對於4G內存,-XX:MaxPermSize不會超過256m;
2)-Xms =  -Xmx(線上Server模式),以防止抖動,大小受操作系統和內存大小限制,如果是32位系統,則一般-Xms設置爲1g-2g(假設有4g內存),在64位系統上,沒有限制,不過一般爲機器最大內存的一半左右;
3)-Xmn,在開發環境下,可以用-XX:NewSize和-XX:MaxNewSize來設置新生代的大小(-XX:NewSize<=-XX:MaxNewSize),在生產環境,建議只設置-Xmn,一般-Xmn的大小是-Xms的1/2左右,不要設置的過大或過小,過大導致老年代變小,頻繁Full GC,過小導致minor GC頻繁。如果不設置-Xmn,可以採用-XX:NewRatio=2來設置,也是一樣的效果;
4)-Xss一般是不需要改的,默認值即可。
5)-XX:SurvivorRatio一般設置8-10左右,推薦設置爲10,也即:Survivor區的大小是Eden區的1/10,一般來說,普通的Java程序應用,一次minorGC後,至少98%-99%的對象,都會消亡,所以,survivor區設置爲Eden區的1/10左右,能使Survivor區容納下10-20次的minor GC才滿,然後再進入老年代,這個與 -XX:MaxTenuringThreshold的默認值15次也相匹配的。如果XX:SurvivorRatio設置的太小,會導致本來能通過minor回收掉的對象提前進入老年代,產生不必要的full gc;如果XX:SurvivorRatio設置的太大,會導致Eden區相應的被壓縮。
6)-XX:MaxTenuringThreshold默認爲15,也就是說,經過15次Survivor輪換(即15次minor GC),就進入老年代, 如果設置的小的話,則年輕代對象在survivor中存活的時間減小,提前進入年老代,對於年老代比較多的應用,可以提高效率。如果將此值設置爲一個較大值,則年輕代對象會在Survivor區進行多次複製,這樣可以增加對象在年輕代的存活時間,增加在年輕代即被回收的概率。需要注意的是,設置了 -XX:MaxTenuringThreshold,並不代表着,對象一定在年輕代存活15次才被晉升進入老年代,它只是一個最大值,事實上,存在一個動態計算機制,計算每次晉入老年代的閾值,取閾值和MaxTenuringThreshold中較小的一個爲準。
7)-XX:PretenureSizeThreshold一般採用默認值即可。

jvm調優方法

一切都是爲了這一步,調優,在調優之前,我們需要記住下面的原則:
    1)多數的Java應用不需要在服務器上進行GC優化;
    2)多數導致GC問題的Java應用,都不是因爲我們參數設置錯誤,而是代碼問題;
    3)在應用上線之前,先考慮將機器的JVM參數設置到最優(最適合);
    4)減少創建對象的數量;
    5)減少使用全局變量和大對象;
    6)GC優化是到最後不得已才採用的手段;
    7)在實際使用中,分析GC情況優化代碼比優化GC參數要多得多;
GC優化的目的有兩個(http://www.360doc.com/content/13/0305/10/15643_269388816.shtml):
    1)將轉移到老年代的對象數量降低到最小;
    2)減少full GC的執行時間;
爲了達到上面的目的,一般地,你需要做的事情有:
    1)減少使用全局變量和大對象;
    2)調整新生代的大小到最合適;
    3)設置老年代的大小爲最合適;
    4)選擇合適的GC收集器;
在上面的4條方法中,用了幾個“合適”,那究竟什麼纔算合適,一般的,請參考上面“收集器搭配”和“啓動內存分配”兩節中的建議。但這些建議不是萬能的,需要根據您的機器和應用情況進行發展和變化,實際操作中,可以將兩臺機器分別設置成不同GC參數,並且進行對比,選用那些確實提高了性能或減少GC時間的參數。
真正熟練的使用GC調優,是建立在多次進行GC監控和調優的實戰經驗上的,進行監控和調優的一般步驟爲:
1,監控GC的狀態
使用各種JVM工具,查看當前日誌,分析當前JVM參數設置,並且分析當前堆內存快照和gc日誌,根據實際的各區域內存劃分和GC執行時間,覺得是否進行優化;
2,分析結果,判斷是否需要優化
如果各項參數設置合理,系統沒有超時日誌出現,GC頻率不高,GC耗時不高,那麼沒有必要進行GC優化;如果GC時間超過1-3秒,或者頻繁GC,則必須優化;
注:如果滿足下面的指標,則一般不需要進行GC:
    1)Minor GC執行時間不到50ms;
    2)Minor GC執行不頻繁,約10秒一次;
    3)Full GC執行時間不到1s;
    4)Full GC執行頻率不算頻繁,不低於10分鐘1次;
3,調整GC類型和內存分配
如果內存分配過大或過小,或者採用的GC收集器比較慢,則應該優先調整這些參數,並且先找1臺或幾臺機器進行beta,然後比較優化過的機器和沒有優化的機器的性能對比,並有針對性的做出最後選擇;
4,不斷的分析和調整
通過不斷的試驗和試錯,分析並找到最合適的參數
5,全面應用參數
如果找到了最合適的參數,則將這些參數應用到所有服務器,並進行後續跟蹤。

jvm調優監控

通過監控GC,我們可以搞清楚很多問題,如:
1,minor GC和full GC的頻率;
2,執行一次GC所消耗的時間;
3,新生代的對象何時被移到老生代以及花費了多少時間;
4,每次GC中,其它線程暫停(Stop the world)的時間;
5,每次GC的效果如何,是否不理想;

jvm調優監控-jps

jps命令用於查詢正在運行的JVM進程,常用的參數爲:
    -q:只輸出LVMID,省略主類的名稱
    -m:輸出虛擬機進程啓動時傳給主類main()函數的參數
    -l:輸出主類的全類名,如果進程執行的是Jar包,輸出Jar路徑
    -v:輸出虛擬機進程啓動時JVM參數
命令格式:jps [option] [hostid]
一個簡單的例子:
http://images.cnitblog.com/blog/406312/201312/31175406-0306358534f7455b971fdb4960bc7fa2.png
在上圖中,有一個vid爲309的apache進程在提供web服務。

jvm調優監控-jstat

jstat可以實時顯示本地或遠程JVM進程中類裝載、內存、垃圾收集、JIT編譯等數據(如果要顯示遠程JVM信息,需要遠程主機開啓RMI支持)。如果在服務啓動時沒有指定啓動參數-verbose:gc,則可以用jstat實時查看gc情況。
jstat有如下選項:
   -class:監視類裝載、卸載數量、總空間及類裝載所耗費的時間
   -gc:監聽Java堆狀況,包括Eden區、兩個Survivor區、老年代、永久代等的容量,以用空間、GC時間合計等信息
   -gccapacity:監視內容與-gc基本相同,但輸出主要關注java堆各個區域使用到的最大和最小空間
   -gcutil:監視內容與-gc基本相同,但輸出主要關注已使用空間佔總空間的百分比
   -gccause:與-gcutil功能一樣,但是會額外輸出導致上一次GC產生的原因
   -gcnew:監視新生代GC狀況
   -gcnewcapacity:監視內同與-gcnew基本相同,輸出主要關注使用到的最大和最小空間
   -gcold:監視老年代GC情況
   -gcoldcapacity:監視內同與-gcold基本相同,輸出主要關注使用到的最大和最小空間
   -gcpermcapacity:輸出永久代使用到最大和最小空間
   -compiler:輸出JIT編譯器編譯過的方法、耗時等信息
   -printcompilation:輸出已經被JIT編譯的方法
命令格式:jstat [option vmid [interval[s|ms] [count]]]
jstat可以監控遠程機器,命令格式中VMID和LVMID特別說明:如果是本地虛擬機進程,VMID和LVMID是一致的,如果是遠程虛擬機進程,那麼VMID格式是: [protocol:][//]lvmid[@hostname[:port]/servername],如果省略interval和count,則只查詢一次
查看gc情況的例子:
http://images.cnitblog.com/blog/406312/201312/31175506-25d590802e5f498bb1bb25169dc3c92a.png
在圖中,命令sudo jstat -gc 309 1000 5代表着:蒐集vid爲309的java進程的整體gc狀態, 每1000ms收集一次,共收集5次;XXXC表示該區容量,XXXU表示該區使用量,各列解釋如下:
S0C:S0區容量(S1區相同,略)
S0U:S0區已使用
EC:E區容量
EU:E區已使用
OC:老年代容量
OU:老年代已使用
PC:Perm容量
PU:Perm區已使用
YGC:Young GC(Minor GC)次數
YGCT:Young GC總耗時
FGC:Full GC次數
FGCT:Full GC總耗時
GCT:GC總耗時
用gcutil查看內存的例子:
http://images.cnitblog.com/blog/406312/201312/31175532-488fd7ef8f7640929fe232ba0f3d1fb1.png
圖中的各列與用gc參數時基本一致,不同的是這裏顯示的是已佔用的百分比,如S0爲86.53,代表着S0區已使用了86.53%

jvm調優監控-jinfo

用於查詢當前運行這的JVM屬性和參數的值。
jinfo可以使用如下選項:
   -flag:顯示未被顯示指定的參數的系統默認值
   -flag [+|-]name或-flag name=value: 修改部分參數
   -sysprops:打印虛擬機進程的System.getProperties()
 命令格式:jinfo [option] pid

jvm調優監控-jmap

用於顯示當前Java堆和永久代的詳細信息(如當前使用的收集器,當前的空間使用率等)
   -dump:生成java堆轉儲快照
   -heap:顯示java堆詳細信息(只在Linux/Solaris下有效)
   -F:當虛擬機進程對-dump選項沒有響應時,可使用這個選項強制生成dump快照(只在Linux/Solaris下有效)
   -finalizerinfo:顯示在F-Queue中等待Finalizer線程執行finalize方法的對象(只在Linux/Solaris下有效)
   -histo:顯示堆中對象統計信息
   -permstat:以ClassLoader爲統計口徑顯示永久代內存狀態(只在Linux/Solaris下有效)
 命令格式:jmap [option] vmid
其中前面3個參數最重要,如:
查看對詳細信息:sudo jmap -heap 309
生成dump文件: sudo jmap -dump:file=./test.prof 309
部分用戶沒有權限時,採用admin用戶:sudo -u admin -H  jmap -dump:format=b,file=文件名.hprof pid
查看當前堆中對象統計信息:sudo  jmap -histo 309:該命令顯示3列,分別爲對象數量,對象大小,對象名稱,通過該命令可以查看是否內存中有大對象;
有的用戶可能沒有jmap權限:sudo -u admin -H jmap -histo 309 | less

jvm調優監控-jhat

用於分析使用jmap生成的dump文件,是JDK自帶的工具,使用方法爲: jhat -J -Xmx512m [file]
不過jhat沒有mat好用,推薦使用mat(Eclipse插件: http://www.eclipse.org/mat ),mat速度更快,而且是圖形界面。

jvm調優監控-jstack

用於生成當前JVM的所有線程快照,線程快照是虛擬機每一條線程正在執行的方法,目的是定位線程出現長時間停頓的原因。
   -F:當正常輸出的請求不被響應時,強制輸出線程堆棧
   -l:除堆棧外,顯示關於鎖的附加信息
   -m:如果調用到本地方法的話,可以顯示C/C++的堆棧
命令格式:jstack [option] vmid

jvm調優監控- -verbosgc

-verbosegc是一個比較重要的啓動參數,記錄每次gc的日誌,下面的表格對比了jstat和-verbosegc:
 
jstat
-verbosegc
監控對象
運行在本機的Java應用可以把日誌輸出到終端上,或者藉助jstatd命令通過網絡連接遠程的Java應用。
只有那些把-verbogc作爲啓動參數的JVM。
輸出信息
堆狀態(已用空間,最大限制,GC執行次數/時間,等等)
執行GC前後新生代和老年代空間大小,GC執行時間。
輸出時間
Every designated time
每次設定好的時間。
每次GC發生的時候。
用途
觀察堆空間變化情況
瞭解單次GC產生的效果。
與-verbosegc配合使用的一些常用參數爲:
   -XX:+PrintGCDetails,打印GC信息,這是-verbosegc默認開啓的選項
   -XX:+PrintGCTimeStamps,打印每次GC的時間戳
   -XX:+PrintHeapAtGC:每次GC時,打印堆信息
   -XX:+PrintGCDateStamps (from JDK 6 update 4) :打印GC日期,適合於長期運行的服務器
   -Xloggc:/home/admin/logs/gc.log:制定打印信息的記錄的日誌位置
每條verbosegc打印出的gc日誌,都類似於下面的格式:
time [GC [<collector>: <starting occupancy1> -> <ending occupancy1>(total occupancy1), <pause time1> secs] <starting occupancy3> -> <ending occupancy3>(total occupancy3), <pause time3> secs]
http://images.cnitblog.com/blog/406312/201312/31175806-def01a7b0d644728ab4f77115cc2b250.png
time:執行GC的時間,需要添加-XX:+PrintGCDateStamps參數纔有;
collector:minor gc使用的收集器的名字。
starting occupancy1:GC執行前新生代空間大小。
ending occupancy1:GC執行後新生代空間大小。
total occupancy1:新生代總大小
pause time1:因爲執行minor GC,Java應用暫停的時間。
starting occupancy3:GC執行前堆區域總大小
ending occupancy3:GC執行後堆區域總大小
total occupancy3:堆區總大小
pause time3:Java應用由於執行堆空間GC(包括full GC)而停止的時間。

jvm調優監控- -可視化工具

監控和分析GC也有一些可視化工具,比較常見的有JConsole和VisualVM。
http://blog.csdn.net/java2000_wl/article/details/8049707

jvm調優實踐1

1、JAVA_OPTS="$JAVA_OPTS -server -Xms3G -Xmx3G -Xss256k -XX:PermSize=128m -XX:MaxPermSize=128m -XX:NewSize=1G -XX:MaxNewSize=1G -XX:UseParallelOldGC - XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/aaa/dump -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/usr/aaa/dump/heap_trace.xtxt "
2、上述配置經觀察非常穩定,每次普通gc時間10ms左右,full gc基本不發生,或隔很長時間發生一次。
3、通過dump文件發現,每1個小時發生一次full gc,經多方求證,只要在jvm中開啓jmx服務,jmx會1小時執行一次full gc以清除引用。

jvm調優實踐2

類型            參數
運行模式        -sever
整個堆內存大小  爲-Xms和-Xmx設置相同的值。
新生代空間大小  -XX:NewRatio: 2到4. -XX:NewSize=? –XX:MaxNewSize=?. 使用NewSize代替NewRatio也是可以的。
持久代空間大小  -XX:PermSize=256m -XX:MaxPermSize=256m. 設置一個在運行中不會出現問題的值即可,這個參數不影響性能。
GC日誌          -Xloggc:$CATALINA_BASE/logs/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps. 記錄GC日誌並不會特別地影響Java程序性能,儘可能。
GC算法          -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75. 一般來說推薦使用這些配置,但是根據程序不同的特性,其他的也有可能更好。
發生OOM時創建堆內存轉儲文件     -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$CATALINA_BASE/logs
發生OOM後的操作                 -XX:OnOutOfMemoryError=$CATALINA_HOME/bin/stop.sh 或 -XX:OnOutOfMemoryError=$CATALINA_HOME/bin/restart.sh. 記錄內存轉儲文件後,爲了管理的需要執行一個合適的操作。
爲了得到程序的性能表現,需要以下這些信息:
    1)系統吞吐量(TPS、OPS):從整體概念上理解程序的性能。
    2)每秒請求數(Request Per Second – RPS):嚴格來說,RPS和單純的響應能力是不同的,但是你可以把它理解爲響應能力。通過這個指標,你能夠瞭解到用戶需要多長時間才能得到請求的結果。
    3)RPS的標準差:如果可能的話,還有必要包括事件的RPS。一旦出現了偏差,你應該檢查GC或者網絡系統。
stop-the-world耗時過長
stop-the-world耗時過長可能是由於GC參數不合理或者代碼實現不正確。你可以通過分析工具或堆內存轉儲文件(Heap dump)來定位問題,比如檢查堆內存中對象的類型和數量。如果在其中找到了很多不必要的對象,那麼最好去改進代碼。如果沒有發現創建對象的過程中有特別的問題,那麼最好單純地修改GC參數。
爲了適當地調整GC參數,你需要獲取一段足夠長時間的GC日誌,還必須知道哪些情況會導致長時間的stop-the-world。想了解更多關於如何選擇合適的GC參數,可以閱讀我同事的一篇博文:How to Monitor Java Garbage Collection。
CPU使用率過低
當系統發生阻塞,吞吐量和CPU使用率都會降低。這可能是由於網絡系統或者併發的問題。爲了解決這個問題,你可以分析線程轉儲信息(Thread dump)或者使用分析工具。閱讀這篇文章可以獲得更多關於線程轉儲分析的知識:How to Analyze Java Thread Dumps。
你可以使用商業的分析工具對線程鎖進行精確的分析,不過大部分時候,只需使用JVisualVM中的CPU分析器,就能獲得足夠的信息。
CPU使用率過高
如果吞吐量很低但是CPU使用率卻很高,很可能是低效率代碼導致的。這種情況下,你應該使用分析工具定位代碼中性能的瓶頸。可使用的工具有:JVisualVM、Eclipse TPTP或者JProbe。

jvm調優實踐-實例1

筆者昨日發現部分開發測試機器出現異常:java.lang.OutOfMemoryError: GC overhead limit exceeded,這個異常代表:GC爲了釋放很小的空間卻耗費了太多的時間,其原因一般有兩個:1,堆太小,2,有死循環或大對象;
筆者首先排除了第2個原因,因爲這個應用同時是在線上運行的,如果有問題,早就掛了。所以懷疑是這臺機器中堆設置太小;
使用ps -ef |grep "java"查看,發現:
http://images.cnitblog.com/blog/406312/201312/31180232-b35faf9a89dd48a184de58bc3a7e26ca.png
該應用的堆區設置只有768m,而機器內存有2g,機器上只跑這一個java應用,沒有其他需要佔用內存的地方。另外,這個應用比較大,需要佔用的內存也比較多;
筆者通過上面的情況判斷,只需要改變堆中各區域的大小設置即可,於是改成下面的情況:
http://images.cnitblog.com/blog/406312/201312/31180253-523e7c956672437cb0203cae27232059.png
跟蹤運行情況發現,相關異常沒有再出現;

jvm調優實踐-實例2

一個服務系統,經常出現卡頓,分析原因,發現Full GC時間太長:
jstat -gcutil:
S0     S1    E     O       P        YGC YGCT FGC FGCT  GCT
12.16 0.00 5.18 63.78 20.32  54   2.047 5     6.946  8.993
分析上面的數據,發現Young GC執行了54次,耗時2.047秒,每次Young GC耗時37ms,在正常範圍,而Full GC執行了5次,耗時6.946秒,每次平均1.389s,數據顯示出來的問題是:Full GC耗時較長,分析該系統的是指發現,NewRatio=9,也就是說,新生代和老生代大小之比爲1:9,這就是問題的原因:
1,新生代太小,導致對象提前進入老年代,觸發老年代發生Full GC;
2,老年代較大,進行Full GC時耗時較大;
優化的方法是調整NewRatio的值,調整到4,發現Full GC沒有再發生,只有Young GC在執行。這就是把對象控制在新生代就清理掉,沒有進入老年代(這種做法對一些應用是很有用的,但並不是對所有應用都要這麼做)

jvm調優實踐-實例3

一應用在性能測試過程中,發現內存佔用率很高,Full GC頻繁,使用sudo -u admin -H  jmap -dump:format=b,file=文件名.hprof pid 來dump內存,生成dump文件,並使用Eclipse下的mat差距進行分析,發現:
http://images.cnitblog.com/blog/406312/201312/31180203-01c1a6a38f35453c82b988da1458f2e0.png
從圖中可以看出,這個線程存在問題,隊列LinkedBlockingQueue所引用的大量對象並未釋放,導致整個線程佔用內存高達378m,此時通知開發人員進行代碼優化,將相關對象釋放掉即可。

jvm調優實踐-實例4

代碼:
//imports skipped for brevity
public class Producer implements Runnable {
  private static ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
  private Deque<byte[]> deque;
  private int objectSize;
  private int queueSize;
 
  public Producer(int objectSize, int ttl) {
    this.deque = new ArrayDeque<byte[]>();
    this.objectSize = objectSize;
    this.queueSize = ttl * 1000;
  }
 
  @Override
  public void run() {
    for (int i = 0; i < 100; i++) {
      deque.add(new byte[objectSize]);
      if (deque.size() > queueSize) {
        deque.poll();
      }
    }
  }
 
  public static void main(String[] args) throws InterruptedException {
    executorService.scheduleAtFixedRate(new Producer(200 * 1024 * 1024 / 1000, 5), 0, 100, TimeUnit.MILLISECONDS);
    executorService.scheduleAtFixedRate(new Producer(50 * 1024 * 1024 / 1000, 120), 0, 100, TimeUnit.MILLISECONDS);
    TimeUnit.MINUTES.sleep(10);
    executorService.shutdownNow();
  }
}
參數:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps
日誌:
2015-06-04T13:34:16.119-0200: 1.723: [GC (Allocation Failure) [PSYoungGen: 114016K->73191K(234496K)] 421540K->421269K(745984K), 0.0858176 secs] [Times: user=0.04 sys=0.06, real=0.09 secs]
2015-06-04T13:34:16.738-0200: 2.342: [GC (Allocation Failure) [PSYoungGen: 234462K->93677K(254976K)] 582540K->593275K(766464K), 0.2357086 secs] [Times: user=0.11 sys=0.14, real=0.24 secs]
2015-06-04T13:34:16.974-0200: 2.578: [Full GC (Ergonomics) [PSYoungGen: 93677K->70109K(254976K)] [ParOldGen: 499597K->511230K(761856K)] 593275K->581339K(1016832K), [Metaspace: 2936K->2936K(1056768K)], 0.0713174 secs] [Times: user=0.21 sys=0.02, real=0.07 secs]
以三個不同的配置各運行了10分鐘,在下表中總結了三個差距較大的結果:
堆       GC算法                  有效工作   長暫停
-Xmx12g  -XX:+UseConcMarkSweepGC  89.8%     560 ms
-Xmx12g  -XX:+UseParallelGC       91.5%     1,104 ms
-Xmx8g   -XX:+UseConcMarkSweepGC  66.3%     1,610 ms

jvm調優實踐-實例5

建議1:預測收集能力
讓我們看一下下面這個簡單的例子:
public static List reverse(List<?extends T> list) {
   List result = new ArrayList();
   for (int i = list.size() - 1; i >= 0; i--) {
       result.add(list.get(i));
    }
   return result;
}
以上方法分配了一個新的數組,再將另一個列表的項目填充其中,但只能按倒序填充。
但是,難就難在如何優化增加項目到新列表這一步驟。每次添加後,該列表還需確保其底層數組有足夠的空槽能裝下新項目。如果能裝下,它就會直接在下一個空槽中存儲新項目;但如果空間不夠,它就會重新分配一個底層數組,將舊數組的內容複製到新數組中,然後再添加新項目。這一過程會導致分配的多個數組都會佔據內存,直到GC最後來回收。
所以,我們可以在構建時告知數組需容納多少個項目,重構後的代碼如下:
public static List reverse(List<?extends T> list) {
   List result = new ArrayList(list.size());
   for (int i = list.size() - 1; i >= 0; i--) {
       result.add(list.get(i));
    }
   return result;
}
這樣一來,可以保證ArrayList構造函數在最初配置時就能容納下list.size()個項目,這意味着它不需要再在迭代中重新分配內存。
Guava的集合類則更加先進,允許我們用一個確切數量或估計值來初始化集合。
List result =Lists.newArrayListWithCapacity(list.size());
List result =Lists.newArrayListWithExpectedSize(list.size());
第一行代碼是我們知道有多少項目需要存儲的情況,第二行會分配一些多餘填充以適應預估誤差。
建議2:直接用處理流
當處理數據流時,如從文件中讀取數據或從網上下載數據,例如,我們通常可以從數據流中有所發現:
byte[] fileData = readFileToByteArray(newFile(“myfile.txt”));
由此產生的字節數組可以被解析爲XML文檔、JSON對象或協議緩衝消息,來命名一些常用選項。
當處理大型或未知大小的文件時,這個想法則不適用了,因爲當JVM無法分配文件大小的緩衝區時,則會出現OutOfMemoryErrors錯誤。
但是,即使數據大小看似能管理,當涉及到垃圾回收時,上述模式仍會造成大量開銷,因爲它在堆上分配了相當大的blob來容納文件數據。
更好的處理方式是使用合適的InputStream(本例中是FileInputStream),並直接將其送到分析器,而不是提前將整個文件讀到字節數組中。所有主要庫會將API直接暴露給解析流,例如:    
FileInputStream fis = newFileInputStream(fileName);
MyProtoBufMessage msg = MyProtoBufMessage.parseFrom(fis);
建議3:使用不可變對象
不變性有諸多優勢,但有一個優勢卻極少被重視,那就是不變性對垃圾回收的影響。
不可變對象是指對象一旦創建後,其字段(本例中指非原始字段)將無法被修改。例如:
public class ObjectPair {
   private final Object first;
   private final Object second;
   public ObjectPair(Object first, Object second) {
       this.first = first;
       this.second = second;
    }
    publicObject getFirst() {
       return first;
    }
   public Object getSecond() {
       return second;
    }
}
實例化上面類的結果爲不可變對象——所有的字段一旦標記後則不能再被修改。
不變性意味着在構造容器完成之前,由不可變容器引用的所有對象都已經創建。在GC看來:容器會和其最新的新生代保持一致。這意味着當對新生代(young generations)執行垃圾回收週期時,GC可以跳過老年代(older generations)中的不可變對象,因爲它知道不可變對象不能引用新生代的任何內容。
越少對象掃描意味着需掃描的內存頁越少,而越少的內存頁掃描意味着GC週期越短,同時也預示着更短的GC停頓和更好的整體吞吐量。
建議4:慎用字符串連接
字符串可能是任何基於JVM的應用中最普遍的非原始數據結構。但是,其隱含重量和使用便利性使得它們成爲應用內存變大的罪魁禍首。
很明顯,問題不在於被內聯和拘留的文字字符串,而在於字符串在運行時被分配和構建。接下來看看構建動態字符串的簡單示例:
public static String toString(T[] array) {
   String result = "[";
   for (int i = 0; i < array.length; i++) {
       result += (array[i] == array ? "this" : array[i]);
       if (i < array.length - 1) {
           result += ", ";
       }
    }
   result += "]";
   return result;
}
獲取數組並返回它的字符串表示是一個很不錯的方法,但這也正是對象分配的問題所在。
要看到其背後所有的語法糖並不容易,但真正的幕後場景應該是這樣:
public static String toString(T[] array) {
   String result = "[";
   for (int i = 0; i < array.length; i++) {
       StringBuilder sb1 = new StringBuilder(result);
       sb1.append(array[i] == array ? "this" : array[i]);
       result = sb1.toString();
       if (i < array.length - 1) {
           StringBuilder sb2 = new StringBuilder(result);
           sb2.append(", ");
           result = sb2.toString();
       }
    }
   StringBuilder sb3 = new StringBuilder(result);
   sb3.append("]");
   result = sb3.toString();
   return result;
}
字符串是不可變的,所以在其連接時並沒有被修改,而是依次分配新的字符串。此外,編譯器利用標準StringBuilder類來執行的這些鏈接。這就導致了雙重麻煩,在每次循環迭代時,我們得到(1)隱式分配臨時字符串,(2)隱式分配臨時的StringBuilder對象來幫助我們構建最終結果。
避免上述問題的最佳方法是明確使用StringBuilder並直接附加給它,而不是使用略幼稚的串聯運算符(“+”)。所以應該是這樣:
public static String toString(T[] array) {
   StringBuilder sb = new StringBuilder("[");
   for (int i = 0; i < array.length; i++) {
       sb.append(array[i] == array ? "this" : array[i]);
       if (i < array.length - 1) {
           sb.append(", ");
       }
    }
   sb.append("]");
   return sb.toString();
}
此時,在方法開始時我們只分配了StringBuilder。從這一點來看,所有的字符串和列表項都會被添加到唯一的StringBuilder中,最終只調用一次toString方法轉換成字符串,然後返回結果。
建議5:使用專門的原始集合
Java的標準庫非常方便且通用,支持使用集合綁定半靜態類型。例如,如果要用一組字符串(Set<String>),或一對字符串映射到字符串列表(Map<Pair, List<String>>),直接利用標準庫會非常方便。
事實上,問題之所以出現是因爲我們想把double類型的值放在 int 類型的list集合或map映射中。由於泛型不能調用原始集合,則可以用包裝類型代替,所以放棄List<int>而使用List<Integer>更好。
但其實這非常浪費,Integer本身就是一個完備對象,由12字節的對象頭和內部4字節的整數字段組合而成,加起來每個Integer對象佔16個字節,這是同樣大小的基類int類型長度的4倍!然而,更大的問題是所有這些Integer實際上都是垃圾回收過程中的對象實例。
爲了解決這個問題,我們在Takipi 中使用優秀Trove 集合庫。Trove放棄了一些(但不是全部)支持專業高效內存的原始集合的泛型。例如,不用浪費的Map
TIntDoubleMap map = newTIntDoubleHashMap();
map.put(5, 7.0);
map.put(-1, 9.999);
...
Trove底層實現了原始數組的使用,所以在操作集合時沒有裝箱(int -> Integer)或拆箱(Integer -> int)發生,因此也不會將對象存儲在基類中。

jvm調優實踐-實例6

開始之前
Java 虛擬機有自己完善的硬件架構, 如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 屏蔽了與具體操作系統平臺相關的信息,使得 Java 程序只需生成在 Java 虛擬機上運行的目標代碼 (字節碼), 就可以在多種平臺上不加修改地運行。Java 虛擬機在執行字節碼時,實際上最終還是把字節碼解釋成具體平臺上的機器指令執行。
注意:本文僅針對 JDK7、HotSPOT Java 虛擬機,對於 JDK8 引入的 JVM 新特性及其他 Java 虛擬機,本文不予關注。
我們以一個例子開始這篇文章。假設你是一個普通的 Java 對象,你出生在 Eden 區,在 Eden 區有許多和你差不多的小兄弟、小姐妹,可以把 Eden 區當成幼兒園,在這個幼兒園裏大家玩了很長時間。Eden 區不能無休止地放你們在裏面,所以當年紀稍大,你就要被送到學校去上學,這裏假設從小學到高中都稱爲 Survivor 區。開始的時候你在 Survivor 區裏面劃分出來的的“From”區,讀到高年級了,就進了 Survivor 區的“To”區,中間由於學習成績不穩定,還經常來回折騰。直到你 18 歲的時候,高中畢業了,該去社會上闖闖了。於是你就去了年老代,年老代裏面人也很多。在年老代裏,你生活了 20 年 (每次 GC 加一歲),最後壽終正寢,被 GC 回收。有一點沒有提,你在年老代遇到了一個同學,他的名字叫愛德華 (慕光之城裏的帥哥吸血鬼),他以及他的家族永遠不會死,那麼他們就生活在永生代。
之前的文章《JVM 垃圾回收器工作原理及使用實例介紹》中已經介紹過年輕代、年老代、永生代,本文主要講講如何運用這些區域,爲系統性能提供更好的幫助。本文不再重複這些概念,直接進入主題。
如何將新對象預留在年輕代
衆所周知,由於 Full GC 的成本遠遠高於 Minor GC,因此某些情況下需要儘可能將對象分配在年輕代,這在很多情況下是一個明智的選擇。雖然在大部分情況下,JVM 會嘗試在 Eden 區分配對象,但是由於空間緊張等問題,很可能不得不將部分年輕對象提前向年老代壓縮。因此,在 JVM 參數調優時可以爲應用程序分配一個合理的年輕代空間,以最大限度避免新對象直接進入年老代的情況發生。清單 1 所示代碼嘗試分配 4MB 內存空間,觀察一下它的內存使用情況。
清單 1. 相同大小內存分配
public class PutInEden {
 public static void main(String[] args){
 byte[] b1,b2,b3,b4;//定義變量
 b1=new byte[1024*1024];//分配 1MB 堆空間,考察堆空間的使用情況
 b2=new byte[1024*1024];
 b3=new byte[1024*1024];
 b4=new byte[1024*1024];
 }
}
使用 JVM 參數-XX:+PrintGCDetails -Xmx20M -Xms20M 運行清單 1 所示代碼,輸出如清單 2 所示。
清單 2. 清單 1 運行輸出
[GC [DefNew: 5504K->640K(6144K), 0.0114236 secs] 5504K->5352K(19840K),
   0.0114595 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
[GC [DefNew: 6144K->640K(6144K), 0.0131261 secs] 10856K->10782K(19840K),
0.0131612 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
[GC [DefNew: 6144K->6144K(6144K), 0.0000170 secs][Tenured: 10142K->13695K(13696K),
0.1069249 secs] 16286K->15966K(19840K), [Perm : 376K->376K(12288K)],
0.1070058 secs] [Times: user=0.03 sys=0.00, real=0.11 secs]
[Full GC [Tenured: 13695K->13695K(13696K), 0.0302067 secs] 19839K->19595K(19840K),
[Perm : 376K->376K(12288K)], 0.0302635 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
[Full GC [Tenured: 13695K->13695K(13696K), 0.0311986 secs] 19839K->19839K(19840K),
[Perm : 376K->376K(12288K)], 0.0312515 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
[Full GC [Tenured: 13695K->13695K(13696K), 0.0358821 secs] 19839K->19825K(19840K),
[Perm : 376K->371K(12288K)], 0.0359315 secs] [Times: user=0.05 sys=0.00, real=0.05 secs]
[Full GC [Tenured: 13695K->13695K(13696K), 0.0283080 secs] 19839K->19839K(19840K),
[Perm : 371K->371K(12288K)], 0.0283723 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[Full GC [Tenured: 13695K->13695K(13696K), 0.0284469 secs] 19839K->19839K(19840K),
[Perm : 371K->371K(12288K)], 0.0284990 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
[Full GC [Tenured: 13695K->13695K(13696K), 0.0283005 secs] 19839K->19839K(19840K),
[Perm : 371K->371K(12288K)], 0.0283475 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
[Full GC [Tenured: 13695K->13695K(13696K), 0.0287757 secs] 19839K->19839K(19840K),
[Perm : 371K->371K(12288K)], 0.0288294 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
[Full GC [Tenured: 13695K->13695K(13696K), 0.0288219 secs] 19839K->19839K(19840K),
[Perm : 371K->371K(12288K)], 0.0288709 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
[Full GC [Tenured: 13695K->13695K(13696K), 0.0293071 secs] 19839K->19839K(19840K),
[Perm : 371K->371K(12288K)], 0.0293607 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
[Full GC [Tenured: 13695K->13695K(13696K), 0.0356141 secs] 19839K->19838K(19840K),
[Perm : 371K->371K(12288K)], 0.0356654 secs] [Times: user=0.01 sys=0.00, real=0.03 secs]
Heap
 def new generation total 6144K, used 6143K [0x35c10000, 0x362b0000, 0x362b0000)
 eden space 5504K, 100% used [0x35c10000, 0x36170000, 0x36170000)
 from space 640K, 99% used [0x36170000, 0x3620fc80, 0x36210000)
 to space 640K, 0% used [0x36210000, 0x36210000, 0x362b0000)
 tenured generation total 13696K, used 13695K [0x362b0000, 0x37010000, 0x37010000)
 the space 13696K, 99% used [0x362b0000, 0x3700fff8, 0x37010000, 0x37010000)
 compacting perm gen total 12288K, used 371K [0x37010000, 0x37c10000, 0x3b010000)
 the space 12288K, 3% used [0x37010000, 0x3706cd20, 0x3706ce00, 0x37c10000)
 ro space 10240K, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000)
 rw space 12288K, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000)
清單 2 所示的日誌輸出顯示年輕代 Eden 的大小有 5MB 左右。分配足夠大的年輕代空間,使用 JVM 參數-XX:+PrintGCDetails -Xmx20M -Xms20M-Xmn6M 運行清單 1 所示代碼,輸出如清單 3 所示。
清單 3. 增大 Eden 大小後清單 1 運行輸出
[GC [DefNew: 4992K->576K(5568K), 0.0116036 secs] 4992K->4829K(19904K),
 0.0116439 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
[GC [DefNew: 5568K->576K(5568K), 0.0130929 secs] 9821K->9653K(19904K),
0.0131336 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
[GC [DefNew: 5568K->575K(5568K), 0.0154148 secs] 14645K->14500K(19904K),
0.0154531 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]
[GC [DefNew: 5567K->5567K(5568K), 0.0000197 secs][Tenured: 13924K->14335K(14336K),
0.0330724 secs] 19492K->19265K(19904K), [Perm : 376K->376K(12288K)],
0.0331624 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
[Full GC [Tenured: 14335K->14335K(14336K), 0.0292459 secs] 19903K->19902K(19904K),
[Perm : 376K->376K(12288K)], 0.0293000 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
[Full GC [Tenured: 14335K->14335K(14336K), 0.0278675 secs] 19903K->19903K(19904K),
[Perm : 376K->376K(12288K)], 0.0279215 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
[Full GC [Tenured: 14335K->14335K(14336K), 0.0348408 secs] 19903K->19889K(19904K),
[Perm : 376K->371K(12288K)], 0.0348945 secs] [Times: user=0.05 sys=0.00, real=0.05 secs]
[Full GC [Tenured: 14335K->14335K(14336K), 0.0299813 secs] 19903K->19903K(19904K),
[Perm : 371K->371K(12288K)], 0.0300349 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
[Full GC [Tenured: 14335K->14335K(14336K), 0.0298178 secs] 19903K->19903K(19904K),
[Perm : 371K->371K(12288K)], 0.0298688 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space[Full GC [Tenured:
14335K->14335K(14336K), 0.0294953 secs] 19903K->19903K(19904K),
[Perm : 371K->371K(12288K)], 0.0295474 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
[Full GC [Tenured
: 14335K->14335K(14336K), 0.0287742 secs] 19903K->19903K(19904K),
[Perm : 371K->371K(12288K)], 0.0288239 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
[Full GC [Tenuredat GCTimeTest.main(GCTimeTest.java:16)
: 14335K->14335K(14336K), 0.0287102 secs] 19903K->19903K(19904K),
[Perm : 371K->371K(12288K)], 0.0287627 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
Heap
 def new generation total 5568K, used 5567K [0x35c10000, 0x36210000, 0x36210000)
 eden space 4992K, 100% used [0x35c10000, 0x360f0000, 0x360f0000)
 from space 576K, 99% used [0x36180000, 0x3620ffe8, 0x36210000)
 to space 576K, 0% used [0x360f0000, 0x360f0000, 0x36180000)
 tenured generation total 14336K, used 14335K [0x36210000, 0x37010000, 0x37010000)
 the space 14336K, 99% used [0x36210000, 0x3700ffd8, 0x37010000, 0x37010000)
 compacting perm gen total 12288K, used 371K [0x37010000, 0x37c10000, 0x3b010000)
 the space 12288K, 3% used [0x37010000, 0x3706ce28, 0x3706d000, 0x37c10000)
 ro space 10240K, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000)
 rw space 12288K, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000)
通過清單 2 和清單 3 對比,可以發現通過設置一個較大的年輕代預留新對象,設置合理的 Survivor 區並且提供 Survivor 區的使用率,可以將年輕對象保存在年輕代。一般來說,Survivor 區的空間不夠,或者佔用量達到 50%時,就會使對象進入年老代 (不管它的年齡有多大)。清單 4 創建了 3 個對象,分別分配一定的內存空間。
清單 4. 不同大小內存分配
blic class PutInEden2 {
 public static void main(String[] args){
 byte[] b1,b2,b3;
 b1=new byte[1024*512];//分配 0.5MB 堆空間
 b2=new byte[1024*1024*4];//分配 4MB 堆空間
 b3=new byte[1024*1024*4];
 b3=null; //使 b3 可以被回收
 b3=new byte[1024*1024*4];//分配 4MB 堆空間
 }
}
使用參數-XX:+PrintGCDetails -Xmx1000M -Xms500M -Xmn100M -XX:SurvivorRatio=8 運行清單 4 所示代碼,輸出如清單 5 所示。
清單 5. 清單 4 運行輸出
Heap
 def new generation total 92160K, used 11878K [0x0f010000, 0x15410000, 0x15410000)
 eden space 81920K, 2% used [0x0f010000, 0x0f1a9a20, 0x14010000)
 from space 10240K, 99% used [0x14a10000, 0x1540fff8, 0x15410000)
 to space 10240K, 0% used [0x14010000, 0x14010000, 0x14a10000)
 tenured generation total 409600K, used 86434K [0x15410000, 0x2e410000, 0x4d810000)
 the space 409600K, 21% used [0x15410000, 0x1a878b18, 0x1a878c00, 0x2e410000)
 compacting perm gen total 12288K, used 2062K [0x4d810000, 0x4e410000, 0x51810000)
 the space 12288K, 16% used [0x4d810000, 0x4da13b18, 0x4da13c00, 0x4e410000)
No shared spaces configured.
清單 5 輸出的日誌顯示,年輕代分配了 8M,年老代也分配了 8M。我們可以嘗試加上-XX:TargetSurvivorRatio=90 參數,這樣可以提高 from 區的利用率,使 from 區使用到 90%時,再將對象送入年老代,運行清單 4 代碼,輸出如清單 6 所示。
清單 6. 修改運行參數後清單 4 輸出   
Heap
 def new generation total 9216K, used 9215K [0x35c10000, 0x36610000, 0x36610000)
 eden space 8192K, 100% used [0x35c10000, 0x36410000, 0x36410000)
 from space 1024K, 99% used [0x36510000, 0x3660fc50, 0x36610000)
 to space 1024K, 0% used [0x36410000, 0x36410000, 0x36510000)
 tenured generation total 10240K, used 10239K [0x36610000, 0x37010000, 0x37010000)
 the space 10240K, 99% used [0x36610000, 0x3700ff70, 0x37010000, 0x37010000)
 compacting perm gen total 12288K, used 371K [0x37010000, 0x37c10000, 0x3b010000)
 the space 12288K, 3% used [0x37010000, 0x3706cd90, 0x3706ce00, 0x37c10000)
 ro space 10240K, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000)
 rw space 12288K, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000)
如果將 SurvivorRatio 設置爲 2,將 b1 對象預存在年輕代。輸出如清單 7 所示。
清單 7. 再次修改運行參數後清單 4 輸出
Heap
 def new generation total 7680K, used 7679K [0x35c10000, 0x36610000, 0x36610000)
 eden space 5120K, 100% used [0x35c10000, 0x36110000, 0x36110000)
 from space 2560K, 99% used [0x36110000, 0x3638fff0, 0x36390000)
 to space 2560K, 0% used [0x36390000, 0x36390000, 0x36610000)
 tenured generation total 10240K, used 10239K [0x36610000, 0x37010000, 0x37010000)
 the space 10240K, 99% used [0x36610000, 0x3700fff0, 0x37010000, 0x37010000)
 compacting perm gen total 12288K, used 371K [0x37010000, 0x37c10000, 0x3b010000)
 the space 12288K, 3% used [0x37010000, 0x3706ce28, 0x3706d000, 0x37c10000)
 ro space 10240K, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000)
rw space 12288K, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000)
如何讓大對象進入年老代
我們在大部分情況下都會選擇將對象分配在年輕代。但是,對於佔用內存較多的大對象而言,它的選擇可能就不是這樣的。因爲大對象出現在年輕代很可能擾亂年輕代 GC,並破壞年輕代原有的對象結構。因爲嘗試在年輕代分配大對象,很可能導致空間不足,爲了有足夠的空間容納大對象,JVM 不得不將年輕代中的年輕對象挪到年老代。因爲大對象佔用空間多,所以可能需要移動大量小的年輕對象進入年老代,這對 GC 相當不利。基於以上原因,可以將大對象直接分配到年老代,保持年輕代對象結構的完整性,這樣可以提高 GC 的效率。如果一個大對象同時又是一個短命的對象,假設這種情況出現很頻繁,那對於 GC 來說會是一場災難。原本應該用於存放永久對象的年老代,被短命的對象塞滿,這也意味着對堆空間進行了洗牌,擾亂了分代內存回收的基本思路。因此,在軟件開發過程中,應該儘可能避免使用短命的大對象。可以使用參數-XX:PetenureSizeThreshold 設置大對象直接進入年老代的閾值。當對象的大小超過這個值時,將直接在年老代分配。參數-XX:PetenureSizeThreshold 只對串行收集器和年輕代並行收集器有效,並行回收收集器不識別這個參數。
清單 8. 創建一個大對象
public class BigObj2Old {
 public static void main(String[] args){
 byte[] b;
 b = new byte[1024*1024];//分配一個 1MB 的對象
 }
}
使用 JVM 參數-XX:+PrintGCDetails –Xmx20M –Xms20MB 運行,可以得到清單 9 所示日誌輸出。
清單 9. 清單 8 運行輸出
Heap
 def new generation total 6144K, used 1378K [0x35c10000, 0x362b0000, 0x362b0000)
 eden space 5504K, 25% used [0x35c10000, 0x35d689e8, 0x36170000)
 from space 640K, 0% used [0x36170000, 0x36170000, 0x36210000)
 to space 640K, 0% used [0x36210000, 0x36210000, 0x362b0000)
 tenured generation total 13696K, used 0K [0x362b0000, 0x37010000, 0x37010000)
 the space 13696K, 0% used [0x362b0000, 0x362b0000, 0x362b0200, 0x37010000)
 compacting perm gen total 12288K, used 374K [0x37010000, 0x37c10000, 0x3b010000)
 the space 12288K, 3% used [0x37010000, 0x3706dac8, 0x3706dc00, 0x37c10000)
 ro space 10240K, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000)
 rw space 12288K, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000)
可以看到該對象被分配在了年輕代,佔用了 25%的空間。如果需要將 1MB 以上的對象直接在年老代分配,設置-XX:PetenureSizeThreshold=1000000,程序運行後輸出如清單 10 所示。
清單 10. 修改運行參數後清單 8 輸出
Heap
 def new generation total 6144K, used 354K [0x35c10000, 0x362b0000, 0x362b0000)
 eden space 5504K, 6% used [0x35c10000, 0x35c689d8, 0x36170000)
 from space 640K, 0% used [0x36170000, 0x36170000, 0x36210000)
 to space 640K, 0% used [0x36210000, 0x36210000, 0x362b0000)
 tenured generation total 13696K, used 1024K [0x362b0000, 0x37010000, 0x37010000)
 the space 13696K, 7% used [0x362b0000, 0x363b0010, 0x363b0200, 0x37010000)
 compacting perm gen total 12288K, used 374K [0x37010000, 0x37c10000, 0x3b010000)
 the space 12288K, 3% used [0x37010000, 0x3706dac8, 0x3706dc00, 0x37c10000)
 ro space 10240K, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000)
 rw space 12288K, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000)
清單 10 裏面可以看到當滿 1MB 時進入到了年老代。
如何設置對象進入年老代的年齡
堆中的每一個對象都有自己的年齡。一般情況下,年輕對象存放在年輕代,年老對象存放在年老代。爲了做到這點,虛擬機爲每個對象都維護一個年齡。如果對象在 Eden 區,經過一次 GC 後依然存活,則被移動到 Survivor 區中,對象年齡加 1。以後,如果對象每經過一次 GC 依然存活,則年齡再加 1。當對象年齡達到閾值時,就移入年老代,成爲老年對象。這個閾值的最大值可以通過參數-XX:MaxTenuringThreshold 來設置,默認值是 15。雖然-XX:MaxTenuringThreshold 的值可能是 15 或者更大,但這不意味着新對象非要達到這個年齡才能進入年老代。事實上,對象實際進入年老代的年齡是虛擬機在運行時根據內存使用情況動態計算的,這個參數指定的是閾值年齡的最大值。即,實際晉升年老代年齡等於動態計算所得的年齡與-XX:MaxTenuringThreshold 中較小的那個。清單 11 所示代碼爲 3 個對象申請了若干內存。
清單 11. 申請內存
public class MaxTenuringThreshold {
 public static void main(String args[]){
 byte[] b1,b2,b3;
 b1 = new byte[1024*512];
 b2 = new byte[1024*1024*2];
 b3 = new byte[1024*1024*4];
 b3 = null;
 b3 = new byte[1024*1024*4];
 }
}
參數設置爲:-XX:+PrintGCDetails -Xmx20M -Xms20M -Xmn10M -XX:SurvivorRatio=2
運行清單 11 所示代碼,輸出如清單 12 所示。
清單 12. 清單 11 運行輸出
[GC [DefNew: 2986K->690K(7680K), 0.0246816 secs] 2986K->2738K(17920K),
 0.0247226 secs] [Times: user=0.00 sys=0.02, real=0.03 secs]
[GC [DefNew: 4786K->690K(7680K), 0.0016073 secs] 6834K->2738K(17920K),
0.0016436 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
 def new generation total 7680K, used 4888K [0x35c10000, 0x36610000, 0x36610000)
 eden space 5120K, 82% used [0x35c10000, 0x36029a18, 0x36110000)
 from space 2560K, 26% used [0x36110000, 0x361bc950, 0x36390000)
 to space 2560K, 0% used [0x36390000, 0x36390000, 0x36610000)
 tenured generation total 10240K, used 2048K [0x36610000, 0x37010000, 0x37010000)
 the space 10240K, 20% used [0x36610000, 0x36810010, 0x36810200, 0x37010000)
 compacting perm gen total 12288K, used 374K [0x37010000, 0x37c10000, 0x3b010000)
 the space 12288K, 3% used [0x37010000, 0x3706db50, 0x3706dc00, 0x37c10000)
 ro space 10240K, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000)
 rw space 12288K, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000)
更改參數爲-XX:+PrintGCDetails -Xmx20M -Xms20M -Xmn10M -XX:SurvivorRatio=2 -XX:MaxTenuringThreshold=1,運行清單 11 所示代碼,輸出如清單 13 所示。
清單 13. 修改運行參數後清單 11 輸出
[GC [DefNew: 2986K->690K(7680K), 0.0047778 secs] 2986K->2738K(17920K),
 0.0048161 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 4888K->0K(7680K), 0.0016271 secs] 6936K->2738K(17920K),
0.0016630 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
 def new generation total 7680K, used 4198K [0x35c10000, 0x36610000, 0x36610000)
 eden space 5120K, 82% used [0x35c10000, 0x36029a18, 0x36110000)
 from space 2560K, 0% used [0x36110000, 0x36110088, 0x36390000)
 to space 2560K, 0% used [0x36390000, 0x36390000, 0x36610000)
 tenured generation total 10240K, used 2738K [0x36610000, 0x37010000, 0x37010000)
 the space 10240K, 26% used [0x36610000, 0x368bc890, 0x368bca00, 0x37010000)
 compacting perm gen total 12288K, used 374K [0x37010000, 0x37c10000, 0x3b010000)
 the space 12288K, 3% used [0x37010000, 0x3706db50, 0x3706dc00, 0x37c10000)
 ro space 10240K, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000)
 rw space 12288K, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000)
清單 13 所示,第一次運行時 b1 對象在程序結束後依然保存在年輕代。第二次運行前,我們減小了對象晉升年老代的年齡,設置爲 1。即,所有經過一次 GC 的對象都可以直接進入年老代。程序運行後,可以發現 b1 對象已經被分配到年老代。如果希望對象儘可能長時間地停留在年輕代,可以設置一個較大的閾值。
穩定的 Java 堆 VS 動盪的 Java 堆
一般來說,穩定的堆大小對垃圾回收是有利的。獲得一個穩定的堆大小的方法是使-Xms 和-Xmx 的大小一致,即最大堆和最小堆 (初始堆) 一樣。如果這樣設置,系統在運行時堆大小理論上是恆定的,穩定的堆空間可以減少 GC 的次數。因此,很多服務端應用都會將最大堆和最小堆設置爲相同的數值。但是,一個不穩定的堆並非毫無用處。穩定的堆大小雖然可以減少 GC 次數,但同時也增加了每次 GC 的時間。讓堆大小在一個區間中震盪,在系統不需要使用大內存時,壓縮堆空間,使 GC 應對一個較小的堆,可以加快單次 GC 的速度。基於這樣的考慮,JVM 還提供了兩個參數用於壓縮和擴展堆空間。
-XX:MinHeapFreeRatio 參數用來設置堆空間最小空閒比例,默認值是 40。當堆空間的空閒內存小於這個數值時,JVM 便會擴展堆空間。
-XX:MaxHeapFreeRatio 參數用來設置堆空間最大空閒比例,默認值是 70。當堆空間的空閒內存大於這個數值時,便會壓縮堆空間,得到一個較小的堆。
當-Xmx 和-Xms 相等時,-XX:MinHeapFreeRatio 和-XX:MaxHeapFreeRatio 兩個參數無效。
清單 14. 堆大小設置
import java.util.Vector;
public class HeapSize {
 public static void main(String args[]) throws InterruptedException{
 Vector v = new Vector();
 while(true){
 byte[] b = new byte[1024*1024];
 v.add(b);
 if(v.size() == 10){
 v = new Vector();
 }
 Thread.sleep(1);
 }
 }
}
清單 14 所示代碼是測試-XX:MinHeapFreeRatio 和-XX:MaxHeapFreeRatio 的作用,設置運行參數爲-XX:+PrintGCDetails -Xms10M -Xmx40M -XX:MinHeapFreeRatio=40 -XX:MaxHeapFreeRatio=50 時,輸出如清單 15 所示。
清單 15. 修改運行參數後清單 14 輸出
[GC [DefNew: 2418K->178K(3072K), 0.0034827 secs] 2418K->2226K(9920K),
 0.0035249 secs] [Times: user=0.00 sys=0.00, real=0.03 secs]
[GC [DefNew: 2312K->0K(3072K), 0.0028263 secs] 4360K->4274K(9920K),
0.0029905 secs] [Times: user=0.00 sys=0.00, real=0.03 secs]
[GC [DefNew: 2068K->0K(3072K), 0.0024363 secs] 6342K->6322K(9920K),
0.0024836 secs] [Times: user=0.00 sys=0.00, real=0.03 secs]
[GC [DefNew: 2061K->0K(3072K), 0.0017376 secs][Tenured: 8370K->8370K(8904K),
0.1392692 secs] 8384K->8370K(11976K), [Perm : 374K->374K(12288K)],
0.1411363 secs] [Times: user=0.00 sys=0.02, real=0.16 secs]
[GC [DefNew: 5138K->0K(6336K), 0.0038237 secs] 13508K->13490K(20288K),
0.0038632 secs] [Times: user=0.00 sys=0.00, real=0.03 secs]
改用參數:-XX:+PrintGCDetails -Xms40M -Xmx40M -XX:MinHeapFreeRatio=40 -XX:MaxHeapFreeRatio=50,運行輸出如清單 16 所示。
清單 16. 再次修改運行參數後清單 14 輸出
[GC [DefNew: 10678K->178K(12288K), 0.0019448 secs] 10678K->178K(39616K),
 0.0019851 secs] [Times: user=0.00 sys=0.00, real=0.03 secs]
[GC [DefNew: 10751K->178K(12288K), 0.0010295 secs] 10751K->178K(39616K),
0.0010697 secs] [Times: user=0.00 sys=0.00, real=0.02 secs]
[GC [DefNew: 10493K->178K(12288K), 0.0008301 secs] 10493K->178K(39616K),
0.0008672 secs] [Times: user=0.00 sys=0.00, real=0.02 secs]
[GC [DefNew: 10467K->178K(12288K), 0.0008522 secs] 10467K->178K(39616K),
0.0008905 secs] [Times: user=0.00 sys=0.00, real=0.02 secs]
[GC [DefNew: 10450K->178K(12288K), 0.0008964 secs] 10450K->178K(39616K),
0.0009339 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC [DefNew: 10439K->178K(12288K), 0.0009876 secs] 10439K->178K(39616K),
0.0010279 secs] [Times: user=0.00 sys=0.00, real=0.02 secs]
從清單 16 可以看出,此時堆空間的垃圾回收穩定在一個固定的範圍。在一個穩定的堆中,堆空間大小始終不變,每次 GC 時,都要應對一個 40MB 的空間。因此,雖然 GC 次數減小了,但是單次 GC 速度不如一個震盪的堆。
增大吞吐量提升系統性能
吞吐量優先的方案將會儘可能減少系統執行垃圾回收的總時間,故可以考慮關注系統吞吐量的並行回收收集器。在擁有高性能的計算機上,進行吞吐量優先優化,可以使用參數:
java –Xmx3800m –Xms3800m –Xmn2G –Xss128k –XX:+UseParallelGC
   –XX:ParallelGC-Threads=20 –XX:+UseParallelOldGC
–Xmx380m –Xms3800m:設置 Java 堆的最大值和初始值。一般情況下,爲了避免堆內存的頻繁震盪,導致系統性能下降,我們的做法是設置最大堆等於最小堆。假設這裏把最小堆減少爲最大堆的一半,即 1900m,那麼 JVM 會儘可能在 1900MB 堆空間中運行,如果這樣,發生 GC 的可能性就會比較高;
-Xss128k:減少線程棧的大小,這樣可以使剩餘的系統內存支持更多的線程;
-Xmn2g:設置年輕代區域大小爲 2GB;
–XX:+UseParallelGC:年輕代使用並行垃圾回收收集器。這是一個關注吞吐量的收集器,可以儘可能地減少 GC 時間。
–XX:ParallelGC-Threads:設置用於垃圾回收的線程數,通常情況下,可以設置和 CPU 數量相等。但在 CPU 數量比較多的情況下,設置相對較小的數值也是合理的;
–XX:+UseParallelOldGC:設置年老代使用並行回收收集器。
嘗試使用大的內存分頁
CPU 是通過尋址來訪問內存的。32 位 CPU 的尋址寬度是 0~0xFFFFFFFF ,計算後得到的大小是 4G,也就是說可支持的物理內存最大是 4G。但在實踐過程中,碰到了這樣的問題,程序需要使用 4G 內存,而可用物理內存小於 4G,導致程序不得不降低內存佔用。爲了解決此類問題,現代 CPU 引入了 MMU(Memory Management Unit 內存管理單元)。MMU 的核心思想是利用虛擬地址替代物理地址,即 CPU 尋址時使用虛址,由 MMU 負責將虛址映射爲物理地址。MMU 的引入,解決了對物理內存的限制,對程序來說,就像自己在使用 4G 內存一樣。內存分頁 (Paging) 是在使用 MMU 的基礎上,提出的一種內存管理機制。它將虛擬地址和物理地址按固定大小(4K)分割成頁 (page) 和頁幀 (page frame),並保證頁與頁幀的大小相同。這種機制,從數據結構上,保證了訪問內存的高效,並使 OS 能支持非連續性的內存分配。在程序內存不夠用時,還可以將不常用的物理內存頁轉移到其他存儲設備上,比如磁盤,這就是大家耳熟能詳的虛擬內存。
在 Solaris 系統中,JVM 可以支持 Large Page Size 的使用。使用大的內存分頁可以增強 CPU 的內存尋址能力,從而提升系統的性能。
java –Xmx2506m –Xms2506m –Xmn1536m –Xss128k –XX:++UseParallelGC
 –XX:ParallelGCThreads=20 –XX:+UseParallelOldGC –XX:+LargePageSizeInBytes=256m
–XX:+LargePageSizeInBytes:設置大頁的大小。
過大的內存分頁會導致 JVM 在計算 Heap 內部分區(perm, new, old)內存佔用比例時,會出現超出正常值的劃分,最壞情況下某個區會多佔用一個頁的大小。
使用非佔有的垃圾回收器
爲降低應用軟件的垃圾回收時的停頓,首先考慮的是使用關注系統停頓的 CMS 回收器,其次,爲了減少 Full GC 次數,應儘可能將對象預留在年輕代,因爲年輕代 Minor GC 的成本遠遠小於年老代的 Full GC。
java –Xmx3550m –Xms3550m –Xmn2g –Xss128k –XX:ParallelGCThreads=20
 –XX:+UseConcMarkSweepGC –XX:+UseParNewGC –XX:+SurvivorRatio=8 –XX:TargetSurvivorRatio=90
 –XX:MaxTenuringThreshold=31
–XX:ParallelGCThreads=20:設置 20 個線程進行垃圾回收;
–XX:+UseParNewGC:年輕代使用並行回收器;
–XX:+UseConcMarkSweepGC:年老代使用 CMS 收集器降低停頓;
–XX:+SurvivorRatio:設置 Eden 區和 Survivor 區的比例爲 8:1。稍大的 Survivor 空間可以提高在年輕代回收生命週期較短的對象的可能性,如果 Survivor 不夠大,一些短命的對象可能直接進入年老代,這對系統來說是不利的。
–XX:TargetSurvivorRatio=90:設置 Survivor 區的可使用率。這裏設置爲 90%,則允許 90%的 Survivor 空間被使用。默認值是 50%。故該設置提高了 Survivor 區的使用率。當存放的對象超過這個百分比,則對象會向年老代壓縮。因此,這個選項更有助於將對象留在年輕代。
–XX:MaxTenuringThreshold:設置年輕對象晉升到年老代的年齡。默認值是 15 次,即對象經過 15 次 Minor GC 依然存活,則進入年老代。這裏設置爲 31,目的是讓對象儘可能地保存在年輕代區域。
結束語
通過本文的學習,讀者瞭解瞭如何將新對象預留在年輕代、如何讓大對象進入年老代、如何設置對象進入年老代的年齡、穩定的 Java 堆 VS 動盪的 Java 堆、增大吞吐量提升系統性能、嘗試使用大的內存分頁、使用非佔有的垃圾回收器等主題,通過實例及對應輸出解釋的形式讓讀者對於 JVM 優化有一個初步認識。如其他文章相同的觀點,沒有哪一條優化是固定不變的,讀者需要自己判斷、實踐後才能找到正確的道路。

線程池調優

1、java線程池有幾個重要的配置參數:
     1)corePoolSize:核心線程數
     2)maximumPoolSize:最大線程數,超過這個數量的任務會被拒絕,可以通過RejectedExecutionHandler接口自定處理方式。
     3)keepAliveTime:線程保持活動的時間。
     4)workQueue:工作隊列,存放執行的任務。
2、Queue不同選擇,線程池有完全不同的行爲:
     1)SynchronousQueue:沒有容量的等待隊列,insert必須等待remove後,每個任務分配一個線程。
     2)LinkedBlockingQueue:無界隊列,線程池將會忽略maximumPoolSize參數,而用corePoolSize數目的線程來處理隊列中的任務,未處理的則在隊列中排隊。
     3)ArrayBlockingQueue:有界隊列,很難調優。大的Queue和小的maximumPoolSize將導致cpu低負載,小的Queue和大的maximumPoolSize將導致queue沒作用。
3、java線程池的思想:任務放到queue中,當queue中放不下時,才考慮創建新的線程,當然最大線程數爲maximumPoolSize。如果queue滿了並且無法創建新線程,就決絕該任務。該設計導致“先放等執行”、“放不下再執行”、“拒絕不等待”。所以根據不同的queue,要提高吞吐量不能一味的增大maximumPoolSize。
4、需要的線程池的思想:能設置線程數的最小值、最大值,當最小值<任務數<最大值,創建新線程處理。當任務數>最大值,排隊等待有空閒線程再處理。
5、重新實現java線程池:
     1)選用SynchronousQueue,是線程池的maximumPoolSize發揮作用,防止線程被無限創建,適當提高maximumPoolSize來提高程序吞吐量。
     2)自定義RejectExecutionHandler,當線程數超過maximumPoolSize時,隔一段時間自動檢查線程池是否有空閒線程,如果有則執行新任務。檢查時間以來於keepAliveTime大小。

Servlet安全

servlet體系結構是建立在java多線程機制上的,它的生命週期是由web容器負責的。當客戶端第一次請求某servlet時,容器根據web.xml中配置實例化servlet類。當有新的客戶端請求該servlet時,一般不會再次實例化該servlet類,也就是多個線程在使用同一個實例。這樣,當多線程同時訪問一個servlet時,可能會發生多線程同時訪問同一資源的情況,數據會變的不一致。所以說servlet是線程不安的。所以servlet內,一般不聲明全局共享變量,而只是在方法中聲明局部變量,而這些局部變量是存放在棧幀中的,棧幀屬於線程私有的,不存在一致性問題。
A. JVM會試圖爲相關Java對象在Eden中初始化一塊內存區域
B. 當Eden空間足夠時,內存申請結束。否則到下一步
C. JVM試圖釋放在Eden中所有不活躍的對象(這屬於1或更高級的垃圾回收), 釋放後若Eden空間仍然不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區
D. Survivor區被用來作爲Eden及OLD的中間交換區域,當OLD區空間足夠時,Survivor區的對象會被移到Old區,否則會被保留在Survivor區
E. 當OLD區空間不夠時,JVM會在OLD區進行完全的垃圾收集(0級)
F. 完全垃圾收集後,若Survivor及OLD區仍然無法存放從Eden複製過來的部分對象,導致JVM無法在Eden區爲新對象創建內存區域,則出現”out of memory錯誤”
young generation的內存,由一塊Eden(伊甸園,有意思)和兩塊Survivor Space(1.4文檔中稱爲semi-space)構成。新創建的對象的內存都分配自eden。兩塊Survivor Space總有會一塊是空閒的,用作copying collection的目標空間。Minor collection的過程就是將eden和在用survivor space中的活對象copy到空閒survivor space中。所謂survivor,也就是大部分對象在伊甸園出生後,根本活不過一次GC。對象在young generation裏經歷了一定次數的minor collection後,年紀大了,就會被移到old generation中,稱爲tenuring。
剩餘內存空間不足會觸發GC,如eden空間不夠了就要進行minor collection,old generation空間不夠要進行major collection,permanent generation空間不足會引發full GC。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章