java虛擬機運行時數據區主要包含如下幾個模塊?java
程序計數器:用來記錄字節指令的行號;咱們將.java文件編譯成.class文件後,交由JVM去執行的時候,程序一行一行執行就是交給程序計數器去作的
Java虛擬機棧:好比咱們寫一個方法,JVM執行這個方法的時候,相似於建立了一個棧針;入棧到出棧就是這個方法調用的整個過程;對應的就是一個方法一個棧。
本地方法棧:就是JVM虛擬機執行一些本地方法庫;咱們在進行一個CAS操做的時候:經過unsafe的compareAndSwapInt調到本地方法庫裏面的native方法。那麼這些native方法就是在本地方法棧裏面運行的。
方法區:存放類信息,類變量,靜態變量。
堆:幾乎全部數組跟對象的建立都是在堆裏面。c++
程序計數器(Program Counter Register)是一塊較小的內存空間,是當前線程所執行的字節碼的行號指示器。程序員
內存區域中惟⼀⼀個在Java虛擬機規範中沒有規定任OutOfMemoryError 狀況的區域。由於程序計數器自己不須要咱們程序員去操做,因此不會出現OOM。數據庫
咱們建立一個Person類;擁有屬性age,提供getter/setter方法。數組
public class Person { public int getAge() { return age; } public void setAge(int age) { this.age = age; } private int age; }
咱們使用javac進行編譯源代碼爲字節碼,而後使用javap查看字節碼,以下: 數據結構
咱們經過javap -l能夠看到字節碼的內容;咱們看到裏面有兩個方法:setAge;getAge;而後咱們看到getAge方法在第5行。setAge在8,9行。如今若是咱們須要執行Person裏面的getAge方法;咱們說到,這個時候咱們可能多線程來執行這個方法;因此這塊運行時數據庫是一塊獨立的內存;咱們知道程序計數器是一塊線程隔離的數據庫,因此每塊線程有本身獨立的程序計數器。這塊內存是在咱們的線程裏面單獨隔離開來的,不一樣的線程維護了本身不一樣的程序計數器。多線程
上一節咱們講解了程序計數器;程序計數器是線程私有的一塊小內存,線程私有的內存除了程序計數器以外還有兩塊:java虛擬機棧,本地方法棧。方法區跟堆相對的就是線程共享的內存。jvm
Java方法執行的一塊內存區域;隨着線程方法執行時候壓棧出棧,此內存區域也會銷燬,他是跟線程的生命週期相同的。this
方法的執行是按照棧數據結構;每一個方法在執行的同時都會建立一個棧幀(Stack Frame)用於存放局部變量表、操做數棧、動態連接、⽅法出⼝等信息。每⼀個⽅法從調⽤直⾄執⾏完成的過程,就對應着⼀個棧幀在虛擬機棧中⼊棧到出棧的過程。spa
public class StackDemo { public static void a(){ System.out.println("method a executed"); } public static void b(){ //a(); b(); System.out.println("method b executed"); } public static void main(String[] args){ b(); System.out.println("method main executed"); } }
咱們修改b()的調用爲本身。上面進行了遞歸調用:b()方法執行不斷入棧操做,而沒有出棧,致使所分配的棧內存不夠,從而出現棧內存溢出。棧長度超過制定長度大小。結果以下:
java運行時數據區裏面的java虛擬機棧的講解;JVM運行時數據區裏面java虛擬機棧、本地方法棧、程序計數器這3快的內存是線程私有的。他的生命週期跟線程的生命週期是同樣的。java虛擬機棧跟本地方法棧是很類似的。他們的區別無非就是:java虛擬機棧他執行的是java方法,他會被編譯成字節碼。本地方法棧執行的是native方法。那麼什麼是native方法?native是一個修飾符。native方法比較特殊,他不容許有方法體。咱們native方法執行的時候,他會調用CAS(CAS是cpu的原子指令)。
native方法執行的一塊內存區域。
咱們java程序須要調用native方法從而調用cpu指令。知足CAS原子性操做
咱們經過java -version能夠查看使用的JVM類型。
本地方法實戰,在咱們juc裏面會有一些原子類:Atomic相關類,好比:以AtomicInteger爲例子。
咱們能夠看到上面demo;原子類進行CAS操做時候會調用底層native方法。
native方法就是調用java語言以外的語言,好比c語言/c++語言;咱們調用其餘語言的話,咱們將這個方法用關鍵字:native來修飾。java裏面原子類都是基於native方法調用cpu指令的。
上一節咱們講解了java運行時數據區裏面本地方法棧的講解;咱們講解了java虛擬機棧、本地方法棧、程序計數器的講解,這三塊是java運行時候數據區裏面的線程獨佔區內存。那麼除了線程獨佔區內存以外。咱們圖示左邊是線程共享區:方法區跟堆。咱們先講解java運行時數據區裏面的線程共享區:java堆。
Java中⽤來存放對象實例的最大一塊內存區域,【⼏乎全部的對象實例都在這⾥分配內
存】
咱們運行一個SpringBoot項目;經過ps或者jps查看其對應的pid。
而後經過指令:jmap -heap 13027;
Heap Configuration: //對應jvm啓動參數-XX:MinHeapFreeRatio設置JVM堆最小空閒比率(default 40) MinHeapFreeRatio = 0 //對應jvm啓動參數 -XX:MaxHeapFreeRatio設置JVM堆最大空閒比率(default 70) MaxHeapFreeRatio = 100 //對應jvm啓動參數-XX:MaxHeapSize=設置JVM堆的最大大小 MaxHeapSize = 16848519168 (16068.0MB) //對應jvm啓動參數-XX:NewSize=設置JVM堆的‘新生代’的默認大小 NewSize = 351272960 (335.0MB) //對應jvm啓動參數-XX:MaxNewSize=設置JVM堆的‘新生代’的最大大小 MaxNewSize = 5616173056 (5356.0MB) //對應jvm啓動參數-XX:OldSize=<value>:設置JVM堆的‘老生代’的大小 OldSize = 703594496 (671.0MB) //對應jvm啓動參數-XX:NewRatio=:‘新生代’和‘老生代’的大小比率 NewRatio = 2 //對應jvm啓動參數-XX:SurvivorRatio=設置年輕代中Eden區與Survivor區的大小比值 SurvivorRatio = 8 MetaspaceSize = 21807104 (20.796875MB) CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize = 17592186044415 MB G1HeapRegionSize = 0 (0.0MB) Heap Usage://堆內存分步 PS Young Generation Eden Space://Eden區內存分佈 capacity = 117964800 (112.5MB) //Eden區總容量 used = 27181312 (25.922119140625MB) //Eden區已使用 free = 90783488 (86.577880859375MB) //Eden區剩餘容量 23.041883680555557% used //Eden區使用比率 From Space: //其中一個Survivor區的內存分佈 capacity = 1572864 (1.5MB) used = 950272 (0.90625MB) free = 622592 (0.59375MB) 60.416666666666664% used To Space://另外一個Survivor區的內存分佈 capacity = 1572864 (1.5MB) used = 0 (0.0MB) free = 1572864 (1.5MB) 0.0% used PS Old Generation //當前的Old區內存分佈 capacity = 1331167232 (1269.5MB) used = 115252040 (109.91291046142578MB) free = 1215915192 (1159.5870895385742MB) 8.657968527879147% used 61594 interned Strings occupying 6891128 bytes.
上一節咱們講解了java運行時數據區塊裏面線程私有的:java虛擬機棧、本地方法棧、程序計數器;以及線程間共享的數據區:java堆。這一節咱們講解線程共享的數據區方法區:
線程共享的用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據的內存區域。
爲了存放長久存在的極少被垃圾回收的常量。Hotspot使⽤永久代來實現⽅法區 JRockit、IBM J9VM Java堆⼀樣爲了管理這部份內存。
運⾏時常量池是⽅法區的⼀部分,Class⽂件除了有類的版本、字段、⽅法、接⼝等描述信息外,還有⼀項信息是常量池,⽤於存放編譯器⽣成的各類字⾯量和符號引⽤,這部份內容將在類被加載後進⼊⽅法區的運⾏時常量池中存放。
什麼是類信息:類版本號、⽅法、接⼝。
咱們打開class文件時候:咱們能夠看到開頭是cafe babe(由於咱們的java的圖標是一杯咖啡),裏面就是一些類版本號、⽅法、接⼝信息。
用戶存放共有的常量數據。
運⾏時常量池是⽅法區的⼀部分,受到⽅法區內存的限制,當常量池再申請到內存時會拋出OutOfMemoryError異常。
咱們經過代碼發現:a跟b是相等的,若是說a跟b是存在在堆內存,那麼將會建立不一樣的對象,開闢不一樣的空間,那麼這個時候a跟b確定是不相等的。因此咱們知道a跟b是存放在方法區。而且經過源碼發現String類型的都是常量,常量是存放在方法區的。