JIT编译的代码存放在哪里?

9

我有一个用Java编写的方法:

public void myMethod(int y){
    int x = 5 + y;
    doSomething(x);
}

假设我的程序频繁地调用这个方法...

在Java虚拟机上运行编译好的代码时,JVM首先会解释该方法。然后经过一段时间后,如果我理解正确的话,它会决定将其编译成机器语言。

此时,

它会被机器码覆盖吗?如果被覆盖了,那么大小差异的问题如何解决?如果它被写入内存中的其他位置,已经加载到内存中的字节码是否会被释放?此外,如果字节码和JIT编译的代码都存在于内存中,当应用再次调用该方法时,JVM如何决定执行JIT编译的代码而不是字节码?


我明白这种事情取决于你是在Windows机器上运行还是在内存匮乏的微控制器上运行。换句话说,它是平台相关的,对吧? - Ghostkeeper
2
答案1:你为什么在意呢? 答案2:JVM将每个方法的JITCed代码保存在C堆中,并通过类对象的内部表示中的表进行链接。只有当JVM结束或在“卸载”类的罕见情况下才会删除代码。这一切都由适当的魔法管理。 - Hot Licks
1
“C heap”在这里实际上是一个误称 - 如果它指的是使用malloc调用获得的内存(可能或可能不是页面对齐),相应的页面必须标记为可执行。更有可能的是,JIT使用mmap、VirtualAlloc等来为生成的代码分配内存。 - Martin Törnwall
答案是实现定义的。 - davmac
1
@MartinTörnwall - “C heap”是JVM内部通常用于指代不属于GC堆的堆的通用术语。至于它的实现方式,这取决于它运行的操作系统(除了您想象中的那个环境外,还有许多其他环境)。 - Hot Licks
1
@HotLicks 啊,为什么你在意的那个人来了... 你好! - Koray Tugay
3个回答

16

HotSpot JVM 在元空间中(或早期版本的 PermGen 中)拥有一个Method结构。

它包含方法字节码,该字节码永远不会被覆盖,并且指向已编译代码的指针,最初为空,直到方法被编译。

一个方法可能有多个入口点:

  • _i2i_entry - 指向字节码解释器的指针。
  • _code->entry_point() - JIT编译代码的入口点。编译后的方法驻留在CodeCache中 - VM动态生成代码的特殊本机内存区域。
  • i2cc2i适配器,用于在解释器和编译代码之间调用。这些适配器是必要的,因为解释方法和编译方法具有不同的调用约定(传递参数的方式,如何构造帧等)

编译的方法可以有不常见的陷阱,在一些罕见情况下会回退到解释器。此外,Java方法可以多次动态重新编译,因此JVM无法放弃原始字节码。无论如何,释放它是没有意义的,因为字节码通常比编译代码小得多。


6
不,它不会被覆盖,因为将这两种表示放在同一位置通常没有实际好处。 JVM字节码只是数据片段。 JIT生成的代码是一系列本机CPU指令的流(在某些架构中,需要明确标记为可执行)。
通常,当需要执行新功能时,JIT编译器读取该函数的字节码,分配其他位置的内存,将等效的本机代码写入该内存,然后返回指向新生成的本机代码入口的函数指针。

2
此外,在许多现代平台上,您必须明确要求虚拟内存子系统提供可执行内存。将字节码分配给可执行页面没有充分的理由。 - Martin Törnwall

3
据我所知,Java®虚拟机规范没有指定任何相关内容。
我能找到的唯一与JIT有关的参考是在第三章中:

[...] 这种翻译器的一个例子是即时(JIT)代码生成器,它仅在Java虚拟机代码已加载后才生成特定于平台的指令。本章不涉及与代码生成相关的问题,只涉及将用Java编程语言编写的源代码编译为Java虚拟机指令的问题。

据我理解,这可以由不同的实现以不同的方式完成。
但是,在我的看法中,似乎很不可能使用本地CPU指令覆盖包含Java字节码的内存,因为CPU指令在技术上是可执行的,而字节码只是数据,必须进行解释。虽然这并非不可能,但非常奇怪。

那么Java如何让CPU执行这个本地代码呢? - user13947194
@user13947194 mprotect 然后跳转到其地址? - Siguza
你能详细说明一下吗?mprotect是什么? - user13947194

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接