JVM源码剖析之JVM层面调用Java方法

news2024/11/15 15:59:35

先看以下2个案例。

Runnable runnable  = () -> {
    System.out.println(1);
};
new Thread(runnable).start();

为什么调用Thread的start方法就能执行Runnable的代码?

public static void main(String[] args) {
    System.out.println(1);
}

作为Java开发者,一定明白main方法是作为Java程序的入口。那么,为什么是main方法?那main方法是如何被调用执行的?

此篇文章借用2个很简单的案例,带读者明白JVM层面是如何调用Java方法。

源码分析:

版本信息如下:

jdk版本:jdk8u40
为了源码的简单,使用字节码解释器:C++解释器

我们不妨借用上文的2个案例来推理一下:

  1. 方法是属于类
  2. 在Hotspot中使用 Klass/Oop(C++类)二分模型表示类和对象。
  3. 类加载步骤大致分为:加载.class文件,根据jvm规范手册解析.class文件中的常量池(ConstantPool)、方法(Method)、变量(Field)等等(详情请看JVM手册第4章)最终生成Klass类装载以上解析后的数据
  4. 根据以上3点,我们能明白方法是在类解析的过程中就生成好了,并且存放在Klass中,也就是存放在类中。
  5. Java方法经过 javac 编译后生成.class文件的表示无非就是一条条的字节码指令。

所以,我们不妨大胆猜测,JVM层面调用Java方法无非就是调用类中对应的方法,而方法的表示就是一条条字节码指令(只考虑解释器模式)

既然猜测了,那么接下来就是源码论证环节。

源码选自JVM启动流程中调用Java层面main方法的部分源码细节。/src/share/bin/java.c文件

int JNICALL
JavaMain(void * _args)
{

    …………

    // 得到启动类。
    mainClass = LoadMainClass(env, mode, what);


    // 得到启动类中main方法签名。
    mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
                                       "([Ljava/lang/String;)V");

    // 执行
    (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);

    // 尝试结束JVM进程。
    LEAVE();

}

我们并不关心如何得到启动类,如何得到main方法的签名,我们只关心如何执行main方法。所以接着看CallStaticVoidMethod的实现。

JNI_ENTRY(void, jni_CallStaticVoidMethod(JNIEnv *env, jclass cls, jmethodID methodID, ...))

  ………… 

  JavaValue jvalue(T_VOID);     // JavaValue表示Java层面的值,而这里是作为返回值类型 
  JNI_ArgumentPusherVaArg ap(methodID, args);       // 包装参数
  jni_invoke_static(env, &jvalue, NULL, JNI_STATIC, methodID, &ap, CHECK);

JNI_END


static void jni_invoke_static(JNIEnv *env, JavaValue* result, jobject receiver, JNICallType call_type, jmethodID method_id, JNI_ArgumentPusher *args, TRAPS) {
  
  …………

  // 通过方法签名得到Method(c++类表示方法)
  methodHandle method(THREAD, Method::resolve_jmethod_id(method_id));
  // 得到方法参数的个数
  int number_of_parameters = method->size_of_parameters();
  // 参数包装器
  JavaCallArguments java_args(number_of_parameters);
  // 调用方法
  JavaCalls::call(result, method, &java_args, CHECK);
}

这一步很清楚的可以看到拿到Method对象(因为Hotspot是C++实现,所以也存在对象),而Method对象表示一个Java方法。是不是感觉离真相越来越近了~

继续看到JavaCalls::call方法

void JavaCalls::call(JavaValue* result, methodHandle method, JavaCallArguments* args, TRAPS) {

  assert(THREAD->is_Java_thread(), "only JavaThreads can make JavaCalls");
  // 执行的包装,实际上就是调用call_helper方法。
  os::os_exception_wrapper(call_helper, result, &method, args, THREAD);
}

void JavaCalls::call_helper(JavaValue* result, methodHandle* m, JavaCallArguments* args, TRAPS) {
  
  …………  省略很多断言判断

  // 拿到解释器的执行入口
  // 而解释器的执行入口,是在JVM启动的流程中做了初始化,与当前内容关联不大,所以暂时省略
  // 我们清楚,字节码指令仅仅是JVM抽象的指令集。
  // 而不是CPU能够执行的ISA指令集
  // 所以这里需要包装一个解释器的执行入口,然后去执行方法对应的字节码指令
  address entry_point = method->from_interpreted_entry();

  …………  省略很多断言判断

  // do call
  { JavaCallWrapper link(method, receiver, result, CHECK);
    { HandleMark hm(thread);  // HandleMark used by HandleMarkCleaner

      // 这里很多人看不懂
      // 其实,这里是一个函数指针的回调。
      // StubRoutines::call_stub() 拿到具体的函数地址。
      // 意义在于,统一入口,再根据entry_point做分发。
      StubRoutines::call_stub()(
        (address)&link,
        result_val_address,          
        result_type,
        method(),
        entry_point,
        args->parameters(),
        args->size_of_parameters(),
        CHECK
      );

      // 拿到返回值。
      result = link.result();  
      // 返回值放入到线程变量中(线程安全,方便赋值,方便获取)
      if (oop_result_flag) {
        thread->set_vm_result((oop) result->get_jobject());
      }
    }
  } 

}

