溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

java中JVM內(nèi)存劃分、棧區(qū)、堆區(qū)、方法區(qū)的區(qū)別

發(fā)布時(shí)間:2021-06-26 14:07:51 來(lái)源:億速云 閱讀:170 作者:chen 欄目:大數(shù)據(jù)

本篇內(nèi)容介紹了“java中JVM內(nèi)存劃分、棧區(qū)、堆區(qū)、方法區(qū)的區(qū)別”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

現(xiàn)在用一張圖來(lái)介紹每個(gè)區(qū)域存儲(chǔ)的內(nèi)容。

java中JVM內(nèi)存劃分、棧區(qū)、堆區(qū)、方法區(qū)的區(qū)別

運(yùn)行時(shí)數(shù)據(jù)區(qū)怎么理解?
JVM運(yùn)行時(shí)首先需要類(lèi)加載器(classLoader)加載所需類(lèi)的字節(jié)碼文件。加載完畢交由執(zhí)行引擎執(zhí)行,在執(zhí)行過(guò)程中需要一段空間來(lái)存儲(chǔ)數(shù)據(jù)(類(lèi)比CPU與主存)。這段內(nèi)存空間的分配和釋放過(guò)程正是我們需要關(guān)心的運(yùn)行時(shí)數(shù)據(jù)區(qū)。

運(yùn)行時(shí)數(shù)據(jù)區(qū)
運(yùn)行時(shí)數(shù)據(jù)區(qū)都包括,程序計(jì)數(shù)器,方法區(qū)(包含常量池),虛擬機(jī)棧,本地方法棧,堆 。 JVM本身就是一臺(tái)虛擬的計(jì)算機(jī),目的是為了實(shí)現(xiàn)一次編譯處處執(zhí)行。

程序計(jì)數(shù)器
程序計(jì)數(shù)器是一塊較小的內(nèi)存空間。他可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼行號(hào)指示器。字節(jié)碼解釋器工作就是通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選擇下一條需要執(zhí)行的字節(jié)碼指令。分支,循環(huán),跳轉(zhuǎn),異常,線程恢復(fù)等基礎(chǔ)功能都需要依賴這個(gè)計(jì)數(shù)器來(lái)完成。
由于JVM虛擬機(jī)的多線程是通過(guò)線程輪流切換并分配處理器執(zhí)行時(shí)間的方式實(shí)現(xiàn)的,在任何一個(gè)確定的時(shí)刻,一個(gè)處理器(對(duì)于多核處理器來(lái)說(shuō)是一個(gè)內(nèi)核)都只會(huì)執(zhí)行一條線程中的指令。當(dāng)切換到另外一條線程時(shí),若不保存當(dāng)前未執(zhí)行完線程的執(zhí)行位置,下次處理機(jī)再執(zhí)行這條線程時(shí),又要重新開(kāi)始執(zhí)行。這種情況顯然是不能容忍的。因此,為了線程切換后能正確的恢復(fù)到執(zhí)行位置,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器,各條線程之間計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ),我們稱(chēng)這類(lèi)內(nèi)存區(qū)域?yàn)椤€程私有’內(nèi)存,
如果線程正在執(zhí)行的是一個(gè)java方法,這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址,如果正在執(zhí)行的是native方法,這個(gè)計(jì)數(shù)器則為(undefined),此內(nèi)存區(qū)域是為一個(gè)在java虛擬機(jī)規(guī)范中沒(méi)有規(guī)定任何OutOfMemoryError情況的區(qū)域

