[Golang实现JVM第六篇]实现Native方法

一次疑似 JVM native 内存泄漏的排查实录 最近开发同学反馈,某定时任务服务疑似有内存泄漏,整个进程的内存占用比 Xmx 内存大不少,而且看起来是缓慢上升的,做了下面这次分析,包括下面的内容: 阅读详情

首先需要明确几个问题。

没有Native方法JVM什么也做不了

可能很多人认为native方法是Java里的禁区,使用本地方法会牺牲可移植性,而且还会有额外开销,貌似几乎没有程序员会在实际项目中写本地方法,这玩意就是个很冷门的东西。其实这种看法是错误的,哪怕一个Hello Word程序都是要严重依赖于本地方法的。在JDK中,你会发现任何涉及到I/O、线程操作的类,层层追踪源码后最终都能找到一个对应的native调用,真正把Hello World打印到控制台的正是这些native方法。而用于启动线程的Thread.start()方法,最终也是调用了一个叫native void start0()的本地方法。因为任何对硬件的操作都必须通过操作系统提供的系统调用(system call)来实现,JVM作为一个用户程序并不具备操作硬件的能力,必须通过发起系统调用才能实现网络I/O、文件I/O、创建线程等操作。

"本地"是相对于VM实现而言的

另一个误区是认为只要是本地方法那就一定要用C/C++实现,这也是不正确的。本地是相对于VM的执行环境而言的,如果VM是用C++写成(如Hotspot JVM),那么C++就是这个VM的本地语言;如果JVM是用python写成,那么python就是JVM的本地语言。假如有人在浏览器中使用Javascript实现了一个JVM,那么这个在浏览器中运行的可怜的Java代码如果想在console中打印Hello Word, 那就必须执行JS里的console.log()才能实现,于是JS就成了他的本地语言了,浏览器下JS没有的能力(如文件读写)那对应的JVM也无法实现。由于我们的Mini-JVM使用Go来实现的,那自然就要用Go来实现native方法了,而不是C++。同样,Go没有的能力,例如OS线程,那Mini-JVM也就没有,但是可以用协程来模拟线程,效果也差不多。

实现本地方法

首先我们要看一下javac是如何编译本地方法的,例如这个类:

package cn.minijvm.io;

public class Printer {
    public static native void print(int num);
}

编译后使用javap -verbose Printer查看:

  public static native void print(int);
    descriptor: (I)V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_NATIVE

可以看到跟普通方法一样都有描述符和访问标记,但是没有字节码。

然后再看一下如果调用本地方法javac又会生成些啥:

package com.fh;
import cn.minijvm.io.Printer;

public class ArrayTest {
    public static void main(String[] args) {
				int sum = 10;
        Printer.print(sum);
    }
}

字节码:

        ... ... 省略
        ... ...
        77: iload_1
        78: invokestatic  #2                  // Method cn/minijvm/io/Printer.print:(I)V
        81: return

常量池:

   #1 = Methodref          #4.#14         // java/lang/Object."<init>":()V
   #2 = Methodref          #15.#16        // cn/minijvm/io/Printer.print:(I)V
   #3 = Class              #17            // com/fh/IfTest

可以看到生成了invokestatic指令,后面跟着一个常量池下标2, 2对应常量池中的元素就是一个普通的MethodRef方法引用常量,跟正常调用静态方法是完全一致的,没啥特殊的地方。

