jvm 知識

Java中並沒有一個類似的運算符。事實上,Java也不需要這種運算符。Java中基本類型的大小在語言規範中已經定義了,而C/C++中基本類型大小則跟平臺相關。Java有自己的通過序列化構建的IO框架。再者,由於Java中沒有指針,因此指針運算和內存塊拷貝之類的操作也不存在。
但是,Java程序員有時還是希望能知道一個Java對象到底用了多少內存的。不過這個問題的答案並不簡單。
首先要區分清楚的是shallow size和deep size。Shallow size是指對象自身佔用的內存大小,其引用對象的大小不算在內。而deep size,則是自身所佔內存大小和其遞歸引用的所有對象所佔內存大小的總和。大多數情況下,你會希望獲得一個對象的deep size,但是爲了知道這個值,首先要知道怎麼算shallow size,下面我來介紹一下。
有人抱怨JVM規範中沒有針對運行時Java對象的內存結構的說明,這也就是說JVM供應商可以按照自己的需要來實現這一點。後果就是,同一個類在不同的JVM上運行的實例對象佔用的內存大小會有差別。好在是世界上大部分人(包括我在內)都使用Sun HotSpot虛擬機,這就大大簡化了這個問題。我們接下來的討論也會基於32位的Sun公司的JVM。下面我介紹一些規則來輔助解釋JVM如何組織對象在內存中的佈局的。
沒有實例屬性的類的內存佈局
在Sun JVM中,(除了數組之外的)對象都有兩個機器字(words)的頭部。第一個字中包含這個對象的標示哈希碼以及其他一些類似鎖狀態和等標識信息,第二個字中包含一個指向對象的類的引用。另外,任何對象都是8個字節爲粒度進行對齊的。這就是對象內存佈局的第一個規則:
規則1:任何對象都是8個字節爲粒度進行對齊的。
比如,如果調用new Object(),由於Object類並沒有其他沒有其他可存儲的成員,那麼僅僅使用堆中的8個字節來保存兩個字的頭部即可。
繼承了Object的類的內存佈局
除了上面所說的8個字節的頭部,類屬性緊隨其後。屬性通常根據其大小來排列。例如,整型(int)以4個字節爲單位對齊,長整型(long)以8個字節爲單位對齊。這裏是出於性能考慮而這麼設計的:通常情況下,如果數據以4字節爲單位對齊,那麼從內存中讀4字節的數據並寫入到處理器的4字節寄存器是性價比更高的。
爲了節省內存,Sun VM並沒有按照屬性聲明時的順序來進行內存佈局。實際上,屬性在內存中按照下面的順序來組織:
1. 雙精度型(doubles)和長整型(longs)
2. 整型(ints)和浮點型(floats)
3. 短整型(shorts)和字符型(chars)
4. 布爾型(booleans)和字節型(bytes)
5. 引用類型(references)
內存使用率會通過這個機制得到優化。例如,如下聲明一個類:
class MyClass {
 
       byte a;
 
       int c;
 
       boolean d;
 
       long e;
 
       Object f;          
 
}
如果JVM並沒有打亂屬性的聲明順序,其對象內存佈局將會是下面這個樣子:
 
