1.1.12

JIT编译后的本地代码执行时的内存结构是怎么样的?

弱问: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: