內存管理是開發者必須掌握的基本功,否則程序老是會在各類難以捉摸的錯誤中崩潰,一些語言,例如C、C++開發者們本身申請內存,使用完本身釋放,可是不當的代碼書寫習慣每每致使內存泄露,引用空指針等等錯誤,而Java藉助於虛擬機幫咱們完成了許多工做,使開發者從內存管理的深坑中爬出來了,可是因爲隔着這層虛擬機,出現問題時的應對策略更顯功力,須要對虛擬機內存管理機制的深刻了解。算法
這篇文章只是粗淺的介紹下虛擬機內存區域的大概分佈,讓初學者在腦海中有個大概印象,而印象的開始則藉助於下面的一幅圖:編程
Java內存區域分爲大的兩個區域,一部分是線程共享的,另外一部分則是每一個線程所獨有的。剛開始瞭解編程時,大體就有印象,棧內存存在於方法體中,定義的那些變量什麼的都是棧內存,方法結束就沒了,堆內存則是使用new關鍵字申請出來的。固然這只是粗線的認識,下面一塊塊的說上圖中的內存分佈。數組
相似於CPU中的PC寄存器,用於存放下一條指令的地址,可是虛擬機不使用CPU的程序計數器,而是本身在內存裏設立一片區域模擬CPU的程序計數器。改變計數器的值來選取下一條須要執行的字節碼指令,包括分支、循環、跳轉、異常、線程恢復等基礎功能都依賴於計數器。數據結構
Java的每一個線程都有其獨立的計數器,計數器之間互不影響,這樣當多線程操做時,一個掛起的線程在恢復時,仍然可以從計數器中恢復以前運行到的地方,繼續執行。因此說程序計數器也是線程隔離的。執行Java方法時,計數器中存放的是虛擬機字節碼的地址,而運行Native方法時則是空(undefined)。該區域不會產生OutOfMemoryError。多線程
Java虛擬機棧也是線程私有的,它的生命週期等同於線程的生命週期。虛擬機棧描述的是Java方法執行時的內存模型。當方法執行時,會建立一個棧幀,用於存儲方法執行期間所用到的數據結構,包含局部變量表,操做數棧,動態連接,方法出口等信息。post
當一個方法執行時,一個包含以上元素的棧幀入棧,當方法退出時,棧幀出棧。通常咱們都會知道內存區分爲堆區和棧區,實際上也是個粗淺的分法,棧指的就是虛擬機棧,而其中最重要的部分就是局部變量表。學習
Java的垃圾收集器是不會去回收棧上的內容的,由於棧上的內容老是隨着方法的結束自動釋放。局部變量表包含着各類編譯期已知的基本數據類型、對象引用和returnAddress。基本數據類型就是Java的8大基本數據類型(boolean,byte,char, short, int, float, long, double),對象引用,你能夠把它當成指向實際對象地址的指針或者一個表明對象的句柄,returnAddress則是一條字節碼指令的地址。當進入一個方法時,它所須要分配的空間在編譯期就是已知的了。線程
在虛擬機棧中可能會報如下兩種異常:3d
區別於虛擬機棧執行的是Java方法,本地方法棧則是虛擬機使用的Native方法服務,咱們在看一些庫的源碼時正常定位到最後就是用Native方法實現的,可是在虛擬機規範裏對本地方法使用的語言,使用方式進行硬性規定,因此虛擬機能夠任意實現它。HotSpot中本地方法棧和虛擬機棧是合在一塊兒的。指針
至少從學習C語言時,咱們就據說malloc方法會在堆上分配空間。Java的堆上也是分配實例對象的。虛擬機規範上講,基本全部的對象實例和數組都分配的堆上,例如上面棧上對象引用指向的對象,都是在堆上分配的。可是隨着編譯器技術的發展,全部對象都在堆上分配就不是那麼純粹了。
堆也是垃圾收集(GC)的主陣地。現代收集器基本都採用了分代收集算法,因此堆也能夠劃分爲新生代和老年代,再細緻點還有Eden空間,From Survivor空間和To Survivor空間。因爲虛擬機實現了自動垃圾收集,因此在Java中,堆中new出的對象是不須要手動釋放的。
咱們能夠經過-Xmx
和-Xms
控制堆的默認大小,若是在堆中再也申請不到內存,則會拋出OutOfMemoryError
異常。
方法區也是各個線程間共享的區域,通常存儲已被加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。通常不少人也把方法區稱爲永久代。其實僅僅是HotSpot團隊把GC分代收集也擴展到方法區了,讓垃圾收集器能夠一塊回收方法區的內存,可是其它的虛擬機實現沒有這麼作,也不存在永久代的,HotSpot自身也已經在JDK1.7上移除了永久代中的常量池。
相對而言,垃圾收集在方法區是不怎麼出現的。這個區域主要回收的是常量池和類型的卸載,可是實際上對兩者的回收發生的條件極爲苛刻,不多發生收集。
運行時常量池是方法區的一部分,Class文件中包含常量池信息,用於存放編譯期生成的各類字面量和符號引用,這部分在類加載後進入方法區的運行時常量池。這部分能夠參考個人:深刻理解JVM類文件格式