[HEADER:  8 bytes]  8
[a:       1 byte ]  9
[padding: 3 bytes] 12
[c:       4 bytes] 16
[d:       1 byte ] 17
[padding: 7 bytes] 24
[e:       8 bytes] 32
[f:       4 bytes] 36
[padding: 4 bytes] 40
此時,用於佔位的14個字節是浪費的,這個對象一共使用了40個字節的內存空間。但是,如果用上面的規則對這些對象重新排序,其內存結果會變成下面這個樣子:
[HEADER:  8 bytes]  8
[e:       8 bytes] 16
[c:       4 bytes] 20
[a:       1 byte ] 21
[d:       1 byte ] 22
[padding: 2 bytes] 24
[f:       4 bytes] 28
[padding: 4 bytes] 32
這次,用於佔位的只有6個字節,這個對象使用了32個字節的內存空間。
因此,對象內存佈局的第二個規則是:
規則2:類屬性按照如下優先級進行排列:長整型和雙精度類型;整型和浮點型;字符和短整型;字節類型和布爾類型,最後是引用類型。這些屬性都按照各自的單位對齊。
現在我們知道如何計算一個繼承了Object的類的實例的內存大小了。下面這個例子用來做下練習: java.lang.Boolean。這是其內存佈局:
[HEADER:  8 bytes]  8
[value:   1 byte ]  9
[padding: 7 bytes] 16
Boolean類的實例佔用16個字節的內存!驚訝吧?(別忘了最後用來佔位的7個字節)。
繼承其他類的子類的內存佈局
JVM所遵守的下面3個規則用來組織有父類的類的成員。對象內存佈局的規則3如下:
規則3:不同類繼承關係中的成員不能混合排列。首先按照規則2處理父類中的成員,接着纔是子類的成員。
舉例如下:
class A {
   long a;
   int b;
   int c;
}
 
class B extends A {
   long d;
}
 
類B的實例在內存中的存儲如下:
[HEADER:  8 bytes]  8
[a:       8 bytes] 16
[b:       4 bytes] 20
[c:       4 bytes] 24
[d:       8 bytes] 32
如果父類中的成員的大小無法滿足4個字節這個基本單位,那麼下一條規則就會起作用:
規則4:當父類中最後一個成員和子類第一個成員的間隔如果不夠4個字節的話,就必須擴展到4個字節的基本單位。
舉例如下:
class A {
   byte a;
}
 
class B {
   byte b;
}
[HEADER:  8 bytes]  8
[a:       1 byte ]  9
[padding: 3 bytes] 12
[b:       1 byte ] 13
[padding: 3 bytes] 16
注意到成員a被擴充了3個字節以保證和成員b之間的間隔是4個字節。這個空間不能被類B使用,因此被浪費了。
最後一條規則在下面情況下用來節省一些空間:如果子類成員是長整型或雙精度類型,並且父類並沒有用完8個字節。
規則5:如果子類第一個成員是一個雙精度或者長整型,並且父類並沒有用完8個字節,JVM會破壞規則2,按照整形(int),短整型(short),字節型(byte),引用類型(reference)的順序,向未填滿的空間填充。
舉例如下:
class A {
  byte a;
}
 
class B {
  long b;
  short c;  
  byte d;
}
其內存佈局如下:

7 [HEADER:  8 bytes]  8
[a:       1 byte ]  9
[padding: 3 bytes] 12
[c:       2 bytes] 14
[d:       1 byte ] 15
[padding: 1 byte ] 16
[b:       8 bytes] 24
在第12字節處,類A“結束”的地方,JVM沒有遵守規則2,而是在長整型之前插入一個短整型和一個字節型成員,這樣可以避免浪費3個字節。
數組的內存佈局
數組有一個額外的頭部成員,用來存放“長度”變量。數組元素以及數組本身,跟其他常規對象同樣,都需要遵守8個字節的邊界規則。
下面是一個有3個元素的字節數組的內存佈局:
1
2
3
4
5 [HEADER:  12 bytes] 12
[[0]:      1 byte ] 13
[[1]:      1 byte ] 14
[[2]:      1 byte ] 15
[padding:  1 byte ] 16
下面是一個有3個元素的長整型數字的內存佈局:
1
2
3
4
5 [HEADER:  12 bytes] 12
[padding:  4 bytes] 16
[[0]:      8 bytes] 24
[[1]:      8 bytes] 32
[[2]:      8 bytes] 40
內部類的內存佈局
非靜態內部類(Non-static inner classes)有一個額外的“隱藏”成員,這個成員是一個指向外部類的引用變量。這個成員是一個普通引用,因此遵守引用內存佈局的規則。內部類因此有4個字節的額外開銷。
最後的一點想法
我們已經學習了在32位Sun JVM中如何計算Java對象的shallow size。知道內存是如何組織的有助於理解類實例佔用的內存數。
 