java虛擬機(jī)棧
與程序計(jì)數(shù)器一樣java虛擬機(jī)也是線程私有的,他的生命周期與線程相同,虛擬機(jī)棧描述的是java方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame,可以這么理解棧幀,虛擬機(jī)棧包含N個(gè)棧幀每個(gè)棧幀包含局部變量表,操作數(shù)棧,動(dòng)態(tài)鏈接,方法出口等信息)。每個(gè)方法從調(diào)用到執(zhí)行完成這個(gè)過(guò)程,就對(duì)應(yīng)這一個(gè)棧幀在虛擬機(jī)棧中的入棧到出棧的過(guò)程。
經(jīng)常有人將java內(nèi)存分為堆,棧內(nèi)存,這其實(shí)是很粗糙的,java內(nèi)存的劃分遠(yuǎn)比這復(fù)雜。只能說(shuō)明傳統(tǒng)的程序員最關(guān)注的,與對(duì)象內(nèi)存分配關(guān)系最密切的就是這兩塊內(nèi)存堆,棧。棧就是現(xiàn)在說(shuō)的虛擬機(jī)棧,或者說(shuō)是虛擬機(jī)棧中棧幀的局部變量表部分。
局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類(lèi)型(boolean,byte,char,short,int,float,long,double),對(duì)象引用(reference類(lèi)型,他不等同于對(duì)象本身,可能是一個(gè)指向?qū)ο笃鹗嫉刂返囊弥羔槪部赡苁侵赶蛞粋€(gè)代表對(duì)象的句柄或其他與此相關(guān)的位置)和returnAddress類(lèi)型(指向了一條字節(jié)碼指令的地址)
其中64位長(zhǎng)度的long和double類(lèi)型會(huì)占用2個(gè)局部變量空間,其余的數(shù)據(jù)類(lèi)型只會(huì)占用1個(gè)局部變量空間。局部變量表所需的內(nèi)存空間在編譯期間完成內(nèi)存分配,當(dāng)進(jìn)入一個(gè)方法時(shí),這個(gè)方法需要在幀中分配多大的內(nèi)存空間是完全確定的,在方法運(yùn)行期間不會(huì)改變局部變量表的大小。
在java虛擬機(jī)規(guī)范中,對(duì)這個(gè)區(qū)域規(guī)定了兩種異常狀態(tài):如果線程請(qǐng)求的棧的深度大于虛擬機(jī)允許的深度,將拋出StackOverFlowError異常(棧溢出),如果虛擬機(jī)棧可以動(dòng)態(tài)擴(kuò)展(現(xiàn)在大部分java虛擬機(jī)都可以動(dòng)態(tài)擴(kuò)展,只不過(guò)java虛擬機(jī)規(guī)范中也允許固定長(zhǎng)度的java虛擬機(jī)棧),如果擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存空間,就會(huì)拋出OutOfmMemoryError異常(沒(méi)有足夠的內(nèi)存)

本地方法棧
本地方法棧(Native Method Stacks)與虛擬機(jī)棧所發(fā)揮的作用是非常相似的,他們之間的區(qū)別不過(guò)是虛擬機(jī)棧為虛擬機(jī)執(zhí)行java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則為虛擬機(jī)使用到的本地Native方法服務(wù),在虛擬機(jī)規(guī)范中對(duì)本地方法棧中的使用方法,語(yǔ)言,與數(shù)據(jù)結(jié)構(gòu)并沒(méi)有強(qiáng)制規(guī)定,因此具體的虛擬機(jī)可以自由實(shí)現(xiàn)它。甚至有的虛擬機(jī)(例如Sun HotSpot虛擬機(jī))直接就把本地方法棧和虛擬機(jī)棧合二為一。與虛擬機(jī)棧一樣本地方法棧也會(huì)拋出StackOverFlowError和OutOfmMemoryError異常

java堆
對(duì)于大多數(shù)應(yīng)用來(lái)說(shuō),java堆(java Heap)是java虛擬機(jī)管理內(nèi)存中的最大一塊。java堆是所有線程共享的一塊內(nèi)存管理區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。此內(nèi)存區(qū)域唯一目的就是存放對(duì)象的實(shí)例。幾乎所有對(duì)象實(shí)例都在堆中分配內(nèi)存。這一點(diǎn)在java虛擬機(jī)規(guī)范中的描述是:所有對(duì)象實(shí)例以及數(shù)組都要在堆上分配,但是隨著JIT編譯器的發(fā)展與逃逸技術(shù)逐漸成熟,棧上分配,標(biāo)量替換優(yōu)化技術(shù)將會(huì)導(dǎo)致一些微妙的變化發(fā)生,所有的對(duì)象都分配在堆上也不是變的那么“絕對(duì)”了。
java堆是垃圾回收器管理的主要區(qū)域,因此很多時(shí)候也被稱(chēng)為GC堆(Garbage Collected Heap)。從內(nèi)存回收的角度來(lái)看,由于現(xiàn)在收集器基本都采用分代收集算法,所以java堆中還可以細(xì)分為:新生代和年老代:在細(xì)致一點(diǎn)的劃分可以分為:Eden空間,F(xiàn)rom Survivor空間,To Survivor空間等。從內(nèi)存分配的角度來(lái)看,線程共享的java堆中可能劃分出多個(gè)線程私有的分配緩沖區(qū) ,不過(guò)無(wú)論如何劃分,都與存放內(nèi)容無(wú)關(guān),無(wú)論哪個(gè)區(qū)域存放的都是對(duì)象實(shí)例。進(jìn)一步劃分的目的是為了更好的回收內(nèi)存,或者更快的分配內(nèi)存。
根據(jù)java虛擬機(jī)規(guī)范的規(guī)定,java堆可以處在物理上不連續(xù)的內(nèi)存空間,只要邏輯上是連續(xù)的即可,就像我們的磁盤(pán)空間一樣。在實(shí)現(xiàn)上既可以實(shí)現(xiàn)成固定大小,也可以是可擴(kuò)展的大小,不過(guò)當(dāng)前主流的虛擬機(jī)都是按照可擴(kuò)展來(lái)實(shí)現(xiàn)的(通過(guò)-Xmx和-Xms控制)。如果在堆中沒(méi)有內(nèi)存實(shí)例完成分配,并且堆也無(wú)法在擴(kuò)展時(shí)將會(huì)拋出OutOfMemoryError異常。

