Java虛擬機14:Java對象大小、對象記憶體佈局及鎖狀態變化

来源:http://www.cnblogs.com/xrq730/archive/2017/06/14/6928133.html
-Advertisement-
Play Games

一個對象占多少位元組? 關於對象的大小,對於C/C++來說,都是有sizeof函數可以直接獲取的,但是Java似乎沒有這樣的方法。不過還好,在JDK1.5之後引入了Instrumentation類,這個類提供了計算對象記憶體占用量的方法。至於具體Instrumentation類怎麼用就不說了,可以參看這 ...


一個對象占多少位元組?

關於對象的大小,對於C/C++來說,都是有sizeof函數可以直接獲取的,但是Java似乎沒有這樣的方法。不過還好,在JDK1.5之後引入了Instrumentation類,這個類提供了計算對象記憶體占用量的方法。至於具體Instrumentation類怎麼用就不說了,可以參看這篇文章如何精確地測量java對象的大小

不過有一點不同的是,這篇文章使用命令行傳入JVM參數來指定代理,這裡我通過Eclipse設置JVM參數:

後面的是我打的agent.jar的具體路徑。剩下的就不說了,看一下測試代碼:

 1 public class JVMSizeofTest {
 2 
 3     @Test
 4     public void testSize() {
 5         System.out.println("Object對象的大小:" + JVMSizeof.sizeOf(new Object()) + "位元組");
 6         System.out.println("字元a的大小:" + JVMSizeof.sizeOf('a') + "位元組");
 7         System.out.println("整型1的大小:" + JVMSizeof.sizeOf(new Integer(1)) + "位元組");
 8         System.out.println("字元串aaaaa的大小:" + JVMSizeof.sizeOf(new String("aaaaa")) + "位元組");
 9         System.out.println("char型數組(長度為1)的大小:" + JVMSizeof.sizeOf(new char[1]) + "位元組");
10     }
11     
12 }

運行結果為:

Object對象的大小:16位元組
字元a的大小:16位元組
整型1的大小:16位元組
字元串aaaaa的大小:24位元組
char型數組(長度為1)的大小:24位元組

接著,代碼不變,加入一條虛擬機參數"-XX:-UseCompressedOops",再運行一遍測試類,運行結果為:

Object對象的大小:16位元組
字元a的大小:24位元組
整型1的大小:24位元組
字元串aaaaa的大小:32位元組
char型數組(長度為1)的大小:32位元組

後文來詳細解釋一下原因。

 

Java對象大小計算方法

JVM對於普通對象和數組對象的大小計算方式有所不同,我畫了一張圖說明:

解釋一下其中每個部分:

  1. Mark Word:存儲對象運行時記錄信息,占用記憶體大小與機器位數一樣,即32位機占4位元組,64位機占8位元組
  2. 元數據指針:指向描述類型的Klass對象(Java類的C++對等體)的指針,Klass對象包含了實例對象所屬類型的元數據,因此該欄位被稱為元數據指針,JVM在運行時將頻繁使用這個指針定位到位於方法區內的類型信息。這個數據的大小稍後說
  3. 數組長度:數組對象特有,一個指向int型的引用類型,用於描述數組長度,這個數據的大小和元數據指針大小相同,同樣稍後說
  4. 實例數據:實例數據就是8大基本數據類型byte、short、int、long、float、double、char、boolean(對象類型也是由這8大基本數據類型複合而成),每種數據類型占多少位元組就不一一例舉了
  5. 填充:不定,HotSpot的對齊方式為8位元組對齊,即一個對象必須為8位元組的整數倍,因此如果最後前面的數據大小為17則填充7,前面的數據大小為18則填充6,以此類推

最後再說說元數據指針的大小。元數據指針是一個引用類型,因此正常來說64位機元數據指針應當為8位元組,32位機元數據指針應當為4位元組,但是HotSpot中有一項優化是對元數據類型指針進行壓縮存儲,使用JVM參數:

  • -XX:+UseCompressedOops開啟壓縮
  • -XX:-UseCompressedOops關閉壓縮

HotSpot預設是前者,即開啟元數據指針壓縮,當開啟壓縮的時候,64位機上的元數據指針將占據4個位元組的大小。換句話說就是當開啟壓縮的時候,64位機上的引用將占據4個位元組,否則是正常的8位元組

 

Java對象記憶體大小計算

有了上面的理論基礎,我們就可以分析JVMSizeofTest類的執行結果及為什麼加入了"-XX:-UseCompressedOops"這條參數後同一個對象的大小會有差異了。

首先是Object對象的大小:

  1. 開啟指針壓縮時,8位元組Mark Word + 4位元組元數據指針 = 12位元組,由於12位元組不是8的倍數,因此填充4位元組,對象Object占據16位元組記憶體
  2. 關閉指針壓縮時,8位元組Mark Word + 8位元組元數據指針 = 16位元組,由於16位元組正好是8的倍數,因此不需要填充位元組,對象Object占據16位元組記憶體

