弱问:JIT编译后的本地代码执行时的内存结构是怎么样的?
发信人: kyxkcoach (coach), 信区: Java
标 题: 弱问:JIT编译后的本地代码执行时的内存结构是怎么样的?
发信站: 水木社区 (Sat Sep 24 14:03:15 2011), 站内
这时用的堆和栈仍是JVM里面的那个吗,还是直接用操作系统的内存?
这时候动态连接、GC等怎么办?
发信人: IcyFenix ().println("helloworld"), 信区: Java
标 题: Re: 弱问:JIT编译后的本地代码执行时的内存结构是怎么样的?
发信站: 水木社区 (Sat Sep 24 14:42:12 2011), 站内
在java heap的内容:代码是否被jit过,对在java heap里面的内容没有任何影响。
java heap的memory layout只由虚拟机寻址架构(32/64bit)和class文件中定义
的内容所决定。具体见这篇文章的内存布局部分:
http://icyfenix.iteye.com/blog/1145044
在java vm stack的内容:还是那个jvm栈,但是对于同一段代码在jit前后所形成的
栈帧是不一样的。
在method area的内容:填充了native code cache,但原来类加载时bytecode在
method area所产生的数据不能被简单代替掉,因为hotspot中有激进优化以及持续优
化,前者在优化条件不成立时,需要逆优化退回解释状态执行,后者在执行条件变化
时,要重新进行jit,典型的就是osr编译转标准编译,这都需要原有的类数据做支
持。
上面这几块内存区域,都是实现在os的堆上。“动态连接、GC等怎么办?”,这个等问
题具体一些再讨论。以上内容限于hotspot。
发信人: kyxkcoach (coach), 信区: Java
标 题: Re: 弱问:JIT编译后的本地代码执行时的内存结构是怎么样的?
发信站: 水木社区 (Sat Sep 24 14:52:54 2011), 站内
也就是说PC指令从JVM指令变成了本地CPU的指令,但内存分配仍在JVM的Runtime Data
Area里面?
那CPU寻址时懂得去找JVM的内存空间?或者说JVM内存空间和OS内存之间有简单直接的映
射?
发信人: IcyFenix ().println("helloworld"), 信区: Java
标 题: Re: 弱问:JIT编译后的本地代码执行时的内存结构是怎么样的?
发信站: 水木社区 (Sat Sep 24 16:39:56 2011), 站内
不需要做映射,jvm的内存就是OS内存的一部分,能直接访问的。具体我们来做一个小
实验吧。看下面简单的代码:
public class Bar {
int a = 1;
static int b = 2;
public int sum(int c) {
return a + b + c;
}
public static void main(String[] args) {
new Bar().sum(3);
}
}
上面代码很简单,但也能说明问题了:a是实例变量,来自java heap,b是类变量,来
自method area,c是参数,来自vm stack(这几句是概念模型的,jit后,实际jvm
运作是怎样见后面结论的说明,这里提示一下,避免有同学看了开头没看结尾产生误
解),来看看jit之后,他们是怎么访问的。
执行环境是7u02 fastdebug with hsdis:
>java -version
java version "1.7.0-ea-fastdebug"
Java(TM) SE Runtime Environment (build 1.7.0-ea-fastdebug-b127)
Java HotSpot(TM) Client VM (build 20.0-b06-fastdebug, mixed mode)
执行命令:
>java -XX:+PrintAssembly -Xcomp -
XX:CompileCommand=dontinline,*Bar,sum -
XX:CompileCommand=compileonly,*Bar.sum test.Bar
上面-Xcomp是让虚拟机尽量编译模式,这样代码偷懒不用jit预热。两个
CompileCommand意思是不要内联sum并且只编译sum,PrintAssembly就是输出反汇
编内容。
all right,一切顺利的话,屏幕上出现类似下面的内容:
[Disassembling for mach='i386']
[Entry Point]
[Constants]
# {method} 'sum' '(I)I' in 'test/Bar'
# this: ecx = 'test/Bar'
# parm0: edx = int
# [sp+0x20] (sp of caller)
……
0x01cac407: cmp 0x4(%ecx),%eax
0x01cac40a: jne 0x01c6b050 ; {runtime_call}
[Verified Entry Point]
0x01cac410: mov %eax,-0x8000(%esp)
0x01cac417: push %ebp
0x01cac418: sub $0x18,%esp ;*aload_0
; - test.Bar::sum@0 (line
8)
;; block B0 [0, 10]
0x01cac41b: mov 0x8(%ecx),%eax ;*getfield a
; - test.Bar::sum@1 (line
8)
0x01cac41e: mov $0x3d2fad8,%esi ; {oop(a
'java/lang/Class' = 'test/Bar')}
0x01cac423: mov 0x68(%esi),%esi ;*getstatic b
; - test.Bar::sum@4 (line
8)
0x01cac426: add %esi,%eax
0x01cac428: add %edx,%eax
0x01cac42a: add $0x18,%esp
0x01cac42d: pop %ebp
0x01cac42e: test %eax,0x2b0100 ; {poll_return}
0x01cac434: ret
代码很简单,一句一句来看:
检查栈溢(mov %eax,-0x8000(%esp))
保存上一栈帧(push %ebp)
建立新帧(sub $0x18,%esp)
取实例变量a(0x8(%ecx),%eax),这里0x8(%ecx)就是ecx+0x8的意思,前面
[Constants]中提示了,“this:ecx = 'test/Bar'”,那么偏移0x8越过对象头之
后,就是实例变量a的位置。这里没有“映射”,相对地址直接访问。
取test.Bar在方法区的指针($0x3d2fad8,%esi),看,这里0x3d2fad8直接就访问
了,也没有“影射”,如我前面说的,jvm内存也是os内存的一部分,绝对地址也是直接
访问。
取类变量b(mov 0x68(%esi),%esi)
做2次加法,求a+b+c(add %esi,%eax 、add %edx,%eax),c呢?在
[Constants]中提示了,“parm0:edx = int”,在edx中。
撤销栈帧(add $0x18,%esp)
恢复上一栈帧(pop %ebp)
轮询方法返回处的safepoint(test %eax,0x2b0100)
方法返回(ret)
结论:
前一贴提到,java heap、method area、vm stack都在操作系统的堆内存中,在
jit之后,native code面向的是操作系统的堆,说变量a还在java heap中,应当理
解为a的位置还在原来的内存位置上,但是native code是不理会java heap之类的概
念的,因为不是同一个层次的东西。那得出的结论是JVM Spec上的模型与实际运作,
尤其是JIT后实际的内存模型是两码事。
No comments:
Post a Comment