方法區(qū)
方法區(qū)和java堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域,他用于存儲(chǔ)已被虛擬機(jī)加載的類(lèi)信息,常量,靜態(tài)變量,即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。雖然java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一部分,但是他還有個(gè)別名叫做Non-heap(非堆),目的應(yīng)該是與java堆區(qū)分開(kāi)來(lái)。
java虛擬機(jī)規(guī)范對(duì)方法區(qū)的限制非常寬松,除了和java堆一樣不需要連續(xù)的內(nèi)存和可以選擇固定大小或者可擴(kuò)展外,還可以選擇不實(shí)現(xiàn)垃圾收集。相對(duì)而言,垃圾收集在這個(gè)區(qū)域是比較少出現(xiàn)的,但并非數(shù)據(jù)進(jìn)入了方法區(qū)就如永久代的名字一樣永久存在了。這區(qū)域的內(nèi)存回收目標(biāo)重要是針對(duì)常量池的回收和類(lèi)型的卸載,一般來(lái)說(shuō)這個(gè)內(nèi)存區(qū)域的回收‘成績(jī)’比較難以令人滿意。尤其是類(lèi)型的卸載條件非常苛刻,但是這部分的回收確實(shí)是必要的。在sun公司的bug列表中,曾出現(xiàn)過(guò)的若干個(gè)嚴(yán)重的bug就是由于低版本的HotSpot虛擬機(jī)對(duì)此區(qū)域未完成回收導(dǎo)致的內(nèi)存溢出。

這里有三個(gè)概念需要清楚
1 常量池(Constant Pool): 常量池?cái)?shù)據(jù)編譯器被確定,是class文件中的一部分,存儲(chǔ)了類(lèi),方法,接口等中的常量,當(dāng)然也包括字符串常量。
常量池:可以理解為Class文件之中的資源倉(cāng)庫(kù),它是Class文件結(jié)構(gòu)中與其他項(xiàng)目資源關(guān)聯(lián)最多的數(shù)據(jù)類(lèi)型
常量池中主要存放兩大類(lèi)常量:字面量(Literal)和符號(hào)引用(Symbolic Reference)。
字面量:文本字符串、聲明為final的常量值等;
符號(hào)引用:類(lèi)和接口的完全限定名(Fully Qualified Name)、字段的名稱(chēng)和描述符(Descriptor)、方法的名稱(chēng)和描述符
2 字符串池/字符串常量池(String Pool/String Constant Pool):是常量池中的一部分,存儲(chǔ)了編譯器產(chǎn)生的字符串類(lèi)型數(shù)據(jù)

3 運(yùn)行時(shí)常量池(Runtime Constant Pool): 方法區(qū)的一部分,所有線程共享。虛擬機(jī)加載class文件后把常量池中的數(shù)據(jù)存放到運(yùn)行時(shí)常量池中
這里需要注重提一下在JDK1.6之前字符串常量池是存在于方法區(qū)之中,在JDK1.7和以上字符串常量池存在了堆之中。
這是官網(wǎng)翻譯后的中文說(shuō)明:在JDK 7中,在Java堆的永久生成中不再分配interned字符串,而是在Java堆的主要部分(稱(chēng)為young和old generation)中分配,以及應(yīng)用程序創(chuàng)建的其他對(duì)象。此更改將導(dǎo)致更多的數(shù)據(jù)駐留在主Java堆中,而在永久生成中數(shù)據(jù)更少,因此可能需要調(diào)整堆大小。由于這種變化,大多數(shù)應(yīng)用程序在堆使用上只會(huì)看到相對(duì)較小的差異,但是更大的應(yīng)用程序加載了許多類(lèi),或者大量使用了string . intern()方法將看到更顯著的差異。如果還有疑問(wèn)可進(jìn)行測(cè)試,測(cè)試參考http://blog.csdn.net/u014039577/article/details/50377805