看完这些后我们就可以想出这样一种实现思路:

  • 实现一个本地方法表,保存从【类名+方法描述符】到【go函数】的一个映射
  • 在常量池中查找到目标方法引用常量后(如上面的#2),先判断下此方法是否带有Native标记,如果没有就正常去查找字节码循环解释执行,如果有则查本地方法表,找到对应的Go函数,从栈中取出参数后直接调用对应的Go函数;如果本地方法表中没有找到对应的函数就直接报错

本地方法表可以简单的实现如下:

// 完整代码:https://github.com/wanghongfei/mini-jvm/blob/master/vm/native_method_table.go

// 本地方法表
type NativeMethodTable struct {
	MethodInfoMap map[string]*NativeMethodInfo
}

type NativeMethodInfo struct {
	// 方法名
	Name string

	// 类的全名
	FullClassName string

	// 描述符;
	// String getRealnameByIdAndNickname(int id,String name) 的描述符为 (ILjava/lang/String;)Ljava/lang/String;
	Descriptor string

	// 对应的go函数
	EntryFunc NativeFunction
}

要注意这里NativeMethodTable.MethodInfoMap不需要用线程安全的Map, 因为这个Map只会在JVM启动时初始化一次,后面就不再更改了,多线程(协程)读是安全的。

Go函数的定义如下:

// JVM的本地方法, 即go函数;
// 参数args[0]固定为MiniJVM的指针
type NativeFunction func(args ...interface{}) interface{}

这里为了能在native方法,也就是Go函数中访问到JVM中的数据,我们可以约定在调用这个函数时第一个参数一定是MiniJVM的指针,从第二个参数开始才是java native方法中声明的参数。例如Printer.printInt(int num)这个本地方法,Mini-JVM的go函数实现可以是:

func PrintInt(args ...interface{}) interface{} {
	fmt.Println(args[1])
  return true
}

也就是说,只要遇到了cn.minijvm.io.Printer.printInt(10),那我们就调用Go的PrintInt()函数,并且保证args[0]是JVM指针,args[1]是10就可以了。

这里还需要一个本地方法注册的逻辑,也就是向本地方法表中添加数据。这个逻辑只需在JVM启动过程中执行一次:

// 完整代码:https://github.com/wanghongfei/mini-jvm/blob/master/vm/mini_jvm.go

// 本地方法表
	nativeMethodTable := NewNativeMethodTable()
	vm.NativeMethodTable = nativeMethodTable
	// 注册本地方法
	nativeMethodTable.RegisterMethod("cn.minijvm.io.Printer", "print", "(I)V", PrintInt)
	nativeMethodTable.RegisterMethod("cn.minijvm.io.Printer", "printInt", "(I)V", PrintInt)
	nativeMethodTable.RegisterMethod("cn.minijvm.io.Printer", "printInt2", "(II)V", PrintInt2)
	nativeMethodTable.RegisterMethod("cn.minijvm.io.Printer", "printChar", "(C)V", PrintChar)
	nativeMethodTable.RegisterMethod("cn.minijvm.io.Printer", "printString", "(Ljava/lang/String;)V", PrintString)
	nativeMethodTable.RegisterMethod("cn.minijvm.concurrency.MiniThread", "start", "(Ljava/lang/Runnable;)V", ExecuteInThread)
	nativeMethodTable.RegisterMethod("cn.minijvm.concurrency.MiniThread", "sleepCurrentThread", "(I)V", ThreadSleep)

这样我们就可以通过查表的方式来找到native java方法对应的go函数了:

// 完整代码: https://github.com/wanghongfei/mini-jvm/blob/master/vm/interpreted_execution_engine.go

// 是native方法
	if _, ok := flagMap[accflag.Native]; ok {
		// 查本地方法表
		nativeFunc, argCount := i.miniJvm.NativeMethodTable.FindMethod(def.FullClassName, methodName, methodDescriptor)
		if nil == nativeFunc {
			// 该本地方法尚未被支持
			return fmt.Errorf("unsupported native method '%s'", method)
		}

		// 从操作数栈取出argCount个参数
		argCount += 1
		args := make([]interface{}, 0, argCount)
		for ix := 0; ix < argCount; ix++ {
			arg, _ := lastFrame.opStack.Pop()
			args = append(args, arg)
		}

		// 将jvm指针放到参数里,给native方法访问jvm的能力
		args[argCount - 1] = i.miniJvm

		// 因为出栈顺序跟实际参数顺序是相反的, 所以需要反转数组
		for ix := 0; ix < argCount / 2; ix++ {
			args[ix], args[argCount - 1 - ix] = args[argCount - 1 - ix], args[ix]
		}

		i.miniJvm.DebugPrintHistory = append(i.miniJvm.DebugPrintHistory, args[1:]...)

		// 调用go函数
		nativeFunc(args...)

		return nil
	}

用这种简单的思路虽然做不到像真正的JVM那样允许程序员编写go函数来支持自定义的native方法,但理论上已经可以实现JDK中所有native方法了,比如线程相关的操作。到这里我们仍然连一个最简单的Hello World都实现不了,因为要实现Printer.print(String word),我们还需要实现Object,支持new指令,然后支持String.class的加载和解析。当然这些在Mini-JVM(https://github.com/wanghongfei/mini-jvm)中都已经实现了,以后会介绍。

JVM内存模型-入门到精通_知道jvm内存模型有哪些部分吗? 如果还是没有内存区域,则执行垃圾回收器进行内存回收,如果垃圾回收器回收后依然没有足够的内存空间(这里的垃圾回收器指的时CMS或者G1),则执行Full GC,如果还是没有,则抛出OOM异常,实例化对象失败。划分内存的方式有两种,一种是指针碰撞,一种是空闲列表,至于采用何种方式,取决于GC的类型。创建好对象以后,进行初始化,默认值都为零,在JVM的角度看来,对象已经创建成功,但是在程序角度看来,对象还未创建成功,才刚刚开始,接下去会执行初始化方法,进行对象初始化。可以走的很快,但一群人才能走的更远。 阅读详情

相关推荐

cloud-native-programming-golang

cloud native programming golang 文档 epub格式,排版空格较大。

go语言替代java做Android_[剑走偏锋] Android使用Golang代替C/C++进行Native开发

想一个好点的标题事情是这样的: 一次Android项目开发过程中,要用到数据的加密解密,因为数据运算量比较大,所以需要用到native进行开发,但是又极不情愿去写C/C++那种既耽误时间又不好调试的语言,所以想方设法的寻找替代方案,正好最近在用Golang,寻思着,Golang不是号称速度接近C++又能快速开发的吗,所以琢磨着能不能用Golang来写Android的native部分,然后就有了一系...

weixin_42441929的博客 978

Go-PionWebRTC是WebRTCNativeAPI的纯Golang实现

Pion WebRTC是WebRTC Native API的纯Golang实现

Golang实现JAVA虚拟机-运行时数据区

Java虚拟机可以操作两类数据:基本类型(primitive type)和引用类型(reference type)。基本类型的变量存放的就是数据本身布尔类型(boolean type)数字类型 (numeric type)整数类型(integral type)浮点数类型(floating-point type)。引用类型的变量存放的是对象引用,真正的对象数据是在堆里分配的。类类型:指向类实例接口类型:用指向实现了该接口的类或数组实例数组类型: 指向数组实例null。

sdgfafg_25的博客 1153

gonvml:Go的NVIDIA Management Library(NVML)绑定

为NVML进行绑定 是基于C的API,可用于监视NVIDIA GPU设备。 它是封闭源,但可以作为一部分下载。 描述了可以作为NVML一部分使用的各种方法。 nvml.h文件包含在此存储库中,因此我们不依赖于构建环境中是否存在NVML。 bindings.go文件是调用NVML函数的cgo桥。 bindings.go的cgo序言使用dlopen动态加载NVML并使其功能可用。

行为验证码 AJ-Captcha 使用文档

AJ-Captcha验证码系统集成指南:需添加captcha-spring-boot-starter和Redis依赖,配置Redis连接。核心实现包括Redis缓存服务类CaptchaCacheRedisImpl(含设置、查询、删除验证码等方法)、SPI配置文件及Spring Boot配置类(配置验证码属性和Redis模板)。系统支持验证码生成、校验、防刷等安全功能,适用于Web应用安全验证场景。

千层冷面的博客 2108

Go 垃圾回收原理

为什么80%的码农都做不了架构师?>>> ...

weixin_34261415的博客 395

MRI脑影像分析从哲学到技术:一文搞懂VBM预处理基本原理(全网最详细解析)

   分析大脑解剖学差异最常用方法之一是基于体素的形态学方法(voxel-based morphometry, VBM)。 VBM的基本思想是测量大脑组织,尤其是灰质的局部浓度的差异。在大脑的每个体素处进行操作,然后在两个或多个实验组之间进行统计比较,从而在受调查的组之间建立特定大脑区域中脑组织浓度的统计学显着差异。 VBM分析基于(高分辨率)MRI脑部扫描,涉及大量处理步骤,主要是空间归一化,分割,平滑和统计分析,最终结果是统计图,显示了各组之间组织类型明显不同的区域[^1]。 参考 [^1] ..

意疏的学习笔记 3万+

[Golang实现JVM第三篇] 解释器雏形

在上一篇中我们已经完成了class文件的解析工作,虽然没有解析所有的属性,但是已经足够支持一些基本的算法题Java代码编译生成的class文件了。有了这一步,日后如果遇到新的特性需要支持,只需缺哪补哪,补上对应属性的解析逻辑就可以了。下一步就是实现一个基本的执行引擎,即解释器,并且支持基本的栈操作相关的指令,比如iconst_x, istore_x, bipush等。 基于栈的指令集和基于寄存器的指令集 JVM字节码是一套基于栈的指令集,也就是说操作数栈是一切计算的基本容器,大部分指令都是围绕着操作数栈展开

停止更新 608

Linux中断子系统(二)—— 通用框架处理

1、数据结构分析 (1)中断描述符结构structirq_desc: 1)打开CONFIG_SPARSE_IRQ宏(中断编号不连续),中断描述符以radix- tree来组织,用户在初始化时进行动态分配,然后再插入radix-tree中; 2)关闭CONFIG_SPARSE_IRQ宏(中断编号连续),中断描述符以数组的 形式组织,并且已经分配好; 3)不管哪种形式,最终都可以通过linuxirq号来找到对应的中断描述符; (2)stru...

weixin_38428439的博客 4870

用易经智慧看懂人工智能:64卦中的科技密码--当古老智慧遇见未来科技

在人工智能狂飙突进的今天,我们总在追问:这些代码构建的智能究竟会带人类走向何方?或许答案早已藏在《周易》的六十四卦中——这本中华文明的“源代码”,正用一种跨越时空的语言,悄悄解释着最前沿的科技秘密。

weixin_38410320的博客 2081

使用gonative轻松实现Go的跨平台编译

使用gonative轻松实现Go的跨平台编译 项目介绍 gonative 是一个简洁而实用的工具,它解决了Go语言在生产环境中跨编译时遇到的问题。由于标准库中的部分代码依赖于Cgo进行DNS解析、系统证书根访问以及os/user包的部分功能,直接使用交叉编译得到的二进制文件可能不适用。gonative通过下载不同平台的二进制发行版并将其库复制到正确位置,创建一个可以跨平台编译的Go工具链,并确保使...

gitblog_00034的博客 623

互联网人提高效率必备软件合集

很多的时候我们解决不了问题,只是因为没有找到对的工具,实际上,很多好的工具就在那里,只是你不用而已。这点上,信息壁垒是一个很大的原因,因此我希望兄伙伴们多出去走走,不要只是局限在一个圈层;

