JVM-運行時數據區

摘要

  • 自從java面世以來,聲勢浩大,提出「Write Once,Run Anywhere";Java相比於其餘C/C++語言的優點:在JVM內存管理之下,再也不須要爲每個new操做去手動分配內存和free/delete的內存釋放;不容易出現內存泄漏和內存溢出等問題。
  • 本節主要講解Java運行時數據區:線程共享數據區:方法區、堆 線程隔離數據去:虛擬機棧、本地方法棧、程序計數器

思惟導圖

image.png

內容

一、運行時數據區包含哪幾部分?

image.png
java虛擬機運行時數據區主要包含如下幾個模塊?java

  • 線程共享數據區:方法區、堆
  • 線程隔離數據區:虛擬機棧、本地⽅法棧、堆、程序計數器

程序計數器:用來記錄字節指令的行號;咱們將.java文件編譯成.class文件後,交由JVM去執行的時候,程序一行一行執行就是交給程序計數器去作的
Java虛擬機棧:好比咱們寫一個方法,JVM執行這個方法的時候,相似於建立了一個棧針;入棧到出棧就是這個方法調用的整個過程;對應的就是一個方法一個棧。
本地方法棧:就是JVM虛擬機執行一些本地方法庫;咱們在進行一個CAS操做的時候:經過unsafe的compareAndSwapInt調到本地方法庫裏面的native方法。那麼這些native方法就是在本地方法棧裏面運行的。
方法區:存放類信息,類變量,靜態變量。
堆:幾乎全部數組跟對象的建立都是在堆裏面。c++

二、程序計數器

是什麼?

程序計數器(Program Counter Register)是一塊較小的內存空間,是當前線程所執行的字節碼行號指示器程序員

爲何?

  1. 字節碼解釋器工做時候經過改變程序計數器的值來選取下一條要執行的字節碼指令;線程的各個基礎功能都須要依賴這個計數器來完成。
  2. java虛擬機多線程是經過線程輪流切換並分配處理器執行的時間實現,在任意一個肯定的時刻,一個處理器都只會執行一條線程指令,所以爲了線程切換後能恢復到正確的執行位置。每一個線程都須要有一個獨立的程序計數器,各線程之間計數器互不影響,獨立存儲。咱們稱這類內存區域爲「線程私有」的內存。

特色?

內存區域中惟⼀⼀個在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查看字節碼,以下:
image.png數據結構

image.png

咱們經過javap -l能夠看到字節碼的內容;咱們看到裏面有兩個方法:setAge;getAge;而後咱們看到getAge方法在第5行。setAge在8,9行。如今若是咱們須要執行Person裏面的getAge方法;咱們說到,這個時候咱們可能多線程來執行這個方法;因此這塊運行時數據庫是一塊獨立的內存;咱們知道程序計數器是一塊線程隔離的數據庫,因此每塊線程有本身獨立的程序計數器。這塊內存是在咱們的線程裏面單獨隔離開來的,不一樣的線程維護了本身不一樣的程序計數器。多線程

三、java虛擬機棧

上一節咱們講解了程序計數器;程序計數器是線程私有的一塊小內存,線程私有的內存除了程序計數器以外還有兩塊:java虛擬機棧,本地方法棧。方法區跟堆相對的就是線程共享的內存。jvm

是什麼?

Java方法執行的一塊內存區域;隨着線程方法執行時候壓棧出棧,此內存區域也會銷燬,他是跟線程的生命週期相同的。this

爲何?

方法的執行是按照棧數據結構;每一個方法在執行的同時都會建立一個棧幀(Stack Frame)用於存放局部變量表、操做數棧、動態連接、⽅法出⼝等信息。每⼀個⽅法從調⽤直⾄執⾏完成的過程,就對應着⼀個棧幀在虛擬機棧中⼊棧到出棧的過程。spa

特色?

  1. 局部變量表存放了編譯期可知(指代咱們這些基礎數據類型他所對應的數據的大小,大小是可知道的)的各類基本數據類型(boolean、byte、char、short、int、 float、long、double)以及對象引⽤(reference 類型)
  2. 若是線程請求的棧深度⼤於虛擬機所容許的深度,將拋出 StackOverflowError異常。

實戰

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()方法執行不斷入棧操做,而沒有出棧,致使所分配的棧內存不夠,從而出現棧內存溢出。棧長度超過制定長度大小。結果以下:

image.png

四、本地⽅法棧

