JVM JVM 記憶體劃分

2022-09-16 07:12:11 字數 3523 閱讀 3303

概述

如果在大學裡學過或者在工作中使用過 c 或者 c++ 的讀者一定會發現這兩門語言的記憶體管理機制與 j**a 的不同。在使用 c 或者 c++ 程式設計時,程式設計師需要手動的去管理和維護記憶體,就是說需要手動的清除那些不需要的物件,否則就會出現記憶體洩漏與記憶體溢位的問題。

如果你使用 j**a 語言去開發,你就會發現大多數情況下你不用去關心無用物件的**與記憶體的管理,因為這一切 jvm 虛擬機器已經幫我們做好了。了解 jvm 記憶體的各個區域將有助於我們深入了解它的管理機制,避免出現記憶體相關的問題和高效的解決問題。

引出問題

在 j**a 程式設計時我們會用到許多不同型別的資料,比如臨時變數、靜態變數、物件、方法、類等等。 那麼他們的儲存方式有什麼不同嗎?或者說他們存在哪?

執行時資料區域

j**a 虛擬機器在執行 j**a 程式過程中會把它所管理的記憶體分為若干個不同的資料區域,各自有各自的用途。

這其中堆和方法區是執行緒之間共享的,而棧和程式計數器是執行緒私有的。

補充雖然上面的圖里沒有執行時常量池和直接記憶體,但是這兩部分也是我們開發時經常接觸的。所以給大家補充出來。

這裡有乙個概念希望大家能夠清除,堆中使用分代垃圾**演算法時的永久代表方法區,它並不在堆記憶體中,上面的將其放在一起是為了說明分代垃圾**演算法會作用在這幾個區域

jdk 1.8 的改變

對於方法區,它是執行緒共享的,主要用於儲存類的資訊,常量池,方法資料,方法**等。我們稱這個區域為永久代。它也是 jvm 垃圾**作用的區域。

大部分程式設計師應該都見過 j**a.lang.outofmemoryerror:permgen space 異常,這裡的permgen space其實指的就是方法區。由於方法區主要儲存類的相關資訊,所以對於動態生成類的情況比較容易出現永久代的記憶體溢位,典型的場景是在 jsp 頁面比較多的情況,容易出現永久代記憶體溢位。

在 jdk 1.8 中,hotspot 虛擬機器已經沒有 permgen space 方法區這個地方了,取而代之的是乙個叫metaspace(元空間)的東西。

元空間與方法區最大的區別是:元空間不再虛擬機器中,而是使用本地記憶體。預設情況下,元空間的大小僅受本地記憶體限制。

常量區原本在方法區中,現在方法區被移除了,所以常量池被放倒了堆中。

這樣做的好處是:

這樣更改的好處:

虛擬機器物件揭秘

物件的建立過程,最好是能記住,並且能知道每一步在做什麼。

類載入檢查:虛擬機器遇到一條 new 指令的時候,首先去檢查這個指令的引數能否在常量池中定位這個類的符號飲用,檢查這個類的符號引用所代表的類是否已被載入,解析,初始化過。如果沒有,那必須先執行響應的類載入過程。簡單來說,就是要看物件的類是否已經被載入過了

分配記憶體:在類載入檢查通過後,接下來虛擬機器將會為新生物件分配記憶體。物件所需的記憶體大小在類載入完畢後便可以確定了,為物件分配空間的任務相當於把一塊確定大小的記憶體從 j**a 堆中劃分出來。

分配方式有指標碰撞空閒列表兩種。選擇那種方式由 j**a 堆是否規整決定,而 j**a 堆是否規整由垃圾收集器是否帶有壓縮功能決定(複製演算法和標記整理演算法是規整的,標記清除演算法是不規整的)。

記憶體分配併發問題

初始化零值:記憶體分配完畢後,虛擬機器將要分配的記憶體空間都初始化為零值(不包括物件頭)。這一步保證了物件例項在 j**a 中不賦初值就可以直接使用。

設定物件頭:初始化零值完成之後,虛擬機器要對物件進行必要的設定。比如物件的雜湊碼,物件的 gc 分代年齡資訊,偏向鎖,這些資訊放在物件頭中。

執行 init 方法:上面工作完成後,從虛擬機器視角看,乙個新的物件已經產生了。然後執行 init 方法,按照程式設計師的意願將物件進行初始化。

物件構成

hotspot 虛擬機器中,物件在記憶體中的布局可以分為三塊區域:物件頭,例項資料和對齊填充

物件頭中包含兩部分資訊,第一部分用於儲存物件自身執行時資料(雜湊碼,gc 分代年齡,鎖狀態標誌),另一部分是型別指標,即指向它的類元資料的指標,虛擬機器通過這個指標來確定這個物件是哪個類的例項。

例項資料部分儲存的物件的有效資訊。

對其填充起到的是佔位的作用。

物件的訪問定位

控制代碼。直接指標。

補充

string str1 = "abcd";

string str2 = new string("abcd");

system.out.println(str1==str2);//false

這兩種方式建立的物件是有差別的,第一種方式是在常量池中,第二種方式是在堆記憶體中。

string s1 = new string("abc"); 這句話建立了幾個物件?

先有字串 「abc」 放入常量池,然後 new 了乙個字串 「abc」 放入 j**a 堆。棧中的引用指向堆中的物件。

j**a 基本型別的包裝類的大部分都實現了常量池技術,即 byte,short,integer,long,character,boolean。除了 boolean 之外的 5 種包裝類都預設建立了 【-128 127】的快取資料,超出此範圍仍然去建立新的物件。float 和 double 並沒有實現常量池技術。

integer i1 = 33;

integer i2 = 33;

system.out.println(i1 == i2);// 輸出true

integer i11 = 333;

integer i22 = 333;

system.out.println(i11 == i22);// 輸出false

double i3 = 1.2;

double i4 = 1.2;

system.out.println(i3 == i4);// 輸出false

integer i1=40;j**a 在編譯的時候會直接將**封裝成integer i1=integer.valueof(40);,從而使用常量池中的物件。

integer i1 = new integer(40);這種情況下會建立新的物件。

integer i1 = 40;

integer i2 = new integer(40);

system.out.println(i1==i2);//輸出false

JVM JVM效能調優

整個堆大小 年輕代大小 年老代大小 持久代大小 xms 最小可用記憶體 xmx 最大可用記憶體 xmn new generation 年輕代大小 xx surviorratio x 設定年輕代中eden 區與survivor 區的大小比值,2 eden x xx maxtenuringthresho...

c c 記憶體劃分

一 乙個經過編譯的c c 的程式占用的記憶體分成以下幾個部分 1 棧區 stack 由編譯器自動分配和釋放 存放函式的引數值 區域性變數的值等,甚至函式的呼叫過程都是用棧來完成。其操作方式類似於資料結構中的棧。2 堆區 heap 一般由程式設計師手動申請以及釋放,若程式設計師不釋放,程式結束時可能由...

c c 記憶體劃分

一 個經過編譯的c c 的程式占用的記憶體分成以下幾個部分 1 棧區 stack 由編譯器自動分配和釋放 存放函式的引數值 區域性變數的值等,甚至函式的呼叫過程都是用棧來完成。其操作方式類似於資料結構中的棧。2 堆區 heap 一般由程式設計師手動申請以及釋放,若程式設計師不釋放,程式結束時可能由o...