56 對象的默認佈局

前言

最近看到了這樣的一篇文章, 一個對象的引用佔多少個字節呢?4個?8個?算出來都不對 , 呵呵 這是一個 之前想要弄明白, 但是這塊的代碼 似乎是看着有點複雜, 所以 一直沒有花時間來整理一下, 呵呵 最近看到了一篇文章, 看了一下 R大 的分析 

然後 自己結合自己的 實際情況, 整理了一一下 一些東西 

 

以下代碼, 截圖 基於 jdk9 

 

 

首先是測試用例

package com.hx.test04;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * TypeSizeOf
 *
 * @author Jerry.X.He <[email protected]>
 * @version 1.0
 * @date 2020-03-07 16:04
 */
public class Test05TypeSizeOf {

  // fields
  private long id = 1;
  private Test05TypeSizeOf test2 = null;
  private List<Test05TypeSizeOf> list = new ArrayList<>();
  private Date date = new Date();
  private byte status = 2;
  private byte count = 3;

  // refer : https://hllvm-group.iteye.com/group/topic/38670
  // -ea -javaagent:/Users/jerry/Tmp/agent/HelloWorld-1.0-SNAPSHOT_agent.jar
  // vm 調試相關參數爲 -da -dsa -Xint -Xmx100M -XX:+UseSerialGC -javaagent:/Users/jerry/Tmp/agent/HelloWorld-1.0-SNAPSHOT_agent.jar com.hx.test04.Test05TypeSizeOf
  public static void main(String[] args) {

    long sizeOfTest05TypeOfSizeOf = Test01PremainAgentClazz.inst.getObjectSize(new Test05TypeSizeOf());
    System.out.println(sizeOfTest05TypeOfSizeOf);

  }

}

測試結果如下 

第一個輸出是 javaagent 裏面的輸出, 第二個輸出是 這裏 main 方法裏面的輸出 

 

那麼, 這裏輸出的結果是 40, 好了 我們現在知道了 Test05TypeSizeOf 會佔用 40個 字節, 那麼 他又是如何計算的呢, 在內存中是如何排列的呢 ?

 

 

佈局的計算方式?

classFileParser.layout_fields 相關代碼片段如下 

  int nonstatic_oop_space_count   = 0;
  int nonstatic_word_space_count  = 0;
  int nonstatic_short_space_count = 0;
  int nonstatic_byte_space_count  = 0;
  int nonstatic_oop_space_offset = 0;
  int nonstatic_word_space_offset = 0;
  int nonstatic_short_space_offset = 0;
  int nonstatic_byte_space_offset = 0;

  // Try to squeeze some of the fields into the gaps due to
  // long/double alignment.
  if (nonstatic_double_count > 0) {
    int offset = next_nonstatic_double_offset;
    next_nonstatic_double_offset = align_size_up(offset, BytesPerLong);
    if (compact_fields && offset != next_nonstatic_double_offset) {
      // Allocate available fields into the gap before double field.
      int length = next_nonstatic_double_offset - offset;
      assert(length == BytesPerInt, "");
      nonstatic_word_space_offset = offset;
      if (nonstatic_word_count > 0) {
        nonstatic_word_count      -= 1;
        nonstatic_word_space_count = 1; // Only one will fit
        length -= BytesPerInt;
        offset += BytesPerInt;
      }
      nonstatic_short_space_offset = offset;
      while (length >= BytesPerShort && nonstatic_short_count > 0) {
        nonstatic_short_count       -= 1;
        nonstatic_short_space_count += 1;
        length -= BytesPerShort;
        offset += BytesPerShort;
      }
      nonstatic_byte_space_offset = offset;
      while (length > 0 && nonstatic_byte_count > 0) {
        nonstatic_byte_count       -= 1;
        nonstatic_byte_space_count += 1;
        length -= 1;
      }
      // Allocate oop field in the gap if there are no other fields for that.
      nonstatic_oop_space_offset = offset;
      if (length >= heapOopSize && nonstatic_oop_count > 0 &&
              allocation_style != 0) { // when oop fields not first
        nonstatic_oop_count      -= 1;
        nonstatic_oop_space_count = 1; // Only one will fit
        length -= heapOopSize;
        offset += heapOopSize;
      }
    }
  }

  int next_nonstatic_word_offset = next_nonstatic_double_offset +
          (nonstatic_double_count * BytesPerLong);
  int next_nonstatic_short_offset = next_nonstatic_word_offset +
          (nonstatic_word_count * BytesPerInt);
  int next_nonstatic_byte_offset = next_nonstatic_short_offset +
          (nonstatic_short_count * BytesPerShort);
  int next_nonstatic_padded_offset = next_nonstatic_byte_offset +
          nonstatic_byte_count;

  // let oops jump before padding with this allocation style
  if( allocation_style == 1 ) {
    next_nonstatic_oop_offset = next_nonstatic_padded_offset;
    if( nonstatic_oop_count > 0 ) {
      next_nonstatic_oop_offset = align_size_up(next_nonstatic_oop_offset, heapOopSize);
    }
    next_nonstatic_padded_offset = next_nonstatic_oop_offset + (nonstatic_oop_count * heapOopSize);
  }

 classFileParser.layout_fields 這裏是佈局的處理, 默認情況下 allocation_style = 1

 