这段代码比较重要,所以对这里做一个总结:

  1. 从Method对象中获取到解释器的入口地址。解释器的入口地址是JVM启动的过程中放入的,与当前的流程关联不大,所以不在此赘述。
  2. StubRoutines::call_stub() 这个方法会拿到一个Calls to Java 的函数地址,这里实际上就是在回调函数,而函数是在JVM启动时根据底层平台已经确定好的。很多读者可能看不懂这个写法,更不懂意义在何处。其实,很简单,就是统一入口,屏蔽底层不同平台的实现。

所以看StubRoutines::call_stub()得到的具体函数实现。

static void call_stub(
    JavaCallWrapper *call_wrapper,
    intptr_t*        result,
    BasicType        result_type,
    Method*          method,
    address          entry_point,
    intptr_t*        parameters,
    int              parameter_words,
    TRAPS) {

    EntryFrame *frame =
      EntryFrame::build(parameters, parameter_words, call_wrapper, THREAD);

    if (!HAS_PENDING_EXCEPTION) {
      // 开辟此时方法的栈
      thread->push_zero_frame(frame);

      // 调用方法
      // 内部会调用entry_point,最终就是执行字节码
      Interpreter::invoke_method(method, entry_point, THREAD);

      // 出栈
      thread->pop_zero_frame();
    }
}

// 因为这里存在多次方法的回调,所以为了篇幅的简单,省略一部分过程,直接看到最终的字节码执行方法
// Interpreter::invoke_method方法,最终会回调此方法执行方法的字节码指令集
int CppInterpreter::normal_entry(Method* method, intptr_t UNUSED, TRAPS) {
  JavaThread *thread = (JavaThread *) THREAD;

  // Allocate and initialize our frame.
  InterpreterFrame *frame = InterpreterFrame::build(method, CHECK_0);
  thread->push_zero_frame(frame);

  // 循环执行方法中字节码指令集
  main_loop(0, THREAD);

  return 0;
}

因为这里存在多次方法的回调,所以为了篇幅的简单,省略一部分过程,直接看到最终的字节码执行main_loop方法,此方法会循环的执行方法中字节码指令集。

写到这,也已经通过源码论证了我们的猜想。实际上就是JVM执行方法的字节码指令集。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/714070.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于差速驱动移动基座的三维变型机器人轨迹优化

在执行任务时,服务机器人的功能结构变化可能会限制其自主导航能力,从而影响其行动力。本文的研究,旨在解决复杂三维环境中可变形机器人的轨迹规划问题,特别是应用最为广泛的基于差速驱动移动基座的移动机器人的轨迹规划。 这种全…

如何下载一个网站的全部网页文件 如何极速下载网页上的文件

许多网站上都有非常多的内容,一个个下载比较麻烦,那么我们如何下载一个网站的全部网页文件?我们可以使用下载软件抓取整个站点上检索出所有内容,然后通过过滤器选择自己需要的内容。如何极速下载网页上的文件?我们可以…

电脑-问题

如果使用了小米路由器,有望 但是平凡跳转到miwifi进行检查,或者显示证书问题 在浏览器设置里搜索dns,将 确定如何通过安全连接来连接到网站后面部分改成自定义: https://dns.alidns.com/dns-query 主要原因是: edge新…

C++图形开发(4):下落的小球

文章目录 1.小球自上而下依次出现2.下落的小球低配版3.下落的小球高配版 1.小球自上而下依次出现 首先,我们来使小球自上而下依次出现: 分析:要使小球自上而下依次出现,也就是指在一个小球出现之后让程序暂停一段时间&#xff0c…

基于单片机电子密码锁射频卡识别指纹门禁密码锁系统的设计与实现

功能介绍 通过指纹进行开锁或者是按键输入当前的密码,修改密码,对IC卡可以进行注册,删除。当有RFID卡进入到读卡器的读卡范围内时,则会自动读取卡序列号,单片机根据卡的序列号对卡进行判断。若该卡是有效卡&#xff0c…

【 SVG 】二、SVG 容器元素