接著是字元'a'的大小:

  1. 開啟指針壓縮時,8位元組Mark Word + 4位元組元數據指針 + 1位元組char = 13位元組,由於13位元組不是8的倍數,因此填充3位元組,字元'a'占據16位元組記憶體
  2. 關閉指針壓縮時,8位元組Mark Word + 8位元組元數據指針 + 1位元組char = 17位元組,由於17位元組不是8的倍數,因此填充7位元組,字元'a'占據24位元組記憶體

接著是整型1的大小:

  1. 開啟指針壓縮時,8位元組Mark Word + 4位元組元數據指針 + 4位元組int = 16位元組,由於16位元組正好是8的倍數,因此不需要填充位元組,整型1占據16位元組記憶體
  2. 關閉指針壓縮時,8位元組Mark Word + 8位元組元數據指針 + 4位元組int = 20位元組,由於20位元組正好是8的倍數,因此填充4位元組,整型1占據24位元組記憶體

接著是字元串"aaaaa"的大小,所有靜態欄位不需要管,只關註實例欄位,String對象中實例欄位有"char value[]"與"int hash",由此可知:

  1. 開啟指針壓縮時,8位元組Mark Word + 4位元組元數據指針 + 4位元組引用 + 4位元組int = 20位元組,由於20位元組不是8的倍數,因此填充4位元組,字元串"aaaaa"占據24位元組記憶體
  2. 關閉指針壓縮時,8位元組Mark Word + 8位元組元數據指針 + 8位元組引用 + 4位元組int = 28位元組,由於28位元組不是8的倍數,因此填充4位元組,字元串"aaaaa"占據32位元組記憶體

最後是長度為1的char型數組的大小:

  1. 開啟指針壓縮時,8位元組的Mark Word + 4位元組的元數據指針 + 4位元組的數組大小引用 + 1位元組char = 17位元組,由於17位元組不是8的倍數,因此填充7位元組,長度為1的char型數組占據24位元組記憶體
  2. 關閉指針壓縮時,8位元組的Mark Word + 8位元組的元數據指針 + 8位元組的數組大小引用 + 1位元組char = 25位元組,由於25位元組不是8的倍數,因此填充7位元組,長度為1的char型數組占據32位元組記憶體

 

Mark Word

Mark Word前面已經看到過了,它是Java對象頭中很重要的一部分。Mark Word存儲的是對象自身的運行數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態標識、線程持有的鎖、偏向線程ID、偏向時間戳等等。

不過由於對象需要存儲的運行時數據很多,其實已經超出了32位、64位Bitmap結構所能記錄的限度,但是對象頭是與對象自身定義的數據無關的額外存儲成本,考慮到虛擬機的空間效率,Mark Word被設計成一個非固定的數據結構以便在極小的空間記憶體儲儘量多的信息。例如在32位的HotSpot虛擬機中對象未被鎖定的狀態下,Mark Word的32個Bits空間中的25Bits用於存儲對象哈希碼(HashCode),4Bits用於存儲對象分代年齡,2Bits用於存儲鎖標識位,1Bit固定位0。在其他狀態(輕量級鎖定、重量級鎖定、GC標記、可偏向)下對象的存儲內容如下圖所示:

這裡要特別關註的是鎖狀態,後文將對鎖狀態及鎖狀態的變化進行研究。

 

鎖的升級

如上圖所示,鎖的狀態共有四種:無鎖態、偏向鎖、輕量級鎖和重量級鎖,其中偏向鎖和輕量級鎖是JDK1.6開始為了減少獲得鎖和釋放鎖帶來的性能消耗而引入的。

四種鎖的狀態會隨著競爭情況逐漸升級,鎖可以升級但是不能降級,意味著偏向鎖可以升級為輕量級鎖但是輕量級鎖不能降級為偏向鎖,目的是為了提高獲得鎖和釋放鎖的效率。用一張圖表示這種關係:

 

偏向鎖

HotSpot作者經過以往的研究發現大多數情況下鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,為了讓線程獲得鎖的代碼更低因此引入了偏向鎖。偏向鎖的獲取過程為:

  1. 訪問Mark Word中偏向鎖的標識是否設置為1,所標誌位是否為01----確認為可偏向狀態
  2. 如果為可偏向狀態,則測試線程id是否指向當前線程,如果是,執行(5),否則執行(3)
  3. 如果線程id併為指向當前線程,通過CAS操作競爭鎖。如果競爭成功,則將Mark Word中的線程id設置為當前線程id,然後執行(5);如果競爭失敗,執行(4)
  4. 如果CAS獲取偏向鎖失敗,則表示有競爭。當達到全局安全點(safepoint)時獲得偏向鎖的線程被掛起,偏向鎖升級為輕量級鎖(因為偏向鎖是假設沒有競爭,但是這裡出現了競爭,要對偏向鎖進行升級),然後被阻塞在安全點的線程繼續往下執行同步代碼
  5. 執行同步代碼

