C++指针和地址偏移在HotSpot VM中的应用

在前面我们介绍过new运算符,这个操作实际上上包含了如下3个步骤:

  1. 调用operator new的标准库函数。此函数会分配一块内存空间以便函存储相应类型的实例;
  2. 调用相应类的构造函数;
  3. 返回一个指向该对象的指针。

在第一步中,其实我们可以自己写个operator new函数对标准库函数进行重载,通常会根据类信息分配出需要的内存大小,但是分配内存的逻辑现在由我们自己控制,那我们就可以多分配一些内存,然后在多分配出来的内存上存储一些额外定义的信息。例如:

复制代码
class Test {
private:
    int a = 1;
public:
    void *operator new(size_t requested_size) throw() {
        return ::malloc(requested_size);
    }

    void *operator new(size_t requested_size, size_t length) throw() {
        return ::malloc(requested_size + length);
    }

    long access_a_offset() {
        return (size_t) ((intptr_t) &(((Test *) 16)->a) - 16);
    }

    u_char *start_b_address() {
        return (u_char *) this + 4;
    }

    void set_b_value(long val) {
        *start_b_address() = val;
    }

    long get_b_value() {
        return (long) (*start_b_address());
    }
};

我们重载了new运算符,第一个重载函数会分配类实例本来需要的内存大小,而第二个重载函数多分配了length个字节的大小。举个具体使用的例子,如下:

复制代码
std::cout << sizeof(Test) << std::endl;
// 调用第一个operator new函数,分配的内存大小为8,用来存储变量
Test *t1 = new Test();
// 调用第二个operator new函数,分配的内存大小为16,用来存储变量外,还有空闲的8字节
Test *t2 = new (8) Test();

std::cout << t2->access_a_offset() << std::endl;
t2->set_b_value(10);
std::cout << t2->get_b_value() << std::endl;

最终打印的值为0 10

如上的例子在内存末尾多开辟了8字节用来存储long类型的数据,因为这个数据没有对应的属性用来直接存取,所以只能通过偏移来操作。

注意:我们通过this获取到当前实例内存的首地址时,必须要强制转换为u_char*类型,这样加4后才会移动4个字节,因为u_char占用一个字节,此时的指针指向u_char数据类型。其实还可以这样获取:

复制代码
(u_char *) (this + 1)

this指向的是Test类型,加1后指针本身占用的内存大小的末尾,将其强制转换为指向u_char类型的指针即可。

在HotSpot VM中也有这样的操作,例如Method,根据需要有两个可选择性的字段,如下:

根据Method决定是否要多开辟内存来存储native_function和signature_handler,源代码如下:

复制代码
int size = Method::size(access_flags.is_native());

return new (loader_data, size, false, MetaspaceObj::MethodType, THREAD) Method(cm, access_flags, size);

当为本地方法时,会为Method多开辟2 个指针大小的存储空间,然后使用new关键字创建对象。这里也重载了new运算符从指定的元数据区分配内存。

下面来看对这两个伪字段(不能通过类中的实例字段进行存取操作,但是又确实存在)的存取操作。

复制代码
typedef   u_char*       address;

// 源代码位置:openjdk/hotspot/src/share/vm/oops/method.hpp
address* native_function_addr() const {
	  assert(is_native(), "must be native");
	  return (address*) (this+1);
}
address* signature_handler_addr() const {
	  return native_function_addr() + 1;
}

获取两个伪字段的地址。

注意这里的this+1,因为this的类型是Method实例,所以加1并不是加一个字节而是增加一个Method对应的字节数,即获取Method对应内存区域的下一个字节的地址;第二个native_function_addr() + 1,因为native_function_addr()返回的就是一个指针类型的数据,所以这里的加1是增加指针对应的字节数,64位下是8字节。

返回的类型为u_char**,也就是返回一个指向指针的指针。当我们要存储本地函数地址时,可如下操作:

复制代码
// 读取操作
address current = *native_function;

// 存储操作
*native_function = function

其中的function的类型为address。

下面继续看偏移量的操作,HotSpot VM中经常做的操作就是计算某个变量的偏移量。例如定义的用来表示Java类的C++类Klass中有如下2个函数:

复制代码
static ByteSize access_flags_offset(){
  return in_ByteSize(offset_of(Klass, _access_flags));
}

其中的_access_flags属性就是定义在Klass中的,通过调用access_flags_offset()函数来计算这个属性在类中的偏移量。offset_of是一个宏,如下:

复制代码
#define offset_of(klass,field) (size_t)((intx)&(((klass*)16)->field) - 16)

经过宏替换和格式调整后的方法如下:

复制代码
static ByteSize access_flags_offset(){
  return in_ByteSize((size_t)(
     (intx)&(  ((Klass*)16)->_access_flags) - 16
  ));
}

通过 (intx)&(((Klass*)16)->_access_flags) - 16 方式来计算出具体的偏移量。解释一下这种写法。

假如定义个变量Klass a; 我们都知道&a表示变量a的首地址,&(a._access_flags)表示变量_access_flags的地址,那么&(a._access_flags)减去&a就得到_access_flags的偏移量。

((Klass*)16)的地址为16,所以偏移量最终等于&( ((Klass*)16)->_access_flags)减去16。

当HotSpot VM要用一个成员变量的时候,它会根据对象的首地址加上成员的偏移量得到成员变量的地址。当对象的首地址为0时,得到的成员变量地址就是它的偏移量。

本人最近准备出一个手写Hotspot VM的课程,超级硬核,从0开始写HotSpot VM,将HotSpot VM所有核心的实现全部走一遍,如感兴趣,速速入群。

群里可讨论虚拟机和Java性能剖析与故障诊断等话题,欢迎加入。

相关推荐
张张张3129 分钟前
4.2学习总结 Java:list系列集合
java·学习
KATA~12 分钟前
解决MyBatis-Plus枚举映射错误:No enum constant问题
java·数据库·mybatis
xyliiiiiL27 分钟前
一文总结常见项目排查
java·服务器·数据库
shaoing29 分钟前
MySQL 错误 报错:Table ‘performance_schema.session_variables’ Doesn’t Exist
java·开发语言·数据库
腥臭腐朽的日子熠熠生辉1 小时前
解决maven失效问题(现象:maven中只有jdk的工具包,没有springboot的包)
java·spring boot·maven
ejinxian1 小时前
Spring AI Alibaba 快速开发生成式 Java AI 应用
java·人工智能·spring
杉之1 小时前
SpringBlade 数据库字段的自动填充
java·笔记·学习·spring·tomcat
圈圈编码2 小时前
Spring Task 定时任务
java·前端·spring
俏布斯2 小时前
算法日常记录
java·算法·leetcode
27669582922 小时前
美团民宿 mtgsig 小程序 mtgsig1.2 分析
java·python·小程序·美团·mtgsig·mtgsig1.2·美团民宿