JVM內存進階

寫在前面:博主是一位普普通通的19屆二本大學生,平時最大的愛好就是聽聽歌,逛逛B站。博主很喜歡的一句話花開堪折直須折,莫待無花空折枝:博主的理解是頭一次爲人,就應該做自己想做的事,做自己不後悔的事,做自己以後不會留有遺憾的事,做自己覺得有意義的事,不浪費這大好的青春年華。博主寫博客目的是記錄所學到的知識並方便自己複習,在記錄知識的同時獲得部分瀏覽量,得到更多人的認可,滿足小小的成就感,同時在寫博客的途中結交更多志同道合的朋友,讓自己在技術的路上並不孤單。

目錄:
1.堆內內存與堆外內存
       
堆內內存
       
堆外內存
       
堆外內存的優缺點
2.直接內存
3.對象創建內存的內存分析
       
對象創建的初始化工作
       
對象分配內存方式的兩種方式
       
對象分配內存的線程問題
       
對象實例化
4.對象的內存佈局
       
對象的內存佈局的簡介
       
對象頭
       
實例數據
       
對齊填充
5.對象的訪問定位

1.堆內內存與堆外內存

1.1堆內內存

Java 堆內內存是用來存放對象實例及數組,也就是說我們代碼中通過 new 關鍵字 new 出來的對象都存放在這裏。

在使用堆內內存(on-heap memory)的時候,完全遵守JVM虛擬機的內存管理機制,採用垃圾回收器(GC)統一進行內存管理,GC會在某些特定的時間點進行一次徹底回收,也就是Full GC,GC會對所有分配的堆內內存進行掃描,在這個過程中會對JAVA應用程序的性能造成一定影響,還可能會產生Stop The World。

1.2堆外內存

堆外內存和堆內內存是相對的兩個概念,和堆內內存相對應,堆外內存就是把內存對象分配在Java虛擬機的堆以外的內存,這些內存直接受操作系統管理(而不是虛擬機),這樣做的結果就是能夠在一定程度上減少垃圾回收對應用程序造成的影響。我們經常用java.nio.DirectByteBuffer對象進行堆外內存的管理和使用,它會在對象創建的時候就分配堆外內存。

1.3堆外內存的優缺點

優點:

  • 減少了垃圾回收提高效率(因爲垃圾回收會暫停其他的工作)
  • 堆內在flush到遠程時,會先複製到直接內存,然後在發送;而堆外內存相當於省略掉了這個工作

缺點:

  • 堆外內存的缺點就是內存難以控制,使用了堆外內存就間接失去了JVM管理內存的可行性,改由自己來管理,當發生內存溢出時排查起來非常困難。

在這裏插入圖片描述

2.直接內存

直接內存是一塊堆外內存。直接內存並不是虛擬機運行時數據區的一部分,但是被經常的使用,而且也會導致OutOfMemoryError異常

JDK1.4的時候加入NIO類,引入了一種基於通道與緩衝區的I/O方法。它可以使用Native函數庫直接分配堆外內存,然後通過Java堆裏邊的DirectByteBuffer對象來作爲這一塊內存的引用進行操作,能夠在一些場景顯著提高性能,因此避免了Java堆和Native堆來回複製數據。

Native函數庫:

Native關鍵字說明其修飾的方法是一個原生態方法,方法對應的實現不是在當前文件,而是在用其他語言(如C和C++)實現的文件中。Java語言本身不能對操作系統底層進行訪問和操作,但是可以通過JNI接口調用其他語言來實現對底層的訪問。

在這裏插入圖片描述

3.對象創建的內存的內存分析

3.1對象創建的初始化工作

當JVM遇到一條字節碼new指令時,首先會去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,並檢查這個符號引用代表的類是否被加載過,沒有的話執行類加載過程

3.2對象分配內存方式的兩種方式

方式1:指針碰撞

當Java堆是絕對規整的,即所有使用的內存放在一邊,沒有使用的內存放在另一邊。中間放一個指針作爲指針的分界點指示器,那所分配內存是把那個指針向空閒方向挪動一段與對象大小相等的距離。這就是指針碰撞

方式2:空閒列表

當Java堆不是規整的,已被使用和沒被使用的內存交織在一塊,虛擬機會維護一個列表,記錄那些內存是可用的,那些是不可用的,在列表裏找到一塊足夠大的空間給對象,並更新列表裏邊的數據

總的來說怎麼分配是根據 Java堆是否規整 決定的,是否規整有是根據虛擬機所採用的垃圾垃圾收集器 是否具有空間壓縮整理的能力 來決定

3.3對象分配內存的線程問題

當我們使用指針碰撞的方式來給對象A分配內存,指針還沒來得及進行修改對象B又用了原來的指針來分配內存,會引發線程問題

解決方法1:

一種是對分配內存空間的動作進行同步處理——實際上虛擬機是採用CAS配上失敗 重試的方式保證更新操作的原子性

解決方法2:

另外一種是把內存分配的動作按照線程劃分在不同的空間之中進 行,即每個線程在Java堆中預先分配一小塊內存,稱爲本地線程分配緩衝(TLAB)。哪個線程要分配內存,就在哪個線程的本地緩衝區中分配,只有本地緩衝區用完 了,分配新的緩存區時才需要同步鎖定。虛擬機是否使用TLAB,可以通過-XX:+/-UseTLAB參數來 設定

內存分配完成之後,虛擬機必須將分配到的內存空間(但不包括對象頭)都初始化爲零值,如果 使用了TLAB的話,這一項工作也可以提前至TLAB分配時順便進行。這步操作保證了對象的實例字段 在Java代碼中可以不賦初始值就直接使用,使程序能訪問到這些字段的數據類型所對應的零值

3.4對象實例化

我們對象的內存雖然分配好了但是所有的字段都 爲默認的零值,對象需要的其他資源和狀態信息也還沒有按照預定的意圖構造好

,Java編譯器會在遇到new關鍵字的地方同時生成 這兩條字節碼指令.new指令之後會接着執行<init> ()方法,按照程序員的意願對對象進行初始化,這樣一個真正可用的對象纔算完全被構造出來。

4.對象的內存佈局

4.1對象的內存佈局的簡介

對象在堆內存中的存儲佈局可以劃分爲三個部分:

  • 對象頭(Header)
  • 實例 數據(Instance Data)
  • 對齊填充(Padding)

4.2對象頭

對象頭部分包括兩類信息。第一類是:用於存儲對象自身的運行時數據,如哈 希碼(HashCode)、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等,這部 分數據的長度在32位和64位的虛擬機(未開啓壓縮指針)中分別爲32個比特和64個比特。對象頭的另外一部分是類型指針,即對象指向它的類型元數據的指針,Java虛擬機通過這個指針 來確定該對象是哪個類的實例,並不是所有的虛擬機實現都必須在對象數據上保留類型指針,換句話 說,查找對象的元數據信息並不一定要經過對象本身

4.3實例數據

來實例數據部分是對象真正存儲的有效信息,即我們在程序代碼裏面所定義的各種類型的字 段內容,無論是從父類繼承下來的,還是在子類中定義的字段都必須記錄起來

4.4對齊填充

這並不是必然存在的,也沒有特別的含義,它僅僅起着佔位符的作 用。由於HotSpot虛擬機的自動內存管理系統要求對象起始地址必須是8字節的整數倍,換句話說就是 任何對象的大小都必須是8字節的整數倍。對象頭部分已經被精心設計成正好是8字節的倍數(1倍或者 2倍),因此,如果對象實例數據部分沒有對齊的話,就需要通過對齊填充來補全。

在這裏插入圖片描述

5.對象的訪問定位

創建對象自然是爲了後續使用該對象,我們的Java程序會通過棧上的reference(引用)數據來操作堆上的具 體對象 但是並沒有定義 這個引用應該通過什麼方式去定位、訪問到堆中對象的具體位置

所以對象訪問方式也是由虛擬機實 現而定的,主流的訪問方式主要有使用句柄和直接指針兩種:

  • 1.使用句柄法:如果使用句柄訪問的話,Java堆中將可能會劃分出一塊內存來作爲句柄池,reference中存儲的就 是對象的句柄地址,而句柄中包含了對象實例數據與類型數據各自具體的地址信息

在這裏插入圖片描述

  • 2.指針法:如果使用直接指針訪問的話,Java堆中對象的內存佈局就必須考慮如何放置訪問類型數據的相關 信息,reference中存儲的直接就是對象地址,如果只是訪問對象本身的話,就不需要多一次間接訪問 的開銷

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章