將數據分爲了兩批, 一個批次是 *_space_count[Word, Short, Byte, Oop], 是存放在 align_size_up(markOop + klass, BytesPerLong) 的空隙
另外的一部分 : longs/doubles, ints, shorts/chars, bytes, oops, padded fields, 排列
對於我們這裏的場景, 如下

對應於我們這裏的 Test05TypeSizeOf 的實際情況, 佈局大致如下 

markOop[8] + klass[4]
byte status[1]
byte count[1]
padding[2]
long [8]
test2 [4]
list [4]
date [4]
padding [4]
合計 40 bytes, 5 word

 

 

運行時的數據?

那麼我們理論上得到了數據的排列如下, 那麼我們看一下 實際的運行時的一些情況呢 ? 

HSDB attach 到目標進程, inspect Test05TypeSizeOf 的實例 

 inspect 0x0000000795969ab8
  Oop for com/hx/test04/Test05TypeSizeOf @ 0x0000000795969ab8
  _mark: 1
  _metadata._compressed_klass: InstanceKlass for com/hx/test04/Test05TypeSizeOf
  id: 1
  test2: null
  list: Oop for java/util/ArrayList @ 0x0000000795969ae0
  date: Oop for java/util/Date @ 0x00000007959831f8
  status: 2
  count: 3

 

查看一下 的內存數據呢 ? 

 mem 0x0000000795969ab8 5
  0x0000000795969ab8: 0x0000000000000001
  0x0000000795969ac0: 0x00000302f800c354
  0x0000000795969ac8: 0x0000000000000001
  0x0000000795969ad0: 0xf2b2d35c00000000
  0x0000000795969ad8: 0x00000000f2b3063f

# 數據拆解如下
 0x0000000795969ab8 : 0x0000000000000001 爲 markOop
 0x0000000795969ac0 : 0xf800c354 爲 compressedKlass
 0x0000000795969ac4 : 0x02 爲 status
 0x0000000795969ac5 : 0x03 爲 count
 0x0000000795969ac6 : 0x0000 爲 padding
 0x0000000795969ac8 : 0x0000000000000001 爲 id
 0x0000000795969ad0 : 0x0000000 爲 test2
 0x0000000795969ad4 : 0xf2b2d35c 爲 list
 0x0000000795969ad8 : 0xf2b3063f 爲 date
 0x0000000795969ae0 : 0x0000000 爲 padding

但是 發現一個問題, 爲什麼記錄的 數據的地址 和 給定的對象的 oop 的地址不一樣呢 ? 

 

 

compressedOops 相關

從上面可以看到, 實際存儲的 oop 的地址數據 和 給定的 oop 的真實地址是不一樣的, 那麼這是怎麼回事呢?, 兩個數據又有什麼關聯呢 ?

實際存儲的 list 地址爲 0xf2b2d35c, list 的真實地址爲 0x0000000795969ae0 

這是因爲一個 UseCompressedOops 特性, 那麼我們來看下 壓縮之後的地址 和 原來的地址的關係吧 

oop.decode_heap_oop_not_null 相關實現如下 

我們這裏, base 爲 0, shift 爲 3 (<< 3 等價於 * 8[字長])

 

base, shift 初始化的地方在這裏 

至於 base 爲什麼是 0, shift 是 3, 我們可以暫時不深究 

 

好了, 兩者之間的關係大概就是這樣, 那麼 apply 到這裏的實際情況呢 ?

實際存儲的 list 地址爲 0xf2b2d35c, list 的真實地址爲 0x0000000795969ae0 

0xf2b2d35c * 8 = 0x795969ae0 

那麼同理 另外一個 date 的地址記錄的是 0xf2b3063f, date 的真實地址爲 0x00000007959831f8 

0xf2b3063f * 8 = 0x7959831f8

好了, 這裏的相關東西 大概就這些了 

 

 

完 

 

 

參考

一個對象的引用佔多少個字節呢?4個?8個?算出來都不對

 

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