下一篇文章中,會有些示例代碼,這些代碼會把相關內容整理到一起,用反射(reflection)來計算一個對象的deep size。如果你感興趣,請訂閱此源或者等待這個博客的更新吧!
Java中並沒有一個類似的運算符。事實上,Java也不需要這種運算符。Java中基本類型的大小在語言規範中已經定義了,而C/C++中基本類型大小則跟平臺相關。Java有自己的通過序列化構建的IO框架。再者,由於Java中沒有指針,因此指針運算和內存塊拷貝之類的操作也不存在。
但是,Java程序員有時還是希望能知道一個Java對象到底用了多少內存的。不過這個問題的答案並不簡單。
首先要區分清楚的是shallow size和deep size。Shallow size是指對象自身佔用的內存大小,其引用對象的大小不算在內。而deep size,則是自身所佔內存大小和其遞歸引用的所有對象所佔內存大小的總和。大多數情況下,你會希望獲得一個對象的deep size,但是爲了知道這個值,首先要知道怎麼算shallow size,下面我來介紹一下。
有人抱怨JVM規範中沒有針對運行時Java對象的內存結構的說明,這也就是說JVM供應商可以按照自己的需要來實現這一點。後果就是,同一個類在不同的JVM上運行的實例對象佔用的內存大小會有差別。好在是世界上大部分人(包括我在內)都使用Sun HotSpot虛擬機,這就大大簡化了這個問題。我們接下來的討論也會基於32位的Sun公司的JVM。下面我介紹一些規則來輔助解釋JVM如何組織對象在內存中的佈局的。
沒有實例屬性的類的內存佈局
在Sun JVM中,(除了數組之外的)對象都有兩個機器字(words)的頭部。第一個字中包含這個對象的標示哈希碼以及其他一些類似鎖狀態和等標識信息,第二個字中包含一個指向對象的類的引用。另外,任何對象都是8個字節爲粒度進行對齊的。這就是對象內存佈局的第一個規則:
規則1:任何對象都是8個字節爲粒度進行對齊的。
比如,如果調用new Object(),由於Object類並沒有其他沒有其他可存儲的成員,那麼僅僅使用堆中的8個字節來保存兩個字的頭部即可。
繼承了Object的類的內存佈局
除了上面所說的8個字節的頭部,類屬性緊隨其後。屬性通常根據其大小來排列。例如,整型(int)以4個字節爲單位對齊,長整型(long)以8個字節爲單位對齊。這裏是出於性能考慮而這麼設計的:通常情況下,如果數據以4字節爲單位對齊,那麼從內存中讀4字節的數據並寫入到處理器的4字節寄存器是性價比更高的。
爲了節省內存,Sun VM並沒有按照屬性聲明時的順序來進行內存佈局。實際上,屬性在內存中按照下面的順序來組織:
1. 雙精度型(doubles)和長整型(longs)
2. 整型(ints)和浮點型(floats)
3. 短整型(shorts)和字符型(chars)
4. 布爾型(booleans)和字節型(bytes)
5. 引用類型(references)
內存使用率會通過這個機制得到優化。例如,如下聲明一個類:
1
2
3
4
5
6
7
8
9
10
11
12
13 class MyClass {
 
       byte a;
 
       int c;
 
       boolean d;
 
       long e;
 
       Object f;          
 
}
如果JVM並沒有打亂屬性的聲明順序,其對象內存佈局將會是下面這個樣子:
 
1
2
3
4
5
6
7
8
9 [HEADER:  8 bytes]  8
[a:       1 byte ]  9
[padding: 3 bytes] 12
[c:       4 bytes] 16
[d:       1 byte ] 17
[padding: 7 bytes] 24
[e:       8 bytes] 32
[f:       4 bytes] 36
[padding: 4 bytes] 40
此時,用於佔位的14個字節是浪費的,這個對象一共使用了40個字節的內存空間。但是,如果用上面的規則對這些對象重新排序,其內存結果會變成下面這個樣子:
1
2
3
4
5
6
7
8 [HEADER:  8 bytes]  8
[e:       8 bytes] 16
[c:       4 bytes] 20
[a:       1 byte ] 21
[d:       1 byte ] 22
[padding: 2 bytes] 24
[f:       4 bytes] 28
[padding: 4 bytes] 32
這次,用於佔位的只有6個字節,這個對象使用了32個字節的內存空間。
因此,對象內存佈局的第二個規則是:
規則2:類屬性按照如下優先級進行排列:長整型和雙精度類型;整型和浮點型;字符和短整型;字節類型和布爾類型,最後是引用類型。這些屬性都按照各自的單位對齊。
現在我們知道如何計算一個繼承了Object的類的實例的內存大小了。下面這個例子用來做下練習: java.lang.Boolean。這是其內存佈局:
1
2
3 [HEADER:  8 bytes]  8
[value:   1 byte ]  9
[padding: 7 bytes] 16
Boolean類的實例佔用16個字節的內存!驚訝吧?(別忘了最後用來佔位的7個字節)。
繼承其他類的子類的內存佈局
JVM所遵守的下面3個規則用來組織有父類的類的成員。對象內存佈局的規則3如下:
規則3:不同類繼承關係中的成員不能混合排列。首先按照規則2處理父類中的成員,接着纔是子類的成員。
舉例如下:
1
2
3
4
5
6
7
8
9 class A {
   long a;
   int b;
   int c;
}
 
class B extends A {
   long d;
}
 
類B的實例在內存中的存儲如下:
1
2
3
4
5 [HEADER:  8 bytes]  8
[a:       8 bytes] 16
[b:       4 bytes] 20
[c:       4 bytes] 24
[d:       8 bytes] 32
如果父類中的成員的大小無法滿足4個字節這個基本單位,那麼下一條規則就會起作用:
規則4:當父類中最後一個成員和子類第一個成員的間隔如果不夠4個字節的話,就必須擴展到4個字節的基本單位。
舉例如下:
1
2
3
4
5
6
7
8
9
10
11
12 class A {
   byte a;
}
 
class B {
   byte b;
}
[HEADER:  8 bytes]  8
[a:       1 byte ]  9
[padding: 3 bytes] 12
[b:       1 byte ] 13
[padding: 3 bytes] 16
注意到成員a被擴充了3個字節以保證和成員b之間的間隔是4個字節。這個空間不能被類B使用,因此被浪費了。
最後一條規則在下面情況下用來節省一些空間:如果子類成員是長整型或雙精度類型,並且父類並沒有用完8個字節。
規則5:如果子類第一個成員是一個雙精度或者長整型,並且父類並沒有用完8個字節,JVM會破壞規則2,按照整形(int),短整型(short),字節型(byte),引用類型(reference)的順序,向未填滿的空間填充。
舉例如下:
1
2
3
4
5
6
7
8
9 class A {
  byte a;
}
 
class B {
  long b;
  short c;  
  byte d;
}
其內存佈局如下:
1
2
3
4
5
6
7 [HEADER:  8 bytes]  8
[a:       1 byte ]  9
[padding: 3 bytes] 12
[c:       2 bytes] 14
[d:       1 byte ] 15
[padding: 1 byte ] 16
[b:       8 bytes] 24
在第12字節處,類A“結束”的地方,JVM沒有遵守規則2,而是在長整型之前插入一個短整型和一個字節型成員,這樣可以避免浪費3個字節。
數組的內存佈局
數組有一個額外的頭部成員,用來存放“長度”變量。數組元素以及數組本身,跟其他常規對象同樣,都需要遵守8個字節的邊界規則。
下面是一個有3個元素的字節數組的內存佈局:
1
2
3
4
5 [HEADER:  12 bytes] 12
[[0]:      1 byte ] 13
[[1]:      1 byte ] 14
[[2]:      1 byte ] 15
[padding:  1 byte ] 16
下面是一個有3個元素的長整型數字的內存佈局:
1
2
3
4
5 [HEADER:  12 bytes] 12
[padding:  4 bytes] 16
[[0]:      8 bytes] 24
[[1]:      8 bytes] 32
[[2]:      8 bytes] 40
內部類的內存佈局
非靜態內部類(Non-static inner classes)有一個額外的“隱藏”成員,這個成員是一個指向外部類的引用變量。這個成員是一個普通引用,因此遵守引用內存佈局的規則。內部類因此有4個字節的額外開銷。
最後的一點想法Java中並沒有一個類似的運算符。事實上,Java也不需要這種運算符。Java中基本類型的大小在語言規範中已經定義了,而C/C++中基本類型大小則跟平臺相關。Java有自己的通過序列化構建的IO框架。再者,由於Java中沒有指針,因此指針運算和內存塊拷貝之類的操作也不存在。
但是,Java程序員有時還是希望能知道一個Java對象到底用了多少內存的。不過這個問題的答案並不簡單。
首先要區分清楚的是shallow size和deep size。Shallow size是指對象自身佔用的內存大小,其引用對象的大小不算在內。而deep size,則是自身所佔內存大小和其遞歸引用的所有對象所佔內存大小的總和。大多數情況下,你會希望獲得一個對象的deep size,但是爲了知道這個值,首先要知道怎麼算shallow size,下面我來介紹一下。
有人抱怨JVM規範中沒有針對運行時Java對象的內存結構的說明,這也就是說JVM供應商可以按照自己的需要來實現這一點。後果就是,同一個類在不同的JVM上運行的實例對象佔用的內存大小會有差別。好在是世界上大部分人(包括我在內)都使用Sun HotSpot虛擬機,這就大大簡化了這個問題。我們接下來的討論也會基於32位的Sun公司的JVM。下面我介紹一些規則來輔助解釋JVM如何組織對象在內存中的佈局的。
沒有實例屬性的類的內存佈局
在Sun JVM中,(除了數組之外的)對象都有兩個機器字(words)的頭部。第一個字中包含這個對象的標示哈希碼以及其他一些類似鎖狀態和等標識信息,第二個字中包含一個指向對象的類的引用。另外,任何對象都是8個字節爲粒度進行對齊的。這就是對象內存佈局的第一個規則:
規則1:任何對象都是8個字節爲粒度進行對齊的。
比如,如果調用new Object(),由於Object類並沒有其他沒有其他可存儲的成員,那麼僅僅使用堆中的8個字節來保存兩個字的頭部即可。
繼承了Object的類的內存佈局
除了上面所說的8個字節的頭部,類屬性緊隨其後。屬性通常根據其大小來排列。例如,整型(int)以4個字節爲單位對齊,長整型(long)以8個字節爲單位對齊。這裏是出於性能考慮而這麼設計的:通常情況下,如果數據以4字節爲單位對齊,那麼從內存中讀4字節的數據並寫入到處理器的4字節寄存器是性價比更高的。
爲了節省內存,Sun VM並沒有按照屬性聲明時的順序來進行內存佈局。實際上,屬性在內存中按照下面的順序來組織:
1. 雙精度型(doubles)和長整型(longs)
2. 整型(ints)和浮點型(floats)
3. 短整型(shorts)和字符型(chars)
4. 布爾型(booleans)和字節型(bytes)
5. 引用類型(references)
內存使用率會通過這個機制得到優化。例如,如下聲明一個類:
1
2
3
4
5
6
7
8
9
10
11
12
13 class MyClass {
 
       byte a;
 
       int c;
 
       boolean d;
 
       long e;
 
       Object f;          
 
}
如果JVM並沒有打亂屬性的聲明順序,其對象內存佈局將會是下面這個樣子:
 
1
2
3
4
5
6
7
8
9 [HEADER:  8 bytes]  8
[a:       1 byte ]  9
[padding: 3 bytes] 12
[c:       4 bytes] 16
[d:       1 byte ] 17
[padding: 7 bytes] 24
[e:       8 bytes] 32
[f:       4 bytes] 36
[padding: 4 bytes] 40
此時,用於佔位的14個字節是浪費的,這個對象一共使用了40個字節的內存空間。但是,如果用上面的規則對這些對象重新排序,其內存結果會變成下面這個樣子:
1
2
3
4
5
6
7
8 [HEADER:  8 bytes]  8
[e:       8 bytes] 16
[c:       4 bytes] 20
[a:       1 byte ] 21
[d:       1 byte ] 22
[padding: 2 bytes] 24
[f:       4 bytes] 28
[padding: 4 bytes] 32
這次,用於佔位的只有6個字節,這個對象使用了32個字節的內存空間。
因此,對象內存佈局的第二個規則是:
規則2:類屬性按照如下優先級進行排列:長整型和雙精度類型;整型和浮點型;字符和短整型;字節類型和布爾類型,最後是引用類型。這些屬性都按照各自的單位對齊。
現在我們知道如何計算一個繼承了Object的類的實例的內存大小了。下面這個例子用來做下練習: java.lang.Boolean。這是其內存佈局:
1
2
3 [HEADER:  8 bytes]  8
[value:   1 byte ]  9
[padding: 7 bytes] 16
Boolean類的實例佔用16個字節的內存!驚訝吧?(別忘了最後用來佔位的7個字節)。
繼承其他類的子類的內存佈局
JVM所遵守的下面3個規則用來組織有父類的類的成員。對象內存佈局的規則3如下:
規則3:不同類繼承關係中的成員不能混合排列。首先按照規則2處理父類中的成員,接着纔是子類的成員。
舉例如下:
1
2
3
4
5
6
7
8
9 class A {
   long a;
   int b;
   int c;
}
 
class B extends A {
   long d;
}
 
類B的實例在內存中的存儲如下:
1
2
3
4
5 [HEADER:  8 bytes]  8
[a:       8 bytes] 16
[b:       4 bytes] 20
[c:       4 bytes] 24
[d:       8 bytes] 32
如果父類中的成員的大小無法滿足4個字節這個基本單位,那麼下一條規則就會起作用:
規則4:當父類中最後一個成員和子類第一個成員的間隔如果不夠4個字節的話,就必須擴展到4個字節的基本單位。
舉例如下:
1
2
3
4
5
6
7
8
9
10
11
12 class A {
   byte a;
}
 
class B {
   byte b;
}
[HEADER:  8 bytes]  8
[a:       1 byte ]  9
[padding: 3 bytes] 12
[b:       1 byte ] 13
[padding: 3 bytes] 16
注意到成員a被擴充了3個字節以保證和成員b之間的間隔是4個字節。這個空間不能被類B使用,因此被浪費了。
最後一條規則在下面情況下用來節省一些空間:如果子類成員是長整型或雙精度類型,並且父類並沒有用完8個字節。
規則5:如果子類第一個成員是一個雙精度或者長整型,並且父類並沒有用完8個字節,JVM會破壞規則2,按照整形(int),短整型(short),字節型(byte),引用類型(reference)的順序,向未填滿的空間填充。
舉例如下:
1
2
3
4
5
6
7
8
9 class A {
  byte a;
}
 
