java 虚拟机详解

一、什么是java虚拟机
        我们常说的java虚拟机是一个运行中的虚拟机实例,它负责运行一个java程序。
二、虚拟机的生命周期
        当一个java程序启动时,一个虚拟机实例也随之产生。当程序退出时,虚拟机实例也随之消亡。每一个java程序都必须独立运行在一个虚拟机实例中。
        虚拟机实例通过一个main()方法来运行一个java程序。虚拟机内部有两种线程,守护线程和非守护线程。当虚拟机实例中所有非守护线程都终止时,虚拟机实例自动退出。
三、虚拟机的体系结构
        要深入了解JVM,就必须要了解它的体系机构。JVM体系结构包括:类加载器子系统、内存区(方法区、堆、java栈等)、数据类型、及指令。下面将讲解它的几个重要构成。


        1)类装载器子系统
        JVM运行java程序时,首先会通过类加载器加载程序中用到的类。它使用类装载器定位相应的class文件,然后读入这个class文件。例如程序遇到new语句或访问了其他类的静态变量时,JVM首先会将类的类型信息加载到方法区(方法区中存储的信息在后面讲解)。JVM有两种类加载器:启动类加载器和用户自定义类加载器,前者是jVM实例的一部分,后者是java程序的一部分。用户自定义类加载器必须派生自ClassLoader类。

        2)方法区
        类加载器加载后,被装载的类型信息存储在一个逻辑上被称为方法区的内存中。当java程序运行时,首先为新创建的对象加载类型信息,进行计算时(例如类型转换,通过父类调用子类方法时),JVM会在方法区中查找类的类型信息。方法区中存储的类型信息包括:
        •这个类型的全限定名。
        •这个类型的直接超类的全限定名。
        •这个类型时类类型还是接口类型。
        •这个类型的访问修饰符
        •任何直接超接口的全限定名的有序列表。

        除了上面列出的基本类型信息外,虚拟机还得为每个被装载的类型存储一下信息:
        •该类型的常量池。
        •字段信息。
        •方法信息。
        •除了常量以外的所有类(静态)变量。
        •一个到类ClassLoader的引用。
        •一个到Class类的引用。

        常量池:虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用常量的一个有序集合,包括这街常量(stirng\integer\floating point常量)和堆其他类型、字段和方法的符号引用。池中的数据项就像数组一样是通过索引访问的。因为常量池存储了相应类型所用到的所有类型、字段和方法的符号引用,所以它在java程序的动态连接中起着核心作用。

        字段信息:字段名、字段的类型、字段的修饰符。

        方法信息:方法名、方法的返回类型、方法的修饰符、方法的字节码、操作数栈和该方法的栈帧中局部变量的大小、异常表。

        指向ClassLoader的引用:每个类型被装载的时候,虚拟机必须跟踪它是由启动类装载器还是由用户自定义装载器装载的。如果是用户自定义装载器装载的,则必须保存对该装载器的引用。

        指向Class类的引用:对于每一个被装载的类型,虚拟机都会相应的为它创建一个java.lang.Class类的实例,而且虚拟机还必须以某种方式把这个实例和存储在方法区中的类型数据关联起来。

        在你的java程序中,你可以得到并使用指向Class对象的应用。Class类中的一个静态方法可以让用户得到任何已装载的类的Class实例的引用。如通过forName得到Class实例的引用:


 
        3)堆
        java程序在运行中创建的所有类实例或数组都放在同一个堆中。一个JVM只存在一个堆空间,因此所有线程都共享这个堆。堆的内存允许用户或程序员指定初始大小、最大最小值等。

        堆的设计有两种可能,一种是把堆分为两部分:一个句柄池,一个对象池,如图5-5所示。而一个对象引用就是 一个指向句柄池的本地指针。句柄池的每个条目有两部分:一个指向对象的实例变量的指针,一个指向方法区类型数据的指针。这种设计的好处有利于堆碎片的整理,但移动对象池中的对象时,句柄部分只需要更改一下指针指向对象的新地址就可以了。缺点是每次访问对象的实例变量都要经过两次指针传递。

         另一种设计是使对象指针直接指向一组数据,如图5-6所示。而该数据包括对象实例数据以及指向方法区中类数据的指针。这样设计的优缺点正好与前面的方法相反,它只需要一个指针就可以访问对象的实例数据,但是移动对象就变得更加复杂。

          有如下几个理由要求虚拟机必须能够通过对象引用得到类数据:当程序在运行时,需要转换某个对象引用为另一种类型时,虚拟机必须要检查这种转换是否被允许,被转换对象是否的确是被引用对象的派生类。最后当程序中调用某个实例方法时,虚拟机必须进行动态绑定,换句话说,它不能按照引用的类型来决定将要调用的方法,而必须根据对象的实际类,为此,虚拟机必须再次通过对象的引用去访问类数据。



 


 
         4)PC寄存器(程序计数器)
          对于一个java程序而言,其中的每一个线程都有它自己的PC寄存器,它是在该线程启动时创建的。PC寄存器的大小是一个字长,因此它既能够持有一个本地指针,也能够持有一个returnAddress。但线程执行某个java方法时,PC寄存器的内容总是下一条将被执行指令的“地址”,这里的“地址”可以是一个本地指针,也可以是在方法字节码中相对于该方法起始指令的偏移量。如果该线程正在执行一个本地方法,那么此时PC寄存器的值是“undefined”。

         5)java栈

        每当启动一个新线程时,java虚拟机都会为它分配一个java栈。java栈以帧为单位保存线程的运行状态。虚拟机只会直接对java栈执行两种操作:以帧为单位的压栈或出栈。

        某个线程正在执行的方法称为当前方法,当前方法使用的栈帧称为当前帧,当前方法所属的类称为当前类,当前类的常量池称为当前常量池。在线程执行一个方法时,它会跟踪当前类和当前常量池。此外,当虚拟机遇到栈内操作指令时,它对当前帧内数据执行操作。

        每当线程调用一个java方法时,虚拟机都会在该线程的java栈中压入一个新帧。而这个新帧自然就成为了当前帧。在执行这个方法时,它使用这个帧来存储参数、局部变量、中间运算结果等等数据。

         java方法可以以两种方式完成。一种通过return返回的,称为正常返回;一种是通过抛出异常二异常中止的。不管以哪种方式,虚拟机都会将当前帧弹出java栈然后释放掉,这样上一个方法的真就成为当前帧了。

         java栈上的所有数据都是此线程私有的。任何线程都不能访问另一个线程的栈数据。当一个线程调用一个方法时,方法的局部变量保存在调用线程java栈的帧中。只有当前线程能总是访问那些局部变量。

         像方法区和堆一样,java栈和帧在内存中也不必是连续的。帧可以分布在连续的栈里,也可以分布在堆里,或者两者都有之。

         

         6)栈帧

         栈帧由三部分组成:局部变量区,操作数栈和帧数据区。局部变量区和操作数栈的大小要视对应的方法而定,它们是按字长计算的。编译器在编译时就确定了这些值并放在class文件中。而帧数据区的大小依赖于具体实现。

         当虚拟机调用一个java方法时,它从对应类的类型信息中得到此方法的局部变量区和操作数栈的大小,并据此分配栈帧的内存,然后压入java栈中。

         局部变量区  :  java栈帧的局部变量区被组织为一个以字长为单位、从0开始计数的数组。这里不作详述。如图5-9


         操作数栈  : 和局部变量一样,操作数栈也是被组织成 一个以字长为单位的数组。但是和前者不同的是,它不是通过索引来访问,而是通过标准的栈操作——压栈和出栈来访问的。

         虚拟机把操作数栈作为它的工作区。大多数指令都要从这里弹出数据,执行运算,然后把结果返回操作数栈。比如,iadd指令就要从操作数栈中弹出两个整数,执行加法运算,其结果又压回到操作数栈中。如图5-10



         帧数据区  :  除了局部变量区和操作数栈外,java栈还需要一些数据来支持常量池解析、正常方法返回以及异常派发机制。这些信息都保存在java栈帧的帧数据区中。

         java虚拟机中的大多数指令都设计到常量池入口。有些指令仅仅是从常量池中取出数据然后压入java栈(这些数据类型包括int\long\float\double\String);还有些指令使用常量池的数据来指示要实例化的类或数组、要访问的字段、或要调用的方法;还有些指令需要常量池中的数据才能确定某个对象是否属于某个类或实现了某个接口。

          每当虚拟机要执行某个需要用到常量池数据的指令时,它都会通过帧数据区中的指向常量池的指针来访问它。常量池中对类型、字段和方法的引用在开始时都是符号。当虚拟机在常量池中搜索的时候,如果遇到指向类、接口、字段或者方法的入口,假若它们仍然是符号,虚拟机才会进行解析。

注:本文参考至《深入java虚拟机》,将对程序员最有用的部分摘取出来分享。

猜你喜欢

转载自zfguo-love.iteye.com/blog/2328079