直接內(nèi)存
直接內(nèi)存并不是虛擬機(jī)運(yùn)行內(nèi)存的一部分,也不是java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域。但是這部分內(nèi)存區(qū)域也被頻繁的使用,也可能導(dǎo)致OutOfMemoryError異常出現(xiàn),
在jdk1.4中新加入了NIO(New Input/Output)類(lèi),引入了一種基于通道(Channel)和緩沖區(qū)(Buffer)的I/O方式,他可以使用本地的函數(shù)庫(kù)直接分配堆外內(nèi)存,然后通過(guò)一個(gè)存儲(chǔ)在java堆中的DirectByteBuffer對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作,這樣能在一些場(chǎng)景中顯著提高性能,因?yàn)楸苊饬嗽趈ava堆中和Native堆中來(lái)回復(fù)制數(shù)據(jù)。
顯然本機(jī)直接內(nèi)存的分配不會(huì)受到j(luò)ava堆大小的限制,但是既然是內(nèi)存??隙ㄟ€會(huì)受到本機(jī)總內(nèi)存的限制。服務(wù)器管理員在配置虛擬機(jī)內(nèi)存參數(shù)時(shí),會(huì)根據(jù)實(shí)際內(nèi)存設(shè)置-Xmx等參數(shù)信息。但經(jīng)常忽略直接內(nèi)存,使得各個(gè)內(nèi)存區(qū)域總和大于物理內(nèi)存限制(包括物理和操作系統(tǒng)級(jí)的限制),從而導(dǎo)致動(dòng)態(tài)擴(kuò)展時(shí)OutOfMemoryError異常。

=============================================================================================

案例演示:

demo1

數(shù)據(jù)準(zhǔn)備

main類(lèi)

//運(yùn)行時(shí), jvm 把AppMain的信息都放入方法區(qū) 
public class AppMain {
    //main方法本身放入方法區(qū)
    public static void main(String[] args) {
        Sample test1 = new Sample("測(cè)試1");
        test1.printName();
    }
}

Sample 類(lèi)

public class Sample {
    private String name;

    //new Sample實(shí)例后,引用放入棧區(qū),  對(duì)象放入堆
    public  Sample(String name){
        this.name = name;
    }

    //printName方法本身放入 方法區(qū)
    public void printName(){
        System.out.println(name);
    }
}

在JVM中的棧,堆,方法區(qū)的交互

這里寫(xiě)圖片描述

=============================================================================================

demo2

public class baseTest {
    public static void main(String[] args) {
        //創(chuàng)建Car類(lèi)實(shí)例,開(kāi)始通過(guò)JVM向內(nèi)存加載
        Car car1 = new Car(4, "red");
        car1.show();

        Car car2 = new Car(2, "black");
        car2.show();
    }
}

class Car{
    private int wheelNum;//成員變量(堆)
    private String color;//成員變量(堆)

    public Car() {

    }

    public Car(int w, String c) {//形參w,c為局部變量(棧)
        this.wheelNum = w;
        this.color = c;
    }

    public void show(){//方法名show放到方法區(qū)
        System.out.println("car:wheelNum-"+wheelNum+"color-"+color);
    }
}

=============================================================================================

接下來(lái)使用一個(gè)小例子來(lái)對(duì)堆棧進(jìn)行更深一步的了解

String str = “a”;
String strr = “bc”;
String str1 = "abc";     //定義字符串變量str1
        String str2 = "abc";      //定義字符串變量str2
        String str3 = new String("abc"); //以new的方式定義字符串變量str3
        String str4 = new String("abc");//以new的方式定義字符串變量str4

String str5 = str + strr;
String str6 = “a” + “bc”;
       結(jié)果:
         * str1 ==str2    true;     ①
         * str2 ==str3    false;     ②
         * str3 ==str4    false;    ③
         *str1 == str5  false;       ④
         *str1.equals(str5)   true;   ⑤
         *str1==str6        true;   ⑥
講解:

‘==’比較的是地址
     Str1 和 str2顯然指向的是String中常量池中的一個(gè)地址。(何靈鴻上次發(fā)的String的兩種創(chuàng)建方式)

equals比較的是內(nèi)容

①  由于地址相同,所以true
②  由于str3 使用的new ,相當(dāng)于在String類(lèi)堆中創(chuàng)建了一個(gè)堆地址。而str2的地址則是在常量池中。地址對(duì)不上,因此false
③  和②的原理是一樣的,相當(dāng)于new了兩個(gè)對(duì)象,也就是各自擁有了自己的地址。因此false。
④  str5使用的是字符串變量的相加。當(dāng)看String的源碼的時(shí)候,你會(huì)發(fā)現(xiàn)str.append()方法。本質(zhì)上是先new 一個(gè)Stringbuilder對(duì)象,然后使用這個(gè)對(duì)象進(jìn)行append,最后Builder對(duì)象toStirng回到String類(lèi)型。也就是地址發(fā)生了變化。

結(jié)果:
⑤  比較的是內(nèi)容,所以為true
⑥  也許看到這個(gè)會(huì)感到迷茫,一開(kāi)始我也迷茫。后來(lái)一想,這種情況str6則是在常量池中進(jìn)行的拼接(若存在,則直接指向;若不存在,拼接創(chuàng)建)

“java中JVM內(nèi)存劃分、棧區(qū)、堆區(qū)、方法區(qū)的區(qū)別”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI