hotspot中的Java类对象如何保存虚函数

hotspot中的Java类对象如何保存虚函数
  • 定义

在Java中,几乎所有可被继承的函数被称为虚函数。

复制代码
In HotSpot, a virtual method is essentially:

Any non-static, non-private, non-final instance method whose invocation target is determined at runtime based on the actual class of the object.

还是以Dog类为例

复制代码
public class Dog implements Animal {
	...
	public String speak(){
        return "Woof!";
    }
    ...
}

这里的speak()函数就是Java的虚函数,编译后的字节码文件也使用invokevirual字节码来执行该函数

复制代码
27: invokevirtual #53                 // Method speak:()Ljava/lang/String;

在《Java虚拟机规范》上对invokevirtual的解释如下

复制代码
Invoke instance method; dispatch based on class
  • Klass中虚函数字段

在hotspot中使用Klass的虚函数使用vtable()来描述

C++ 复制代码
class Klass : public Metadata {
...
protected:
	// vtable length
  	int _vtable_len;
	// vtables
  	klassVtable vtable() const;//返回KlassVtable对象
  	...
}

在hotspot解析字节码的函数部分中会筛选出虚函数部分

复制代码
#0  klassVtable::compute_vtable_size_and_num_mirandas (
    
#1  0x00007ffff61b1927 in ClassFileParser::post_process_parsed_stream (

#2  0x00007ffff61afcf0 in ClassFileParser::ClassFileParser (

void klassVtable::compute_vtable_size_and_num_mirandas函数中根据函数的访问权限找出虚函数

C++ 复制代码
void klassVtable::compute_vtable_size_and_num_mirandas(
	...
	if (needs_new_vtable_entry(method, super, classloader, classname, class_flags, major_version)) {//判断的昂前函数是否为虚函数
      ...
      vtable_length += vtableEntry::size(); // we need a new entry
    }
    ...
 }

我们现在针对java/lang/Object进行gdb调试,我们来看看needs_new_vtable_entry函数中可以引发vtablemethod

复制代码
Thread 2 "java" hit Breakpoint 4, klassVtable::compute_vtable_size_and_num_mirandas (vtable_length_ret=0x7ffff59fdf28, num_new_mirandas=0x7ffff59fdf30, all_mirandas=0x7ffff0034af0, super=0x0, methods=0x7fffe8800460, class_flags=..., major_version=61, classloader=..., classname=0x7ffff40210f0, local_interfaces=0x7fffe8800058) at /home/jx/src/openjdk/src/hotspot/share/oops/klassVtable.cpp:87
87            vtable_length += vtableEntry::size(); // we need a new entry
(gdb) print method->
Display all 200 possibilities? (y or n)
(gdb) print method->name()->as_C_string()
$7 = 0x7ffff0034bd0 "finalize"
(gdb) display method->name()->as_C_string()
1: method->name()->as_C_string() = 0x7ffff0034be0 "finalize"
(gdb) c
Continuing.
...
1: method->name()->as_C_string() = 0x7ffff0034bf0 "equals"
(gdb) c
Continuing.
...
1: method->name()->as_C_string() = 0x7ffff0034c00 "toString"
(gdb) c
Continuing.

...
1: method->name()->as_C_string() = 0x7ffff0034c10 "hashCode"
(gdb) c
Continuing.

...
1: method->name()->as_C_string() = 0x7ffff0034c20 "clone"
(gdb) 

可以看到Object.java中的5个函数属于虚函数

java 复制代码
@IntrinsicCandidate
public native int hashCode();

public boolean equals(Object obj) {
	return (this == obj);
}

@IntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;

public String toString() {
	return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

@Deprecated(since="9")
protected void finalize() throws Throwable { }

java/lang/Object除此之外的函数声明如下

java 复制代码
@IntrinsicCandidate
public final native Class<?> getClass();

@IntrinsicCandidate
public final native void notify();

@IntrinsicCandidate
public final native void notifyAll();

public final void wait() throws InterruptedException {

public final void wait(long timeoutMillis, int nanos) throws InterruptedException {

public final native void wait(long timeoutMillis) throws InterruptedException;

所有函数都有final声明,表明这些函数不能被继承,即不属于虚函数

虚函数字段的内存布局

C++ 复制代码
class klassVtable {
  Klass*       _klass;            // my klass
  int          _tableOffset;      // offset of start of vtable data within klass
  int          _length;
  public:
      klassVtable(Klass* klass, void* base, int length) 
          : _klass(klass) {
        		_tableOffset = (address)base - (address)klass; 
              	_length = length;
      }

可以通过Klass中的vtable()函数获得内存布局

C++ 复制代码
inline klassVtable Klass::vtable() const {
  return klassVtable(const_cast<Klass*>(this), start_of_vtable(), vtable_length() / vtableEntry::size());
}

/*start_of_vtable()实现如下*/
inline vtableEntry* Klass::start_of_vtable() const {
  return (vtableEntry*) ((address)this + in_bytes(vtable_start_offset()));
}

/*vtable_start_offset()实现如下*/
inline ByteSize Klass::vtable_start_offset() {
  return in_ByteSize(InstanceKlass::header_size() * wordSize);
}

内存布局如下

虚函数位置的摆放算法如下

C++ 复制代码
void klassVtable::put_method_at(Method* m, int index) {
	...
	table()[index].set(m);
}

vtableEntry* table() const      { return (vtableEntry*)(address(_klass) + _tableOffset); }

klassVtable(Klass* klass, void* base, int length) : _klass(klass) {
    _tableOffset = (address)base - (address)klass; _length = length;
}

可以看到table()的地址是start_of_vtable(),也就是说从table()开始摆放虚函数地址

C++ 复制代码
class vtableEntry {
	...
	private:
  		Method* _method;
  		void set(Method* method)  { assert(method != NULL, "use clear"); _method = method; }
  	...
}

由于vtableEntry只有一个成员变量_method,因此可以将其直接作为插槽插入到虚函数表中,如下图

相关推荐
码智社1 小时前
Java实现RSA密钥生成、加密解密、加签验签
java
wear工程师1 小时前
ThreadLocal 在线程池里为什么会串数据:别只会答内存泄漏
java·后端
Iruoyaoxh1 小时前
栈和队列~
java·开发语言
审小匠OpenCPAi2 小时前
银行流水核查怎么自动化?单边匹配、双向勾稽与图聚类异常检测的工程对比
java·前端·人工智能·python·审计
码上上班2 小时前
docker课程
java·docker·容器
腻害兔2 小时前
【若依项目-产品经理视角】RuoYi-Vue-Pro 源码拆解:CRM 客户关系模块深度解析——从线索到回款,一套完整的 B2B 销售闭环是怎么搭的?
java·前端·javascript·vue.js·产品经理·ai编程
达子6662 小时前
第9章_HarmonyOs图解 用Java开发UI
java·ui·harmonyos
龙虾PRO4 小时前
能源与股票量化核心差异,AI全链路落地实操手册
java·面试·职场和发展
llwszx4 小时前
【Java/Go后端手撸原生Agent(第九篇):Plan-and-Execute规划模式——从“走一步看一步“到“先谋后动“】
java·python·golang·agent开发·plan模式·规划执行模式
ZJH__GO4 小时前
网络编程v2--多客户端互通
java·运维·服务器·开发语言·计算机网络