天天看點

jvm:記憶體結構(堆、方法區、程式計數器、本地方法棧、虛拟機棧)

1、jvm記憶體結構

靜态編譯:把java源檔案編譯成位元組碼檔案class,這個時候class檔案以靜态方式存在。

類加載器:把java位元組碼檔案加載到記憶體中

方法區:将位元組碼放到方法區作為中繼資料(簡單名字+描述符)。

堆:對象(類的執行個體)

方法區和堆:運作時資料區在所有線程間共享

虛拟機棧、本地方法棧、程式計數器:運作時資料區線程私有

2、堆

(1)對于大多數應用來說,java堆是java虛拟機所管理的記憶體中的最大的一塊

(2)java堆是被所有線程共享的一塊記憶體區域,在虛拟機啟動時建立,需要考慮線程安全的問題

(3)此記憶體區域的唯一目的就是存放對象執行個體,幾乎所有的對象執行個體對在這裡配置設定記憶體

(4)如果在堆中沒有記憶體完成執行個體配置設定,并且堆也無法再擴充時,OutOfMemoryError異常

如果再配置設定數組的記憶體之前将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:熱點代碼編譯後存儲到方法區

上面的代碼編譯後存放起來,避免反複編譯

(3)編譯的過程

jvm:記憶體結構(堆、方法區、程式計數器、本地方法棧、虛拟機棧)

(4)運作時常量池

常量池:是一個常量表

jvm:記憶體結構(堆、方法區、程式計數器、本地方法棧、虛拟機棧)

 運作時常量池:

常量池是*.class檔案中的,當該類被加載,它的常量池資訊就會放入到運作時常量池,并将裡面的符号位址變為真實位址

4、程式計數器(PC Register)

(1)一塊較小的記憶體空間,它的作用是目前線程所執行的位元組碼行号訓示器(記錄下一條jvm指令的執行位址)

(2)一個處理器隻會執行一條線程中的指令。是以,為了線程切換後能恢複到正确的執行位置,每條線程都需要有一個獨立的程式計數器(線程私有)

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

5、本地方法棧

為本地方法(不是由java代碼編寫的方法)的運作提供的記憶體空間

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

  虛拟機規範中對本地方法棧中的方法使用的語言、使用方式與資料結構并沒有強制規定,是以具體的虛拟機可以自由實作它。

(1)棧是有深度的:

jvm:記憶體結構(堆、方法區、程式計數器、本地方法棧、虛拟機棧)

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

jvm:記憶體結構(堆、方法區、程式計數器、本地方法棧、虛拟機棧)

 棧的預設大小為5248,預設1M。

函數的調用過程:

jvm:記憶體結構(堆、方法區、程式計數器、本地方法棧、虛拟機棧)

6、虛拟機棧(每一個線程運作的時候所需要的記憶體)

每一個方法在執行的時候都會建立一個棧幀(一個棧幀對應一個方法的調用,棧幀即每一個方法需要的記憶體)用于存儲配置設定基本類型和自定義對象的引用,用于存放,局部變量表、操作數棧、動态連結\方法的傳回位址

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

垃圾回收不涉及棧記憶體

可以通過指令來指定棧的大小,但是并不是棧的記憶體越大越好,棧的記憶體變大可能引起線程數量的減少程式反而會變慢

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

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

jvm:記憶體結構(堆、方法區、程式計數器、本地方法棧、虛拟機棧)

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

(2)異常

StackOverflowError:

線程請求的深度大于虛拟機棧的深度(棧記憶體溢出),無限制的方法的遞歸調用容易出現

棧幀過大,直接将棧記憶體存滿,發生的情況極其少

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

OutOfMemoryError:擴充時無法申請到足夠的記憶體

7、堆、棧、方法區

(1)字元串相關:

jvm:記憶體結構(堆、方法區、程式計數器、本地方法棧、虛拟機棧)

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

後面的三個字元隻建立了一個對象,因為存在字元串的折疊

當常量池中已經有了“hello”字元串後在常量池中就不必再建立了,隻需在棧記憶體中建立一個對象即可;但是,如果在常量池中沒有“hello”字元串的話,就需要建立兩個字元串對象了。

(2)JVM執行流程

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的常量值、基本資料類型等

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

jvm:記憶體結構(堆、方法區、程式計數器、本地方法棧、虛拟機棧)

 程式中的數字并未放入到常量池中,這是因為隻有數字超過一定的值以後才會放入到常量值中。常量池在堆中

 (2)運作時常量池(類加載到記憶體中後産生)

将符号引用轉換為實際的位址

 将class加載到記憶體之後經過驗證、連結等之後,将符号替換為真正的位址。jdk1.8放在元空間裡面,和堆相獨立

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

存儲字元串,在堆中

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

每個人都會有一段異常艱難的時光 。 生活的壓力 , 工作的失意 , 學業的壓力。 愛的惶惶不可終日。 挺過來的 ,人生就會豁然開朗。 挺不過來的 ,時間也會教你 ,怎麼與它們握手言和 ,是以不必害怕的。 ——楊绛