JVM入门学习之 runtime data area
JVM入门学习之 runtime data area
阳光test 发表于3年前
JVM入门学习之 runtime data area
  • 发表于 3年前
  • 阅读 73
  • 收藏 0
  • 点赞 0
  • 评论 0

移动开发云端新模式探索实践 >>>   

摘要: 初步讲解了一下JVM的运行时数据区域,其中包括:pc、method area、stack、heap。

        本文主要参考了java6的jvm规范和java8的jvm规范,文档可见:http://docs.oracle.com/javase/specs/jvms/se6/html/VMSpecTOC.doc.htmlhttp://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



      这里对应的Java代码就是



e.printStackTrace();



     我们知道#8在const pool中,而方法调用其实是在stack中,我们也就能够理解stack中的referene to runtime constant pool了。


    我是初学者,有写错的地方欢迎大家拍砖!

 



  








标签: java jvm
  • 打赏
  • 点赞
  • 收藏
  • 分享
共有 人打赏支持
阳光test
粉丝 542
博文 71
码字总数 91741
作品 1
×
阳光test
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: