jvm:內存結構(堆、方法區、程序計數器、本地方法棧、虛擬機棧)


1、jvm內存結構

靜態編譯:把java源文件編譯成字節碼文件class,這個時候class文件以靜態方式存在。

類加載器:把java字節碼文件加載到內存中

方法區:將字節碼放到方法區作為元數據(簡單名字+描述符)。

堆:對象(類的實例)

方法區和堆:運行時數據區在所有線程間共享

虛擬機棧、本地方法棧、程序計數器:運行時數據區線程私有

 

2、堆

(1)對於大多數應用來說,java堆是java虛擬機所管理的內存中的最大的一塊

(2)java堆是被所有線程共享的一塊內存區域,在虛擬機啟動時創建,需要考慮線程安全的問題

(3)此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例對在這里分配內存

new Person();

(4)如果在堆中沒有內存完成實例分配,並且堆也無法再擴展時,OutOfMemoryError異常

 String [] str =new String[1000000];

如果再分配數組的內存之前將jvm的數值調小后便會發生此異常。

(5)是垃圾收集器管理的主要區域

堆內存溢出(OutOfMemoryError)問題:

堆既然有垃圾回收機制,為什么還會有內存溢出的問題呢?

因為垃圾回收器回收的是不被使用的對象,如果不停的產生對象而對象又都在被使用就會出現內存溢出

(6)JDK1.7開始,將StringTable從常量池移動到了堆中,String table又稱為StringPool(字符串常量池)

永久帶的內存回收效率較低,full GC才會觸發,也就是老年代的空間不足才會觸發。但是移動到堆中之后,只需要Minor GC即可觸發垃圾回收,大大減輕了字符串對內存的占用

JVM中的堆一般分為三大部分(jdk1.7):新生代、老年代、永久代(java8以后永久代被元空間代替,元空間使用本地內存,是不與堆內存相連的。因此,默認情況下,元空間的大小僅受本地內存的限制)

(7)引起堆內存溢出的情況

死循環或不停地重復創建大量對象

 

3、方法區

  是各個線程共享的內存區域,虛擬機啟動的時候創建,jdk1.8之前屬於堆的永久區的一部分,會被垃圾回收機制所回收只不過回收的條件較為苛刻,也就造成了永久區較難被回收。1.8開始,將方法區從永久區剝離了出來,取而代之的是元空間,相較於永久區它使用的是物理內存,默認大小是物理內存的大小,但是也可以進行配置

(1)存放的信息

已經被jvm加載的類的信息

常量

靜態變量

及時編譯器編譯后的代碼(JIT)

(2)JIT:熱點代碼編譯后存儲到方法區

  for(int i=0;i<100;i++){
      add();
    }

上面的代碼編譯后存放起來,避免反復編譯

(3)編譯的過程

(4)運行時常量池

常量池:是一個常量表

 

 運行時常量池:

常量池是*.class文件中的,當該類被加載,它的常量池信息就會放入到運行時常量池,並將里面的符號地址變為真實地址

 

4、程序計數器(PC Register)

(1)一塊較小的內存空間,它的作用是當前線程所執行的字節碼行號指示器(記錄下一條jvm指令的執行地址)

(2)一個處理器只會執行一條線程中的指令。因此,為了線程切換后能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器(線程私有)

(3)唯一一個在jvm中沒有規定任何OutOfMemoryError的區域(java的規范所規定)

 

5、本地方法棧

為本地方法(不是由java代碼編寫的方法)的運行提供的內存空間

 protected native Object clone() throws CloneNotSupportedException;

  該方法沒有方法實現,底層是c或c++實現的,是C或c++程序提供給java程序的接口,也存在兩種異常。

  虛擬機規范中對本地方法棧中的方法使用的語言、使用方式與數據結構並沒有強制規定,因此具體的虛擬機可以自由實現它。

(1)棧是有深度的:

private Long aLong=1l;
    public void test(int a,int b){
        aLong++;
        System.out.println(aLong);
        test(a,b);
    }

    public static void main(String[] args) {
     Test1 test1=new Test1();
     test1.test(0,0);
    }

 每調用一次占用一個棧幀:

 棧的默認大小為5248,默認1M。

函數的調用過程:

 

6、虛擬機棧(每一個線程運行的時候所需要的內存)

每一個方法在執行的時候都會創建一個棧幀(一個棧幀對應一個方法的調用,棧幀即每一個方法需要的內存)用於存儲分配基本類型和自定義對象的引用,用於存放,局部變量表、操作數棧、動態鏈接\方法的返回地址

每一個線程只有一個活動棧幀,對應着當前線程正在執行的那個方法

垃圾回收不涉及棧內存

可以通過指令來指定棧的大小,但是並不是棧的內存越大越好,棧的內存變大可能引起線程數量的減少程序反而會變慢

局部變量沒有逃離方法的作用域是線程安全的(線程私有),當作為參數傳遞的變量或是局部變量作為返回值都不是線程安全的,因為可能被別的線程訪問到

(1)一個棧中的多個棧幀

 棧幀1先入棧,然后是棧幀2、棧幀3,相當於方法1調用方法2,方法2又調用了方法3,出棧的時候棧幀3先出棧,然后是棧幀2和棧幀3

(2)異常

StackOverflowError:

線程請求的深度大於虛擬機棧的深度(棧內存溢出),無限制的方法的遞歸調用容易出現

棧幀過大,直接將棧內存存滿,發生的情況極其少

例如:定義一個學生類和一個班級類,一個學生只屬於一個班級,在學生類里面有一個班級編號屬性,一個班級有多個學生,班級類里面可以定義一個集合代表多個學生,在進行JSON轉換的時候就會出現循環引用的現象,即一個學生對應一個班級,一個班級又有多個學生......,要把學生和班級的引用改為單向的,就不會出現循環引用的現象了。

OutOfMemoryError:擴展時無法申請到足夠的內存

 

7、堆、棧、方法區

(1)字符串相關:

JDK1.7開始,將StringTable從常量池移動到了堆中

String string="q"+"w"+"3";

后面的三個字符只創建了一個對象,因為存在字符串的折疊

String string=new String("hello");

當常量池中已經有了“hello”字符串后在常量池中就不必再創建了,只需在棧內存中創建一個對象即可;但是,如果在常量池中沒有“hello”字符串的話,就需要創建兩個字符串對象了。

(2)JVM執行流程

public class Person {
    private String name;
    public void sayhello(String name){
        System.out.println("hello"+name);
    }

    public static void main(String[] args) {
        Person person=new Person();
        person.sayhello("Tom");
    }
}

JVM去方法區尋找Person類信息如果我不到,Classloader加載Person類信息進入內存方法區
在堆內存中創建Person對象,並持有方法區中Person類的類型信息的引用
把person添加到執行main0方法的主線程java調用棧中,指向堆空間中的內存對象
執行person.sayHello0時,JVM根據person定位到堆空間的Person實例
根據Person實例在方法區持有的引用,定位到方法區Person類型信息,獲得sayHello0字節碼,執行此方法執行,打印出結果。

 

8、局部變量表

(1)存放了各種基本數據類型、對象引用和returnAddress類型(指向了一條字節碼指令的地址)
(2)long和double類型的數據會占用2個局部變量空間(Slot),其余的數據類型只占用1個

 

9、常量池與運行時常量池、字符串常量池

(1)常量池存儲字面量和符號引用(是class文件中的常量池,編譯的時候產生)

字面量:字符串、被聲明為final的常量值、基本數據類型等

符號引用:類的完全限定名、字段名稱和描述符、方法名稱和描述符

    public static void main(String[] args) {
        int a=123;
        String string="abc";
        final int num=123;
    }

 程序中的數字並未放入到常量池中,這是因為只有數字超過一定的值以后才會放入到常量值中。常量池在堆中

 (2)運行時常量池(類加載到內存中后產生)

將符號引用轉換為實際的地址

 將class加載到內存之后經過驗證、鏈接等之后,將符號替換為真正的地址。jdk1.8放在元空間里面,和堆相獨立

(3)字符串常量池(編譯時)

存儲字符串,在堆中

常量池是為了避免頻繁的創建和銷毀對象而影響系統性能,其實現了對象的共享。常量池中所有相同的字符串常量被合並,只占用一個空間,節省了空間。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM