堆內內存還是堆外內存?
一般情況下,Java中分配的非空對象都是由Java虛擬機的垃圾收集器管理的,也稱為堆內內存(on-heap memory)。虛擬機會定期對垃圾內存進行回收,在某些特定的時間點,它會進行一次徹底的回收(full gc)。徹底回收時,垃圾收集器會對所有分配的堆內內存進行完整的掃描,這意味著一個重要的事實——這樣一次垃圾收集對Java應用造成的影響,跟堆的大 小是成正比的。過大的堆會影響Java應用的性能。
對于這個問題,一種解決方案就是使用堆外內存(off-heap memory)。堆外內存意味著把內存對象分配在Java虛擬機的堆以外的內存,這些內存直接受操作系統管理(而不是虛擬機)。這樣做的結果就是能保持一個較小的堆,以減少垃圾收集對應用的影響。
但是Java本身也在不斷對堆內內存的實現方式做改進。兩者各有什么優缺點?Vanilla Java博客作者Peter Lawrey撰寫了一篇文章,在文中他對三種方式:用new來分配對象、對象池(object pool)和堆外內存,進行了詳細的分析。
用new來分配對象內存是最基本的一種方式,Lawery提到:
在Java 5.0之前,分配對象的代價很大,以至于大家都使用內存池。但是從5.0開始,對象分配和垃圾回收變得快多了,研發人員發現了性能的提升,紛紛簡化他們的 代碼,不再使用內存池,而直接用new來分配對象。從5.0開始,只有一些分配代價較大的對象,比如線程、套接字和數據庫鏈接,用內存池才會有明顯的性能 提升。
對于內存池,Lawery認為它主要用于兩類對象。第一類是生命周期較短,且結構簡單的對象,在內存池中重復利用這些對象能增加CPU緩存的命中率,從而提高性能。第二種情況是加載含有大量重復對象的大片數據,此時使用內存池能減少垃圾回收的時間。對此,Lawery還以StringInterner為例進行了說明。
最后Lawery分析了堆外內存,它和內存池一樣,也能縮短垃圾回收時間,但是它適用的對象和內存池完全相反。內存池往往適用于生命期較短的可變對象,而生命期中等或較長的對象,正是堆外內存要解決的。堆外內存有以下特點:
- 對于大內存有良好的伸縮性
- 對垃圾回收停頓的改善可以明顯感覺到
- 在進程間可以共享,減少虛擬機間的復制
Lawery還提到對外內存最重要的還不是它能改進性能,而是它的確定性。
當然堆外內存也有它自己的問題,最大的問題就是你的數據結構變得不那么直觀,如果數據結構比較復雜,就要對它進行串行化 (serialization),而串行化本身也會影響性能。另一個問題是由于你可以使用更大的內存,你可能開始擔心虛擬內存(即硬盤)的速度對你的影響 了。
Lawery還介紹了OpenHFT公司提供三個開源庫:Chronicle Queue、Chronicle Map和Thread Affinity,這些庫可以幫助開發人員使用堆外內存來保存數據。采用堆外內存有很多好處,同時也帶來挑戰,對堆外內存感興趣的讀者可以閱讀Lawery的原文來了解更多信息。
來自:http://www.infoq.com/cn/news/2014/12/external-memory-heap-memory