本文主要参考了java6的jvm规范和java8的jvm规范,文档可见:http://docs.oracle.com/javase/specs/jvms/se6/html/VMSpecTOC.doc.html和http://docs.oracle.com/javase/specs/jvms/se8/html/index.html。
这里主要以java6的文档来说,这块的内容详见:http://docs.oracle.com/javase/specs/jvms/se6/html/Overview.doc.html#1732。
runtime data area主要由四部分组成:
1、pc register : 就是我们常说的pc,指向当前正在执行的代码,由于java是多线程的,而每个核在同一个时刻只有一个线程执行,假设现在我们有线程A和线程B运行在单核的机器上,线程A执行到一半的时候切换到线程B,过了一段时间又切到A,而这个时候系统是需要知道线程A已经执行到什么位置了,所以每一个线程都需要有一个pc register存储当前执行到的代码的地址,如果是native代码,则它是undefined;
2、method area:存储了类的结构信息等,比如运行时常量池、class属性、方法等,比如常见的字符串String s = "aaaaa";这里的"aaaaa"就是放在运行时常量池中,看着可能比较抽象,等分析字节码的时候就比较清楚了。在Java6时,hotspot vm放在perm gen 去存储,所以很有可能因为工程类过多而导致OOM,可以通过-XX:PermSize和-XX:MaxPermSize来指定大小,不过在Java8中开始采用meta space来存储method area,而它是一个堆外内存,所以不会有这个问题;
3、stack:这个非常重要,每个线程创建时候都会创建,它存储了stack frame,由于stack只能执行pop、push操作,而每一次方法调用其实就是push一个stack frame,而这个stack frame里面存储了local variable(局部变量表,对应下面代码中的a,b,c变量和this指针)、oprand stack(比如a+b这个操作其实就是入栈a,入栈b然后进行iadd,然后将结果入栈)、 reference to runtime constant pool (这个要分析字节码才比较清楚)、exception table(分析字节码比较清楚)等。
public int test(int a,int b) { int c = a + b; return c; }
4、heap:这个是我们最常用的,存储了绝大部分的对象,比如Test a = new Test();Test对象的实例就存放在heap,为了垃圾回收方便,JVM把这片区域分成了:yong generation(新生代)、tenured generation(老年代)。
yong generation又被细分为:eden、from survivor(S0)、to survivor(S1),其中S0和S1大小相等,每次系统可用的大小为eden+一个survivor的大小,为什么JVM要把它分成两个survivor,其实是由垃圾收集方法决定的,jvm采用了copying的方式收集yong generation,比如现在eden+S0空间被使用,这个时候yong generation大小不够了,触发了一次YGC,所有存活的对象就会被赶到S1去,然后将S0和eden中的空间全部清理干净,之后系统就直接使用eden+S1来存放对象,S0就变成了备胎。
在yong generation中存放的的对象,90%以上都是朝生夕死,在下一次YGC的时候就被干掉了,如果这个对象在一次YGC的时候还能够被Gc roots引用到(也就是说这个对象还有用到),那么就将这个对象的年龄+1,当这个对象的年龄达到某一个阀值时,则开始将这个对象转移到tenured。当然,不仅仅是当对象达到年龄的时候才会被转移到tenured generation,当yong generation实在没有空间的情况下也会的,之前我说到过,在YGC时会将还存活的对象从一个survivor复制到另一个survivor,如果存活对象太多,一个survivor放不下的时候,那也会坑。
在这里,我们用一个简单的Java代码来说明一下,代码如下:
public class Test { public static void main(String[] args) { Test test = new Test(); test.test(100L,2); } public long test(long first,int second) { try{ long third = first + second; return third; }catch (Exception e){ e.printStackTrace(); } return 0L; } }
我们执行javac Test.java编译完之后,执行javap -verbose Test得到 适合人阅读的字节码:
Compiled from "Test.java" public class Test extends java.lang.Object SourceFile: "Test.java" minor version: 0 major version: 50 Constant pool: const #1 = Method #9.#22; // java/lang/Object."<init>":()V const #2 = class #23; // Test const #3 = Method #2.#22; // Test."<init>":()V const #4 = long 100l; const #6 = Method #2.#24; // Test.test:(JI)J const #7 = class #25; // java/lang/Exception const #8 = Method #7.#26; // java/lang/Exception.printStackTrace:()V const #9 = class #27; // java/lang/Object const #10 = Asciz <init>; const #11 = Asciz ()V; const #12 = Asciz Code; const #13 = Asciz LineNumberTable; const #14 = Asciz main; const #15 = Asciz ([Ljava/lang/String;)V; const #16 = Asciz test; const #17 = Asciz (JI)J; const #18 = Asciz StackMapTable; const #19 = class #25; // java/lang/Exception const #20 = Asciz SourceFile; const #21 = Asciz Test.java; const #22 = NameAndType #10:#11;// "<init>":()V const #23 = Asciz Test; const #24 = NameAndType #16:#17;// test:(JI)J const #25 = Asciz java/lang/Exception; const #26 = NameAndType #28:#11;// printStackTrace:()V const #27 = Asciz java/lang/Object; const #28 = Asciz printStackTrace; { public Test(); Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 public static void main(java.lang.String[]); Code: Stack=4, Locals=2, Args_size=1 0: new #2; //class Test 3: dup 4: invokespecial #3; //Method "<init>":()V 7: astore_1 8: aload_1 9: ldc2_w #4; //long 100l 12: iconst_2 13: invokevirtual #6; //Method test:(JI)J 16: pop2 17: return LineNumberTable: line 3: 0 line 4: 8 line 5: 17 public long test(long, int); Code: Stack=4, Locals=6, Args_size=3 0: lload_1 1: iload_3 2: i2l 3: ladd 4: lstore 4 6: lload 4 8: lreturn 9: astore 4 11: aload 4 13: invokevirtual #8; //Method java/lang/Exception.printStackTrace:()V 16: lconst_0 17: lreturn Exception table: from to target type 0 8 9 Class java/lang/Exception LineNumberTable: line 8: 0 line 9: 6 line 10: 9 line 11: 11 line 13: 16 StackMapTable: number_of_entries = 1 frame_type = 73 /* same_locals_1_stack_item */ stack = [ class java/lang/Exception ] }
首先我们看到constant pool中的内容,从const #1到const #28,我们直接看注释,比如const #6,类型是Method,对应着的就是Java代码中的public long test(long first,int second),从这里你就可以理解method area中存放的是啥了把,从method area可以看到类的一个大概,比如类名、方法列表、属性列表等等。
然后我们看看具体的方法public long test(long first,int second),对应字节码是:
public long test(long, int); Code: Stack=4, Locals=6, Args_size=3 0: lload_1 1: iload_3 2: i2l 3: ladd 4: lstore 4 6: lload 4 8: lreturn 9: astore 4 11: aload 4 13: invokevirtual #8; //Method java/lang/Exception.printStackTrace:()V 16: lconst_0 17: lreturn Exception table: from to target type 0 8 9 Class java/lang/Exception LineNumberTable: line 8: 0 line 9: 6 line 10: 9 line 11: 11 line 13: 16 StackMapTable: number_of_entries = 1 frame_type = 73 /* same_locals_1_stack_item */ stack = [ class java/lang/Exception ] }
我说过stack frame中存储了local variable,oprand stack等,我们来看看local variable,这里的变量有那些呢,首先参数中有long first,int second这两个,然后还有方法体中的long third和Exception e,那还有吗,其实还有一个,就是this,它是隐含在内的,我们看到字节码中有一个Locals=6,那这个代表什么呢,其实是代表局部变量有6个slot,JVM定义了在32位系统中32bit及小于32位的变量占用一个slot,而64位的占用两个slot,比如int,char,Object的引用,都是1个word,而long,double占用两个slot,多说一点,long,double JVM并不保证它是原子的。那么我们看看现在的局部变量:long first(2 slot) + int second (1 slot) + this (1 slot) + long third (2 slot) + Exception e (1 slot) = 7 slot,什么情况,为什么比字节码中多了一个slot?
原来JVM会使用slot来复用local variable,怎么理解,很简单,在局部变量表中,如果一个这个变量已经过了作用域,那么后续的局部变量可以复用它的位置,比如这个例子里面的long third = first + second;third这个变量在Exception e这个变量使用时其实已经过了作用域,而它本来占用的local variable的位置为4和5,所以Exception e这个变量占用的是位置4,所以加起来总共locals=6,大家可以将long third = first + second;提到try上面去试试,locals会变成7。
我们看到上面字节码中有exception table,这个是干啥的,其实很简单,就是说只要字节码0-8,如果出现了异常类型为java/lang/Exception的异常,则跳转到字节码9,这里首先astore 4然后aload 4,都是对局部变量表的下标为4的元素操作,也可以间接证明之前说的slot复用的问题。
我们现在看看字节码:
13: invokevirtual #8; //Method java/lang/Exception.printStackTrace:()V
e.printStackTrace();
我是初学者,有写错的地方欢迎大家拍砖!