雖然做爲Java程序員,不用太多關心Java內存管理這一塊,是由JVM自動管理,這一點肯定比C++深得我心(最近在學C++,很痛苦,不說了....),不瞭解JVM的內存結構和各個內存區域的工做職責,將對解決問題帶來很大的麻煩,先來張圖表示下,《深刻理解Java虛擬機(第2版)》中的描述是下面這個樣子的:java
程序計數器屬於內存中比較小的一塊空間,屬於線程私有的,其做用能夠大概理解爲記錄當前線程所執行的字節碼位置,或者通俗來講能夠理解爲代碼執行到第幾行了,爲何須要這麼一小塊空間作這種事情呢,由於JVM的多線程操做實際上並非真正意義上的並行,是經過線程輪流切換並分配CPU執行時間片的方式來實現的,也就是一個CPU在某一個時間點只會執行一個線程中的指令,那麼在線程切換後,不知道本身以前執行的位置,豈不是很懵逼。。因此每一個線程都須要有一個獨立的程序計數器存儲,並且注意,此區域是惟一不會OutOfMemoryError狀況的區域,由於程序計數器是由虛擬機內部維護的。程序員
棧也是屬於線程私有的一塊區域,既然是棧,那裏面確定是要放東西的,虛擬機棧裏面存着的是一種叫「棧幀」的東西,每一個方法會建立一個棧幀,棧幀中存放了局部變量表、操做數棧、動態連接、方法出口等信息。經常所說的棧內存也就是局部變量表這一部份內存空間,隨着方法的調用到執行的完成,也就對應着棧幀的進棧出棧算法
這裏須要注意一點,每一個棧幀的大小是在編譯期就已經分配好了的,運行時並不會改變其內存的大小。數組
棧的內存在固定大小的狀況下,若是調用深度大於JVM的範圍,就會拋出StackOverflowError異常,咱們來模擬一下看看多線程
public class TestClass {
public static void main(String []agrs){
new TestClass().testStackOverFlowError();
}
public void testStackOverFlowError(){
System.out.println("run");
testStackOverFlowError();
}
}
Exception in thread "main" java.lang.StackOverflowError
atcom.example.hik.lib.TestClass.testStackOverFlowError(TestClass.java:9)
複製代碼
能夠看到直接就拋出異常了spa
和虛擬機棧其實相似,只不過一個是執行Java方法服務,而本地方法棧是執行native方法的,其餘就不過多介紹了,這塊區域涉及的比較少,可是與虛擬機棧同樣,本地方法棧也會拋StackOverflowError和OutOfMemoryError異常線程
堆算是整個JVM管理的最大的一塊區域,是屬於線程共享區的,也稱GC堆,在JVM啓動時建立。該內存區域存放了對象實例及數組(全部new的對象)。Java堆能夠處於物理上不連續的內存空間中,只要邏輯上是連續的便可,就像咱們的磁盤空間同樣。在實現時,既能夠實現成固定大小的,也能夠是可擴展的,不過當前主流的虛擬機都是按照可擴展來實現的(經過-Xmx和-Xms控制)code
從內存回收的角度上看,可分爲新生代(Eden空間,From Survivor空間、To Survivor空間)及老年代(Tenured Gen),具體回收算法就很少介紹了cdn
方法區也是線程共享區,用於存儲【虛擬機加載的類信息(類的版本、字段、方法、接口),常量,靜態變量,即時編譯器編譯後的代碼等數據】,Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池,用於存放編譯器生成的各類符號引用,這部份內容將在類加載後放到方法區的運行時常量池中。對象
public class TestClass {
public static void main(String []agrs){
A a =new A();
a.test();
}
}
複製代碼
有這麼一個類A,裏面有個test方法,那麼在執行這兩段代碼時,具體流程是怎麼樣的呢?
首先,建立一個A對象,運行時JVN先會去方法區找A的類型信息,若是沒有找到,那說明類A以前沒有被加載過,則使用Classloader將A.class字節碼文件加載到內存的方法區,將A類的類型消息存放至方法區,方便下次查找
第二步,JVM會在堆中給A的實例對象分配內存空間,通常是會被分配在新生代區域,這個實例是有着指向方法區中的A類型信息的引用,也就是第一步中的信息,指向方法區的內存地址
第三步,由於當前方法確定是執行在一個線程中的,那麼這個線程建立的同時,也建立一個虛擬機棧,在調用一個方法的時候就會建立一個棧幀,上面的a是A對象的引用,並持有着堆中A對象的內存地址
JVM經過a引用找到堆中A對象的內存地址,找到A對象實例,再經過指向方法區中地址的引用找到A類型信息,拿到test()方法的字節碼信息,而後再執行test()方法