Java的內存 -JVM 內存管理


一.綜述

如果你學過C或者C++,那么你應該感受過它們對內存那種強大的掌控力。但是強大的能力往往需要更強大的控制力才能保證能力不被濫用,如果濫用C/C++的內存管理那么很容易出現指針滿天飛的情況,不出問題還好,一出問題debug起來簡直讓人頭疼得不要不要的。借用一句話,“指針一時爽,重構火葬場”。

而對java程序員來說,則沒有這樣的煩惱,因為java直接將內存管理交由jvm來管理,這樣程序員在編寫程序的時候就不用擔心內存的使用情況而可以專注內容的實現。但這其實也造成了一點隱患,如果你不了解jvm內存管理的機制,很可能會因一些錯誤的代碼寫法而導致內存泄漏或內存溢出。

注:所述內容取自Jdk1.6。

二.jvm內存結構

三.每部分存儲了哪些數據

1.程序計數器

程序計數器是一塊較小的內存空間,可以看作當前線程所執行字節碼的行號指示器,即指向正在執行的字節碼。在概念模型中,字節碼解釋器的工作就是通過改變這個程序計數器的值來選取下一條字節碼的指令。

值得一提的是,因為java的多線程是通過線程輪流切換並分配處理器執行時間來實現的(即一個小的時間段內仍然只有一個線程處於運行狀態),每個線程的執行指令都不一樣,為了使線程切換后能正確執行到該線程的下一指令,每個線程都需要一個獨立的程序計數器,所以程序計數器使線程私有的。

2.虛擬機棧

虛擬機堆和虛擬機棧可以說是jvm內存中最值得我們關注的兩塊內存區域。虛擬機棧是內存私有的,每個方法在執行的同時會創建一個棧幀。用於存儲局部變量表,操作數棧,動態鏈接等信息。每一個方法調用到執行完成的過程,其實就是對應一個棧幀在虛擬機棧中入棧到出棧的過程。

在這個區域可能出現的異常情況有兩種,分別是StackOverflowError和OutOfMemoryError。當棧動態拓展過深,比如無限遞歸時會出現StackOverflowError,而當無法申請到足夠內存時,則發生OutOfMemoryError。

3.堆

對大部分應用來說,堆是jvm管理的內存中最大的一塊。與虛擬機棧不同,堆是被所有線程共享的。它的作用是存放對象的實例,幾乎所有的對象實例都在這里分配內存。
堆同時也是垃圾收集器管理的主要區域,從內存回收的角度看,java堆可以分為“新生代”和“老年帶”。

java堆可以處於物理上不連續的內存空間中,只需要其是邏輯上連續的即可,如我們的磁盤空間。當在堆中無法申請到足夠的內存空間時,會拋出OutOfMemoryError。

在JDK1.6之前,字符串常量池一直放在方法區中,但是到了jdk1.7的時候,常量池便被移出方法區,而轉到Java堆中區了。

在HotSpot虛擬機里實現的字符串常量池功能的是一個StringTable類,它是一個Hash表。這個哈希表在每個HotSpot虛擬機的實例只有一份,被所有的類共享。字符串常量由一個一個字符組成,並且相同字符串只保留一份。

HotSpot虛擬機的說明如下:

Area: HotSpot 
Synopsis: In JDK 7, interned strings are no longer allocated in the permanent generation of the Java heap, but are instead allocated in the main part of the Java heap (known as the young and old generations), along with the other objects created by the application. This change will result in more data residing in the main Java heap, and less data in the permanent generation, and thus may require heap sizes to be adjusted. Most applications will see only relatively small differences in heap usage due to this change, but larger applications that load many classes or make heavy use of the String.intern() method will see more significant differences. 
RFE: 6962931 

大意便是說JDK1.7中的字符串不會再分配到Java的永久代中,而是分配到Java堆中。這意味着更多的數據將存於堆中而更少的數據存於方法區,這導致堆大小需要調整以做適配。由於此更改,大多數應用程序只會看到堆使用中的相對較小的差異,較大型的應用可能會發現其顯著差異

4.方法區

與java堆一樣,方法區也是所有線程共享的。它主要的功能時存儲虛擬機加載的類信息,常量,靜態變量,編譯后的代碼數據等。可以明顯發現,方法區存放的這些數據都是比較難以被回收的,所以這個區的垃圾回收行為較少發生。

若在方法區中無法申請到足夠的內存時,將會拋出OutOfMemoryError。

另外方法區中有一個運行時常量池。注意這里不是字符串常量池,它存儲的是類編譯時期生成的各種字面量和符號引用,並且每個類都有一個。

這里介紹一下什么是字面量和符號引用:

  • 字面量包括:1.文本字符串 2.八種基本類型的值 3.被聲明為final的常量等;
  • 符號引用包括:1.類和方法的全限定名 2.字段的名稱和描述符 3.方法的名稱和描述符。

 

四.內存溢出和內存泄漏

內存溢出很好理解,就是發生OutOfMemoryError,比如當Java堆中新建了太多實例,耗完內存后就會發生內存溢出。比如如下實例代碼:

 public class HeapOOM{ static class OOMObject{} public static void main(String[] args){ List<OOMObject> list = new ArrayList<OOMObject>(); while(true){ list.add(new OOMObject()); } } }

由於無限循環不斷新建對象,最終會導致內存溢出。

那么內存泄漏呢?

內存泄漏的原因主要是一個對象已經不再需要使用,但被另一個長對象持有時,就有可能發生內存泄漏。比如在方法內一個對象被全局的HashMap持有,方法執行結束沒有釋放就會導致內存泄漏。

再有就是當一個對象被存儲進HashSet后,其hashcode計算相關的變量被修改了,這也有可能導致內存泄漏,因為這時候這個對應基本已經不可達了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM