概述
如果在大學裡學過或者在工作中使用過 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...