class B {
  long b;
  short c;  
  byte d;
}
其內存佈局如下:
1
2
3
4
5
6
7 [HEADER:  8 bytes]  8
[a:       1 byte ]  9
[padding: 3 bytes] 12
[c:       2 bytes] 14
[d:       1 byte ] 15
[padding: 1 byte ] 16
[b:       8 bytes] 24
在第12字節處,類A“結束”的地方,JVM沒有遵守規則2,而是在長整型之前插入一個短整型和一個字節型成員,這樣可以避免浪費3個字節。
數組的內存佈局
數組有一個額外的頭部成員,用來存放“長度”變量。數組元素以及數組本身,跟其他常規對象同樣,都需要遵守8個字節的邊界規則。
下面是一個有3個元素的字節數組的內存佈局:
1
2
3
4
5 [HEADER:  12 bytes] 12
[[0]:      1 byte ] 13
[[1]:      1 byte ] 14
[[2]:      1 byte ] 15
[padding:  1 byte ] 16
下面是一個有3個元素的長整型數字的內存佈局:
1
2
3
4
5 [HEADER:  12 bytes] 12
[padding:  4 bytes] 16
[[0]:      8 bytes] 24
[[1]:      8 bytes] 32
[[2]:      8 bytes] 40
內部類的內存佈局
非靜態內部類(Non-static inner classes)有一個額外的“隱藏”成員,這個成員是一個指向外部類的引用變量。這個成員是一個普通引用,因此遵守引用內存佈局的規則。內部類因此有4個字節的額外開銷。
最後的一點想法
我們已經學習了在32位Sun JVM中如何計算Java對象的shallow size。知道內存是如何組織的有助於理解類實例佔用的內存數。
 
下一篇文章中,會有些示例代碼,這些代碼會把相關內容整理到一起,用反射(reflection)來計算一個對象的deep size。如果你感興趣,請訂閱此源或者等待這個博客的更新吧!
我們已經學習了在32位Sun JVM中如何計算Java對象的shallow size。知道內存是如何組織的有助於理解類實例佔用的內存數。


每個Java程序員遲早都會碰到下面這個錯誤:
java.lang.OutOfMemoryError
這個時候一般會建議採用如下方式解決這個錯誤:
增加MaxPermSize值
增加最大堆內存到512M(-xmx參數)
這篇文章會具體介紹Java堆空間和參數MaxPermSize的含義。這篇文章涉及下列主題,並採用Hotspot JVM:
垃圾回收器(Garbage Collector,GC)
哪個JVM?
JVM命令行選項
 
垃圾回收器
垃圾回收器負責:
分配內存
保證所有正在被引用的對象還存在於內存中
回收執行代碼已經不再引用的對象所佔的內存
 
應用執行時,定位和回收垃圾對象的過程會佔用總執行時間的將近25%,這會拖累應用的執行效率。


Hotspot VM提供的垃圾回收器是一個分代垃圾回收器(Generational GC)[9,16,18]-將內存劃分爲不同的階段,也就是說,不同的生命週期的對象放置在不同的地址池中。這樣的設計是基於弱年代假設(Weak Generational Hypothesis):
1.越早分配的對象越容易失效;
2.老對象很少會引用新對象。
 
這種分代方式可以減少垃圾回收的停頓時間以及大範圍對象的回收成本。Hotspot VM將其堆空間分爲三個分代空間:
1. 年輕代(Young Generation)
○     Java應用在分配Java對象時,這些對象會被分配到年輕代堆空間中去
○     這個空間大多是小對象並且會被頻繁回收
○     由於年輕代堆空間的垃圾回收會很頻繁,因此其垃圾回收算法會更加重視回收效率
2. 年老代(Old Generationn)
○     年輕代堆空間的長期存活對象會轉移到(也許是永久性轉移)年老代堆空間
○     這個堆空間通常比年輕代的堆空間大,並且其空間增長速度較緩
○     由於大部分JVM堆空間都分配給了年老代,因此其垃圾回收算法需要更節省空間,此算法需要能夠處理低垃圾密度的堆空間
3. 持久代(Permanent Generation)
○     存放VM和Java類的元數據(metadata),以及interned字符串和類的靜態變量
 