一、本文概述 本文所介绍的 svg 中元素,通常不会直接作为直接展示元素,而是配合其他基础元素,以实现指定功能的图层组,本文围绕 svg 常用容器元素,进行实战应用; 二、 SVG 容器元素(常用&#x…

「软件测试」最全面试问题和回答,全文背熟不拿下offer算我输

一般要应聘关于测试的工作,面试题会不会很难?下面小编整理了软件测试面试题及答案,欢迎参考! 一、引言 1.1 文档目的 本次文档是为了收集在面试中遇到的一问题与常见的一些答案并不是唯一答案 二、职业规划 2.1 简单的自我介绍下 面试宫&#xff…

层层剥开Transformer;Windows Copilot初版非常简陋

🦉 AI新闻 🚀 微软Win11引入Windows Copilot功能,但初版非常简陋 摘要:微软在Win11 Build 23493预览版更新中引入了Windows Copilot功能,该功能在任务栏上新增了一个图标按钮。点击按钮后,屏幕右侧会跳出…

10_Activiti7

工作流(workflow)系统 具有工作流的系统,使用的专门的建模语言(BPMN)定义 通过计算机对业务流程自动化执行管理 使用传统方式实现 ​ 代码工作量大,若流程发生改变的话,编写 的代码也会发生响应的改变 工作流引擎 按照BPMN规范进行部署,将业务和节点的流程进行分离 没有…

oracle rowscn 简单记录

可以通过ROWSCN 侦测row是否有变化,但需要注意: 默认是一个block的scn 相同可以通过create table ROWDEPENDENCIES 在每行上记录无论哪种模式,ROW SCN是一个大致值,不是准确值 NOROWDEPENDENCIES | ROWDEPENDENCIES This claus…

软件测试丨常用测试策略与测试手段

测试策略是指在特定环境约束之下,描述软件开发周期中关于测试原则、方法、方式的纲要,并阐述了它们之间如何配合,以高效地减少缺陷、提升质量。 测试策略中需要描述测试类型与测试目标以及测试方法,准入准出的条件,以…

初学spring5(六)静态/动态代理模式

学习回顾:初学spring5(五)使用注解开发 一、代理模式 为什么要学习代理模式,因为AOP的底层机制就是动态代理! 代理模式: 静态代理动态代理 学习aop之前 , 我们要先了解一下代理模式! 二、静态代…

【前端】element button鼠标点击按钮更改样式

目录 一、实现效果二、代码如下&#x1f680;写在最后 一、实现效果 二、代码如下 <span><el-button type"text"><img src"../../../../../src/assets/slice/question.png" style"font-size: 14px;margin-bottom: 0.26rem">&l…

安全响应中心 — 垃圾邮件事件报告(6.28)

2023年6月 第四周 样本概况 ✅ 类型1&#xff1a;伪造正常转发邮件&#xff08;链接&#xff09; 钓鱼邮件一直是邮件防护绕不开的话题。近日安全团队收到了一批钓鱼邮件&#xff0c;发送者将正常邮件内容和钓鱼内容拼凑在一起&#xff0c;将一封钓鱼邮件伪装成经过转发的正…

TypeScript 总结

文章目录 TypeScript 总结概述运行ts文件方式一方式二 基础声明变量类型数组元组联合类型取值限制 枚举类型any & unknownvoid & undefined类型适配 面向对象函数普通函数箭头函数可选参数默认参数 对象创建对象对象的类型限制 类和接口泛型简单使用多个泛型默认泛型类…

react基础-React原理揭秘React路由基础

React原理揭秘 目标 能够说出React组件的更新机制能够对组件进行性能优化能够说出虚拟DOM和DIff算法 组件更新机制 setState() 的两个作用 修改state更新组件 过程&#xff1a;父组件重新渲染时&#xff0c;也会重新渲染子组件&#xff0c;但只会渲染当前组件子树&#xff…

BUUCTF Cipher 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 还能提示什么呢&#xff1f;公平的玩吧&#xff08;密钥自己找&#xff09; Dncnoqqfliqrpgeklwmppu 注意&#xff1a;得到的 flag 请包上 flag{} 提交, flag{小写字母} 密文&#xff1a; Dncnoqqfliqrpgeklwmppu解…

vector 数据流查询命令

原文来自&#xff1a;vector 数据流查询命令 | 老五笔记 A lightweight, ultra-fast tool for building observability pipelines&#xff0c;vector在日常运维数据采集中也具有非常重要的作用。很多命令和详细说明可以从官方文档中达到最权威的介绍&#xff1a; Vector | A l…

Nginx【Nginx场景实践(代理服务、 反向代理、负载均衡、负载均衡算法)】(八)-全面详解(学习总结---从入门到深化)

目录 Nginx场景实践_代理服务 Nginx场景实践_反向代理 Nginx场景实践_负载均衡 Nginx场景实践_负载均衡算法 Nginx场景实践_代理服务 正向代理 正向代理&#xff0c;是在用户端的。比如需要访问某些国外网站&#xff0c;我们可能需要购买vpn。 正向代理最大的特点&#…

[软件基础] ELF executable and linking formate

Chapter 7 Object File Format (Linker and Libraries Guide) https://docs.oracle.com/cd/E19683-01/817-3677/chapter6-46512/index.html