java運行時數據區裏面的java虛擬機棧的講解;JVM運行時數據區裏面java虛擬機棧、本地方法棧、程序計數器這3快的內存是線程私有的。他的生命週期跟線程的生命週期是同樣的。java虛擬機棧跟本地方法棧是很類似的。他們的區別無非就是:java虛擬機棧他執行的是java方法,他會被編譯成字節碼。本地方法棧執行的是native方法。那麼什麼是native方法?native是一個修飾符。native方法比較特殊,他不容許有方法體。咱們native方法執行的時候,他會調用CAS(CAS是cpu的原子指令)。

是什麼?

native方法執行的一塊內存區域。

爲何?

咱們java程序須要調用native方法從而調用cpu指令。知足CAS原子性操做

特色?

  1. 與java虛擬機棧同樣也會拋出Stackoverflow.
  2. java虛擬機棧跟本地方法棧差很少,Hotshot將Java虛擬機棧和本地⽅法棧合⼆爲⼀;咱們上面講解的java運行時數據區是jvm標準,而Hotshot只是jvm虛擬機的實現。

image.png
咱們經過java -version能夠查看使用的JVM類型。

實戰

本地方法實戰,在咱們juc裏面會有一些原子類:Atomic相關類,好比:以AtomicInteger爲例子。
image.png
咱們能夠看到上面demo;原子類進行CAS操做時候會調用底層native方法。
image.png
native方法就是調用java語言以外的語言,好比c語言/c++語言;咱們調用其餘語言的話,咱們將這個方法用關鍵字:native來修飾。java裏面原子類都是基於native方法調用cpu指令的。

五、java堆

上一節咱們講解了java運行時數據區裏面本地方法棧的講解;咱們講解了java虛擬機棧、本地方法棧、程序計數器的講解,這三塊是java運行時候數據區裏面的線程獨佔區內存。那麼除了線程獨佔區內存以外。咱們圖示左邊是線程共享區:方法區跟堆。咱們先講解java運行時數據區裏面的線程共享區:java堆。

是什麼?

Java中⽤來存放對象實例的最大一塊內存區域,【⼏乎全部的對象實例都在這⾥分配內
存】

爲何?

  1. 內存區域的惟⼀⽬的就是存放對象實例。
  2. Java 堆(Java Heap)是 Java 虛擬機所管理的內存中最⼤的⼀塊;Java 堆是被全部線程共享的⼀塊內存區域。

特色?

  1. Java 堆是垃圾收集器管理的主要區域,所以不少時候也被稱作「GC 堆」(Garbage Cash)。
  2. 經過:-Xmx -Xms。(最大堆內存、最小堆內存)。
  3. Java堆能夠分紅新⽣代和⽼年代 新⽣代可分爲To Survivor、From Survivor、Eden。

實戰

咱們運行一個SpringBoot項目;經過ps或者jps查看其對應的pid。
image.png

而後經過指令:jmap -heap 13027;
image.png

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堆⼀樣爲了管理這部份內存。

特色?

  1. 並⾮數據進⼊了⽅法區就如永久代的名字⼀樣「永久」存在了。這區域的內存回收⽬標主要是針對常量池的回收和對數據類型的卸載。---因此咱們把它叫作部分永久。
  2. ⽅法區也會拋出OutofMemoryError,當它⽆法滿⾜內存分配需求時(當咱們的方法區沒法知足內存分配時候),雖然咱們的方法區的內存沒有堆內存那麼大,可是當咱們的方法去裏面的類信息比較龐大時候就會出現OOM。

七、常量池

是什麼?

運⾏時常量池是⽅法區的⼀部分,Class⽂件除了有類的版本、字段、⽅法、接⼝等描述信息外,還有⼀項信息是常量池,⽤於存放編譯器⽣成的各類字⾯量和符號引⽤,這部份內容將在類被加載後進⼊⽅法區的運⾏時常量池中存放。

什麼是類信息:類版本號、⽅法、接⼝。
咱們打開class文件時候:咱們能夠看到開頭是cafe babe(由於咱們的java的圖標是一杯咖啡),裏面就是一些類版本號、⽅法、接⼝信息。

爲何?

用戶存放共有的常量數據。

特色?

運⾏時常量池是⽅法區的⼀部分,受到⽅法區內存的限制,當常量池再申請到內存時會拋出OutOfMemoryError異常。

實例

image.png
咱們經過代碼發現:a跟b是相等的,若是說a跟b是存在在堆內存,那麼將會建立不一樣的對象,開闢不一樣的空間,那麼這個時候a跟b確定是不相等的。因此咱們知道a跟b是存放在方法區。而且經過源碼發現String類型的都是常量,常量是存放在方法區的。

image.png

image.png

image.png

相關文章
相關標籤/搜索