次收集(Minor GC)和全收集(Full GC)
當這三個分代的堆空間比較緊張或者沒有足夠的空間來爲新到的請求分配的時候,垃圾回收機制就會起作用。有兩種類型的垃圾回收方式:次收集和全收集。當年輕代堆空間滿了的時候,會觸發次收集將還存活的對象移到年老代堆空間。當年老代堆空間滿了的時候,會觸發一個覆蓋全範圍的對象堆的全收集。
 
次收集
當年輕代堆空間緊張時會被觸發
相對於全收集而言,收集間隔較短
全收集
當老年代或者持久代堆空間滿了,會觸發全收集操作
可以使用System.gc()方法來顯式的啓動全收集
全收集一般根據堆大小的不同,需要的時間不盡相同,但一般會比較長。不過,如果全收集時間超過3到5秒鐘,那就太長了[1]
 
全收集通常時間最長,並且是程序無法延遲執行或者無法達到吞吐量目標的主因。GC的目標是去減少程序運行過程中垃圾回收的頻率。爲了達到這個目的,可以從這兩方面入手:
從系統方面考慮:
○    儘量採用大堆,但是不要大到需要系統從磁盤上“換”頁。一般而言,可用的RAM(沒有被系統進程佔用的)的80%都應該分配給JVM。
○    Java堆空間越大,垃圾回收器和java應用在吞吐量(throughput)和延遲執行(latency)方面的效果越好。
從應用方面考慮:
○    減少對象分配(object allocations)操作,或者採用對象保留(object retention)方式有助於減小存活的數據大小,這也可以反過來幫助垃圾回收做的更好。
○    參考這篇文章—Java性能提升竅門[19]
 
內存溢出錯誤(OutOfMemoryError)
可怕的內存溢出錯誤是Java程序員最不願意看到的。然而這個錯誤還是會出現,尤其應用中涉及到大量的數據處理時,或應用運行時間過長時。
一個應用所佔內存大小包括:
Java堆大小
線程棧
I/O緩衝區
原生庫所分配的內存
 
當一個應用耗盡了內存並且JVM GC也無法回收任何對象空間的時候,就會發生內存溢出錯誤。但是,內存溢出錯誤並不一定就意味着內存泄露(memory leak)。也有可能只是一個配置問題,例如設置的堆大小(如果沒有設置那就是缺省的堆大小)對於應用來說是不夠用的。
 
JVM命令行參數
無論是客戶端應用還是服務器端應用,一旦系統運行緩慢並且垃圾回收所佔時間過長,你就會希望通過調整堆大小來改善這一點。不過,爲了不影響其他也跑在同一個系統中的應用,不應該將堆大小設置的過大。
GC調優是很重要的。找到最佳的分代堆空間是一個迭代的過程[3,10,12]。這裏我們假定你已經爲你的應用找到了最佳堆大小。那麼你可以採用下面的JVM命令來進行設置:


 
GC 命令行選項 描述
-Xms 設置Java堆大小的初始值/最小值。例如:-Xms512m (請注意這裏沒有”=”).
-Xmx 設置Java堆大小的最大值
-Xmn 設置年輕代對空間的初始值,最小值和最大值。請注意,年老代堆空間大小是依賴於年輕代堆空間大小的
-XX:PermSize=<n>[g|m|k] 設置持久代堆空間的初始值和最小值
-XX:MaxPermSize=<n>[g|m|k] 設置持久代堆空間的最大值
 
最後一點,最早在Java SE 5.0中有對服務器的人機工程學的介紹[13]。這個可以很好的減少服務器端應用的調優時間,尤其是在堆大小測量和複雜GC調優方面。很多情況下,服務器端調優的最好方式就是不去調優。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章