1 准备

1.1 FBI WARNING

文章异常啰嗦且绕弯。

1.2 版本

使用 openjdk 24 为跟踪的源码。

fork 仓库:https://github.com/openjdk/jdk/

2 源码追踪

2.1 oopDesc

在 JVM 中,Java 对象的最高层级抽象是 oopDesc。

代码路径在 hotspot/share/oops/oop.hpp 中。

class oopDesc {
      friend class VMStructs;
      friend class JVMCIVMStructs;
     private:

    // 对象头
      volatile markWord _mark;

    // class 信息
    union _metadata {
        // 指向一个 klass 对象,就是一个 Java 中的 Class 对象
        Klass*      _klass;
        // 压缩指针
        narrowKlass _compressed_klass;
    } _metadata;


    // 其它方法先省略
}

friend class 是 cpp 中的语法,VMStructs 和 JVMCIVMStructs 可以访问 oopDesc 中的私有变量,此处暂不展开。

当 Java 代码需要使用到 Class 相关信息时候,JVM 会调用 klass() 方法来进行一次筛选,此方法在 hotspot/share/oops/oop.inline.hpp 中:

Klass* oopDesc::klass() const {
  switch (ObjLayout::klass_mode()) {

    // ObjLayout 如果显示开启了对象头压缩,直接获取 markword 里的 klass 对象
    case ObjLayout::Compact:
      return mark().klass();

    // ObjLayout 如果显示开启了指针压缩,通过 _compressed_klass 去解压内存地址,并获取 klass 对象
    case ObjLayout::Compressed:
      return CompressedKlassPointers::decode_not_null(_metadata._compressed_klass);

    // 都没开,则直接获取 _klass
    default:
      return _metadata._klass;
  }
}

2.2 ObjLayout

ObjLayout 是一个枚举,代码在 hotspot/share/oops/objLayout.hpp 中:

class ObjLayout {
public:
  enum Mode {
    // +UseCompactObjectHeaders (implies +UseCompressedClassPointers)
    Compact,
    // +UseCompressedClassPointers (-UseCompactObjectHeaders)
    Compressed,
    // -UseCompressedClassPointers (-UseCompactObjectHeaders)
    Uncompressed,
    // Not yet initialized
    Undefined
  };
    // 其它省略
}

ObjLayout::klass_mode() 方法在 hotspot/share/oops/objLayout.inline.hpp 中:

inline ObjLayout::Mode ObjLayout::klass_mode() {
#ifdef ASSERT
  assert(_klass_mode != Undefined, "KlassMode not yet initialized");
  if (UseCompactObjectHeaders) {
    assert(_klass_mode == Compact, "Klass mode does not match flags");
  } else if (UseCompressedClassPointers) {
    assert(_klass_mode == Compressed, "Klass mode does not match flags");
  } else {
    assert(_klass_mode == Uncompressed, "Klass mode does not match flags");
  }
#endif
#ifdef _LP64
  return _klass_mode;
#else
  return Uncompressed;
#endif
}

ifdef _LP64 用于判断当前操作系统是否是 64 位操作系统,JVM 的压缩功能主要针对的是 64 位操作系统,对于 32 位操作系统基本是没有优化空间的。

通过注释可以知道,在设置了压缩对象头的时候,默认也等同于开启了压缩类指针,此时 ObjLayout 是 Compact;没有开启压缩对象头,但是设置了压缩类指针的时候,ObjLayout 是 Compressed;都没有开的时候,是 Uncompressed。

2.3 metadata

回到 oopDesc 中的 _metadata:

union _metadata {
    Klass*      _klass;
    narrowKlass _compressed_klass;
} _metadata;

根据上面解读的 oopDesc.klass() 方法,如果 JVM 启动的时候没开启类指针压缩,则使用 _klass 对象,如果开启了类指针压缩,则使用 _comporessed_klass 对象。

