首页 > 编程语言 >C++指针和地址偏移在HotSpot VM中的应用

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

时间:2023-09-23 10:13:01浏览次数:38  
标签:return 16 VM C++ access flags HotSpot native size

 在前面我们介绍过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,源代码如下:

// 源代码位置:openjdk/hotspot/src/share/vm/oops/method.cpp

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性能剖析与故障诊断等话题,欢迎加入。

标签:return,16,VM,C++,access,flags,HotSpot,native,size
From: https://www.cnblogs.com/mazhimazhi/p/17722818.html

相关文章

  • c++ 数据类型及范围
    short:\(-2^{15}\sim2^{15}-1\)unsignedshort:\(0\sim2^{16}-1\)int:\(-2^{31}\sim2^{31}-1\)unsignedint:\(0\sim2^{32}-1\)longlong:\(-2^{63}\sim2^{63}-1\)unsignedlonglong:\(0\sim2^{64}-1\)参考资料:https://www.cnblogs.com......
  • LVM中对基于xfs的文件系统进行扩容
    Xfs是CentOS7的默认文件系统类型,而不同文件系统类型对应的创建、检查、调整命令不同。在xfs文件系统中,只能增大分区而不能减小。[root@localhost~]#ls/lib//modules/3.10.0-229.20.1.el7.x86_64/kernel/fs#查看内核所支持的所有文件系统类型binfmt_misc.kocephdlm......
  • 如何在win10系统中安装vmware16虚拟机安装教程
    ###回答1:以下是VMware16虚拟机安装教程Win10的步骤:1.首先,下载并安装VMware16软件。2.打开VMware16软件,点击“新建虚拟机”。3.选择“典型(推荐)”模式,点击“下一步”。4.选择操作系统类型和版本,这儿选择“MicrosoftWindows”和“Windows10x64”,点击“下一步”。5.输入虚拟机名称和......
  • C++笔记(细碎小知识点)1
    1.内联:写在类内或外部声明inline(编译器判断是否内联,不是满足上述条件就一定内联),优点更快2.protected:派生类可以直接调用基类的protected成员3.class类内默认private,struct内默认public4.构造函数最优写法,用初始化(只有构造函数有)效率比在函数中写更高(因编译器先进行初始化再执行......
  • Fallible point in C/C++
    Operator[]Theperformanceof[]inCandC++isdifferent.e.g.,whenyouexcuteA[index]IfAisaobject,itwillcalltheoperator[]IfAisapointer,itisequivalenttoA+indexSo,operatoroverloadingisinvalidtopointer.......
  • C++ 的cout.tellp()和cout.seekp()语法介绍
    无论是使用cout输出普通数据,用cout.put()输出指定字符,还是用cout.write()输出指定字符串,数据都会先放到输出流缓冲区,待缓冲区刷新,数据才会输出到指定位置(屏幕或者文件中)。值得一提的是,当数据暂存于输出流缓冲区中时,我们仍可以对其进行修改。ostream类中提供有tellp()和se......
  • C/C++ const关键字 解读
    Thecollocationbetweenconstandoriginalpointerisconfusedtomanypeople.Therearetwousagesofit.Thefirstoneisavariablepointerthatpointsaconstantdata.i.e.constint*p#include<iostream>intmain(){ inta=1,b=2; const......
  • C++ STL 容器简单讲解
    STL简单讲解网上有很多很好的资料可以参考而直接看标准是最准确清晰的vectorstackqueue/priority_queuedequearraymap/multimapset/multisetunordered_mapunordered_set关于指针和迭代器!!!pbds……本文默认认为读者会基本的STL应用。一切STL......
  • C++ | 关键字 explicit
    假如有一个类如下:classpoint{public:intx,y;Point(int_x=0,int_y=0){x=_x,y=_y;}};如果以下面两种方式初始化该类的对象:voiddisplayPoint(constpoint&p){printf("(%d,%d)\n",p.x,p.y);}voidmain(){displayPoint(......
  • JVM堆内存(heap)详解
    JAVA堆内存管理是影响性能主要因素之一。堆内存溢出是JAVA项目非常常见的故障,在解决该问题之前,必须先了解下JAVA堆内存是怎么工作的。先看下JAVA堆内存是如何划分的,如图:Java堆内存又溢出了!教你一招必杀技   JVM内存划分为堆内存和非堆内存,堆内存分为年轻代(YoungGeneration)、......