zhonglunshun的专栏 3483

jvm.go本地方法接口:Go与Java Native Interface的桥接技术全解析

在Java虚拟机(JVM)的世界里,本地方法接口(JNI)扮演着连接Java代码与底层系统的重要角色。而jvm.go作为一个用Go语言实现的玩具JVM,其本地方法接口的设计与实现为我们展示了Go语言与Java Native Interface之间独特的桥接技术。本文将深入探讨jvm.go中的本地方法接口实现,带你了解Go如何优雅地与JNI技术融合。 [![jvm.go项目logo](https:

gitblog_00462的博客 800

YOLOv13:目标检测的全面攻略与实战指南

本文是关于 YOLOv13 的全攻略文章。先回顾 YOLO 系列发展,点明 YOLOv13 在该系列中的创新地位,阐述其基于 HyperACE、FullPAD 和轻量级卷积替换等技术的独特优势。深入剖析各核心技术原理,通过与其他版本及实际场景对比展现其性能优势。介绍项目结构与使用方式,分享数据增强、锚框优化等实用技巧,并列举安防监控、自动驾驶等领域应用案例。最后总结 YOLOv13 价值,展望目标检测技术未来发展趋势,为读者提供全面的 YOLOv13 学习与实践指导。

邓邓子的博客 3432

qt实现OCC读取STEP模型并显示(附完整源码)

qt实现OCC读取STEP模型并显示(附完整源码)

希望我的博客,能帮上你解决学习中工作中所遇到的问题 1480

Go和Kotlin/Native协程对比

说明 系统说明 Kotlin版本:1.4.10 Golang版本:1.12.9 OS:Windows CPU:4核8线程 3.6GHz 内存:32G 其他说明 由于Kotlin Native的协程默认只支持单线程,因此限制Goroutines也只使用单核 测试1为了公平Kotlin不在jvm上跑,Kotlin和Go均编译为exe可执行文件 测试2会对比Kotlin在jvm多线程和不限制Goroutines的对比,这是最常用的开发场景 但由于主力服务端还是Linux,后期也许会增加在Linux系统上的对

gwt0425的博客 3652

企业级 Token 中转架构设计:高并发、多模型、费用监控一次搞定

摘要:本文针对企业级AI开发中的多模型接口混乱、高并发稳定性差及Token费用失控等痛点,提出一套2026年企业级Token中转架构解决方案。该方案采用五层分层设计(接入层、网关层、调度层、服务层、存储层),实现多模型统一接口、智能路由、高并发限流容错及精细化成本监控。核心功能包括:异构模型标准化接入、动态路由策略(场景/成本/负载/容灾)、多级限流熔断机制、全链路Token计费与安全防封体系(指纹清洗、IP池隔离)。通过分层解耦与模块化设计,企业可降低50%成本、提升99%稳定性,并避免封号风险。文章还提

远程部署调试 运行安装 项目调试 二次开发 项目技术新 持续迭代 部分源码免费分享 143

【用Golang实现JVM第一篇】知识准备&前言

学习JVM最好的方法,就是实现它,没有之一。 对JVM感兴趣的朋友可能会去主动寻找讲解JVM的书,比如《深入理解JVM》、《Java虚拟机规范》、《虚拟机的设计与实现》,但是这些书都是定性讲解,多数人看过以后对不少概念还是会很模糊,其根本原因就是我们无法只根据定义在脑袋里想象出具体用代码应该怎么实现,就会有一种空中楼阁的感觉。既然虚拟机规范都是公开的,为什么我们不尝试着去实现一个能跑就行的JVM呢? 方法论:将复杂问题化解为简单问题的集合 国外有一本著名的操作系统教材,叫作《Operating System

停止更新 948

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

go和java语言的反射,在源码上的体现

特性Go 反射Java 反射元信息来源编译器在编译时生成,静态嵌入可执行文件。JVM在运行时从 .class 文件动态加载和解析。核心数据结构runtime.rtype (底层 C/Go 结构体)。JVM 内部的 C++ Klass 对象。API 入口reflect.Type (Go 接口)。java.lang.Class (Java 对象)。实现方式纯 Go 代码,reflect 包是对 runtime 数据的安全封装。

u011091936的博客 1098

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

如何在 Python 中检查字符串是否包含子串:`in` 成员运算符与相关字符串方法实战(materials 项目示例)

本指南以 [how-to-check-if-a-python-string-contains-a-substring](https://link.gitcode.com/i/bd6b386c95f44c649a3bc34c07eed008) 教程为主线,讲解 Python 判断子串最 Pythonic 的写法——成员运算符 `in`,并延伸讲解大小写处理、`index()`/`count()` 等

gitblog_00174的博客 423

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Spring Native 即将取代传统JVM?AOT 编译技术趋势与未来展望

Spring Native AOT 提前编译部署助力Java应用高效启动与低内存运行,适用于云原生、Serverless等场景。通过GraalVM实现原生镜像构建,显著提升性能。掌握核心技术趋势,值得收藏。

PixelShoal的博客 994

Spring Native 为何无法超越传统JVM启动速度?深度剖析编译期优化盲区

揭秘Spring Native启动速度瓶颈,深入解析编译期优化盲区与运行时特性丢失。涵盖AOT编译原理、反射配置遗漏等关键问题,对比JVM预热优势,剖析何时该用Native。深度技术剖析,值得收藏。

GatherTide的博客 699

为超越JVM而生?深入理解Kotlin Native的梦想与可能

????目录1 概述2 编译与产物3内存布局4内存管理5跨语言调用6当前的主要问题7未来与展望8 小结Kotlin Native 是 Kotlin 多平台生态的关键一环,也是 Kotlin 开发者突破自身发展瓶颈的重要方向。本文依据 Kotlin Native 的源码,结合作者在运用 Kotlin Native 开发多平台应用的实战经验,详细为大家解读 Kotlin Native 在编译时和运...

QcloudCommunity的博客 3794
上一篇: [Golang实现JVM第五篇]静态方法调用的实现
下一篇: 从JVM角度看为什么final字段可以“延迟“到构造方法中初始化
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值