Java虚拟机工作原理详解( 二 )

  • Resolving:将该类常量池中的符号引用都改变为直接引用 。(不是很理解)
  • Initialing:初始化类的局部变量,为静态域赋值,同时执行静态初始化块 。
  • 那么,Class Loader在加载类的时候,究竟做了些什么工作呢?
    要了解这其中的细节,必须得先详细介绍一下运行数据区域 。
    二、运行数据区域Runtime Data Areas:当运行一个JVM示例时,系统将分配给它一块内存区域(这块内存区域的大小可以设置的),这一内存区域由JVM自己来管理 。从这一块内存中分出一块用来存储一些运行数据,例如创建的对象,传递给方法的参数,局部变量,返回值等等 。分出来的这一块就称为运行数据区域 。运行数据区域可以划分为6大块:Java栈、程序计数寄存器(PC寄存器)、本地方法栈(Native Method Stack)、Java堆、方法区域、运行常量池(Runtime Constant Pool) 。运行常量池本应该属于方法区,但是由于其重要性,JVM规范将其独立出来说明 。其中,前面3各区域(PC寄存器、Java栈、本地方法栈)是每个线程独自拥有的,后三者则是整个JVM实例中的所有线程共有的 。这六大块如下图所示:
    Java虚拟机工作原理详解

    文章插图
     
    ①. PC计数器:每一个线程都拥有一个PC计数器,当线程启动(start)时,PC计数器被创建,这个计数器存放当前正在被执行的字节码指令(JVM指令)的地址 。
    ②. Java栈:同样的,Java栈也是每个线程单独拥有,线程启动时创建 。这个栈中存放着一系列的栈帧(Stack Frame),JVM只能进行压入(Push)和弹出(Pop)栈帧这两种操作 。每当调用一个方法时,JVM就往栈里压入一个栈帧,方法结束返回时弹出栈帧 。如果方法执行时出现异常,可以调用printStackTrace等方法来查看栈的情况 。栈的示意图如下:
    Java虚拟机工作原理详解

    文章插图
     
    OK 。现在我们再来详细看看每一个栈帧中都放着什么东西 。从示意图很容易看出,每个栈帧包含三个部分:本地变量数组,操作数栈,方法所属类的常量池引用 。
    1. 局部(本地)变量数组:
    局部(本地)变量数组中,从0开始按顺序存放方法所属对象的引用、传递给方法的参数、局部变量 。举个例子:
    public void doSomething(int a, double b, Object o) {...}这个方法的栈帧中的局部变量存储的内容分别是:
    0: this1: a2,3:b4:0看仔细了,其中double类型的b需要两个连续的索引 。取值的时候,取出的是2这个索引中的值 。如果是静态方法,则数组第0个不存放this引用,而是直接存储传递的参数 。
    【Java虚拟机工作原理详解】2. 操作数栈:
    操作数栈中存放方法执行时的一些中间变量,JVM在执行方法时压入或者弹出这些变量 。其实,操作数栈是方法真正工作的地方,执行方法时,局部变量数组与操作数栈根据方法定义进行数据交换 。例如,执行以下代码时,操作数栈的情况如下:
    int a = 90;int b = 10;int c = a + b;
    Java虚拟机工作原理详解

    文章插图
     
    注意在这个图中,操作数栈的地步是在上边,所以先压入的100位于上方 。可以看出,操作数栈其实是一个数据临时存储区,存放一些中间变量,方法结束了,操作数栈也就没有啦 。
    3. 栈帧中数据引用:
    除了局部变量数组和操作数栈之外,栈帧还需要一个常量池的引用 。当JVM执行到需要常量池的数据时,就是通过这个引用来访问常量池的 。栈帧中的数据还要负责处理方法的返回和异常 。如果通过return返回,则将该方法的栈帧从Java栈中弹出 。如果方法有返回值,则将返回值压入到调用该方法的方法的操作数栈中 。另外,数据区中还保存中该方法可能的异常表的引用 。下面的例子用来说明:
    class Example3C{public static void addAndPrint(){double result = addTwoTypes(1,88.88);System.out.println(result);}public static double addTwoTypes(int i, double d){return i+d;}}执行上述代码时,Java栈如下图所示:
    Java虚拟机工作原理详解

    文章插图
     
    花些时间好好研究上图 。一样需要注意的是,栈的底部在上方,先押人员addAndPrint方法的栈帧,再压入addTwoTypes方法的栈帧 。上图最右边的文字说明有错误,应该是addTwoTypes的执行结果存放在addAndPrint的操作数栈中 。
    4. 本地方法栈


    推荐阅读