_compressed_klass 是一个 narrorKlass 类型的对象,在 hotspot/share/oops/compressedKlass.hpp 中,可以看到:

// If compressed klass pointers then use narrowKlass.
typedef juint  narrowKlass;

这个申明说明了,narrowKlass 类型本质上等同于 juint 类型,而在 hotspot/share/utilities/globalDefinitions.hpp 中,可以看到:

typedef uint32_t juint;

所以可以知道,juint 类型本质上就是 uint32_t 类型,在 64 位操作系统上是 4 字节。

narrowKlass 等同于 uint32_t,所以 _compressed_klass 在 64 位系统中是一个 4 字节的数字;而 Klass* 是一个引用,占内存 8 字节。

所以本质上 JVM 通过使用一个 4 字节整数类型去代替了一个引用类型来节约了 4 字节的内存。

2.4 markWord

markWord 的代码路径在 hotspot/share/oops/markWord.hpp 中。

class markWord {
     private:
          uintptr_t _value;

    // 其它方法先省略
}

可以看到 markWord 的本质是一个 uintptr_t 类型的值,在 64 位的操作系统中是 8 字节。

根据上面解读的 oopDesc.klass() 方法,如果 JVM 启动了对象头压缩,则会默认调用 mark().klass() 方法。

对 _mark 的基础操作方法在 hotspot/share/oops/oop.inline.hpp 中:

// 获取 markWord 对象,这个过程是原子化的
markWord oopDesc::mark() const {
  return Atomic::load(&_mark);
}

// 获取一个初始化的 markWord 对象
markWord oopDesc::prototype_mark() const {
  if (UseCompactObjectHeaders) {
    // 当开启对象头压缩的情况下,获取 klass 中的 _prototype_header 对象,作为统一初始化对象头
    return klass()->prototype_header();
  } else {
    // 这里会创建一个新的 markWord 对象
    return markWord::prototype();
  }
}

// 初始化一个 markWord
void oopDesc::init_mark() {
    // 获取一个初始化的 markWord,然后 set 进当前对象的 _mark 对象里
  set_mark(prototype_mark());
}

// 原子化的将 _mark 所指向的对象替换成一个新的 markWord
void oopDesc::set_mark(markWord m) {
  Atomic::store(&_mark, m);
}

markWord::prototype() 在 hotspot/share/oops/markWord.hpp 中:

// 这里参数没列全,涉及到的静态对象有点多
static const uintptr_t no_hash_in_place         = (uintptr_t)no_hash << hash_shift;
static const uintptr_t no_lock_in_place         = unlocked_value;
static const uintptr_t unlocked_value           = 1;
static const uintptr_t no_hash                  = 0 ;
static const int hash_shift                     = age_shift + age_bits + unused_gap_bits;


static markWord prototype() {
    return markWord( no_hash_in_place | no_lock_in_place );
}

这里 JVM 用了一个数学计算来获取了一个初始化的 markWord,具体不展开了。

markWord 操作 klass 的方法在 hotspot/share/oops/markWord.inline.hpp 中:

Klass* markWord::klass() const {
#ifdef _LP64
  assert(UseCompactObjectHeaders, "only used with compact object headers");
  return CompressedKlassPointers::decode_not_null(narrow_klass());
#else
  ShouldNotReachHere();
  return nullptr;
#endif
}

narrowKlass markWord::narrow_klass() const {
#ifdef _LP64
  assert(UseCompactObjectHeaders, "only used with compact object headers");
  return narrowKlass(value() >> klass_shift);
#else
  ShouldNotReachHere();
  return 0;
#endif
}

前面说过,narrowKlass 本质上是一个 4 字节的数字,这里 klass() 方法本质上也就是在一个 4 字节数字里寻址。

参考

https://www.cnblogs.com/RDaneelOlivaw/p/13970242.html
https://blog.csdn.net/dshf\_1/article/details/103422963


三流
57 声望16 粉丝

三流程序员一枚,立志做保姆级教程。