有獲取就有釋放,偏向鎖的釋放點在於上述的第(4)步,只有遇到其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖,線程不會主動去釋放偏向鎖。偏向鎖的釋放過程為:

  1. 需要等待全局安全點(在這個時間點上沒有位元組碼正在執行)
  2. 它會首先暫停擁有偏向鎖的線程,判斷鎖對象是否處於被鎖定狀態
  3. 偏向鎖釋放後恢復到未鎖定(標識位為01)或輕量級鎖(標識位為00)狀態

 

輕量級鎖

輕量級鎖的加鎖過程為:

  1. 在代碼進入同步塊的時候,如果同步對象鎖狀態為無鎖狀態,JVM首先將在當前線程的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用於存儲鎖對象目前的Mark Word的拷貝,官方稱之為Displaced Mark Word,此時線程堆棧與對象頭的狀態如圖所示
  2. 拷貝對象頭中的Mark Word複製到鎖記錄中
  3. 拷貝成功後,JVM將使用CAS操作嘗試將對象的Mark Word更新為指向Lock Record的指針,並將Lock Record里的owner指針指向Object Mark Word,如果更新成功,則執行步驟(4),否則執行步驟(5)
  4. 如果更新動作成功,那麼當前線程就擁有了該對象的鎖,並且對象Mark Word的鎖標識位設置為00,即表示此對象處於輕量級鎖狀態,此時線堆棧與對象頭的狀態如圖所示
  5. 如果更新動作失敗,JVM首先會檢查對象的Mark Word是否指向當前線程的棧幀,如果是就說明當前線程已經擁有了這個對象的鎖,那就可以直接進入同步塊繼續執行。否則說明多個線程競爭鎖,輕量級鎖就要膨脹為重量級鎖,鎖標識的狀態值變為10,Mark Word中存儲的就是指向重量級鎖的指針,後面等待鎖的線程也要進入阻塞狀態。而當前線程變嘗試使用自旋來獲取鎖,自旋就是為了不讓線程阻塞,而採用迴圈去獲取鎖的過程

 

偏向鎖、輕量級鎖與重量級鎖的對比

下麵用一張表格來對比一下偏向鎖、輕量級鎖與重量級鎖,網上看到的,我覺得寫得非常好,為了加深記憶我自己又手打了一遍:


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 閱讀目錄 建議65:避開基本類型數組轉換列表陷阱 建議66:asList方法產生的List的對象不可更改 建議67:不同的列表選擇不同的遍歷演算法 建議68:頻繁插入和刪除時使用LinkList 建議69:列表相等只關心元素數據 閱讀目錄 建議65:避開基本類型數組轉換列表陷阱 建議66:asList ...
  • 在引言章節里,介紹了MovieLens 1M數據集的處理示例。書中介紹該數據集來自GroupLens Research(http://www.groupLens.org/node/73),該地址會直接跳轉到https://grouplens.org/datasets/movielens/,這裡面提供 ...
  • 迴圈(Loops) For條件遞增語句 語法是這樣的:用for作為迴圈的開始,告訴Xcode你要聲明一個迴圈了,for後面跟著括弧,括弧裡面聲明變數、條件和遞增數值。例如: 括弧中的第一個部分是變數,用counter表示,計算已經完成的迴圈的數量,在平時編寫程式時,這裡的變數常常命名為counter ...
  • 一、導論 java技術體系中所提到的記憶體自動化管理歸根結底就是記憶體的分配與回收兩個問題,之前已經和大家談過java回收的相關知識,今天來和大家聊聊java對象的在記憶體中的分配。通俗的講,對象的記憶體分配就是在堆上的分配,對象主要分配在新生代的Eden上(關於對象在記憶體上的分代在垃圾回收中會補上,想瞭解 ...
  • 一.建立資料庫 create database animal; create table animal( sno int, name varchar(20), weight varcahr(20), color varchar(20), birth date ); 插入數據 insert into ...
  • Python 跟 Python3 完全就是兩種語言   0x00 import caffe FAILED      環境為 Ubuntu 16 cuda 8.0 NVIDIA 361.77 Anaconda2。昨天莫名其妙Caffe不能用了: ...
  • 先看一下下麵的結果 解釋 int整型常量比較時,== 是值比較,所以1,2返回true。1,2是值比較。 new Integer() 每次構造一個新的Integer對象,所以3返回false。3是對象比較。 Integer.parseInt每次構造一個int常量,所以4返回true。4是值比較。 I ...
  • 因為工作的原因,學習停滯了很久,也落後了很多。真的,學習堅持很重要,我們身邊太多的誘惑,任何的困難都可能成為放棄的理由。讓自己一直在行走,哪怕步子再小,只要走,就有希望。送給自己,死磕到底。 集合 集合是一個無序的,不重覆的數據組合,它的主要作用如下: 去重,把一個列表變成集合,就自動去重了 關係測 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...