深入了解JVM-----Inside JVM读书笔记
[作者]:菩提树下的杨过 [来源]:互联网 [收录时间]:2007-8-1 20:16:02

本文首先介绍一下Java虚拟机的生存周期,然后大致介绍JVM的体系结构,最后对体系结构中的各个部分进行详细介绍。



( 首先这里澄清两个概念:JVM实例和JVM执行引擎实例,JVM实例对应了一个独立运行的java程序,而JVM执行引擎实例则对应了属于用户运行程序的线程;也就是JVM实例是进程级别,而执行引擎是线程级别的。)



一、 JVM的生命周期



JVM实例的诞生:当启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点,既然如此,那么JVM如何知道是运行class A的main而不是运行class B的main呢?这就需要显式的告诉JVM类名,也就是我们平时运行java程序命令的由来,如java classA hello world,这里java是告诉os运行Sun java 2 SDK的java虚拟机,而classA则指出了运行JVM所需要的类名。



JVM实例的运行:main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以标明自己创建的线程是守护线程。



JVM实例的消亡:当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用Runtime类或者System.exit()来退出。



二、JVM的体系结构



粗略分来,JVM的内部体系结构分为三部分,分别是:类装载器(ClassLoader)子系统,运行时数据区,和执行引擎。



下面将先介绍类装载器,然后是执行引擎,最后是运行时数据区



1,类装载器,顾名思义,就是用来装载.class文件的。JVM的两种类装载器包括:启动类装载器和用户自定义类装载器,启动类装载器是JVM实现的一部分,用户自定义类装载器则是Java程序的一部分,必须是ClassLoader类的子类。(下面所述情况是针对Sun JDK1.2)



动类装载器:只在系统类(java API的类文件)的安装路径查找要装入的类



用户自定义类装载器:



系统类装载器:在JVM启动时创建,用来在CLASSPATH目录下查找要装入的类



其他用户自定义类装载器:这里有必要先说一下ClassLoader类的几个方法,了解它们对于了解自定义类装载器如何装载.class文件至关重要。



protected final Class defineClass(String name, byte data[], int offset, int length)



protected final Class defineClass(String name, byte data[], int offset, int length, ProtectionDomain protectionDomain);



protected final Class findSystemClass(String name)



protected final void resolveClass(Class c)



defineClass用来将二进制class文件(新类型)导入到方法区,也就是这里指的类是用户自定义的类(也就是负责装载类)



findSystemClass通过类型的全限定名,先通过系统类装载器或者启动类装载器来装载,并返回Class对象。



ResolveClass: 让类装载器进行连接动作(包括验证,分配内存初始化,将类型中的符号引用解析为直接引用),这里涉及到java命名空间的问题,JVM保证被一个类装载器装载的类所引用的所有类都被这个类装载器装载,同一个类装载器装载的类之间可以相互访问,但是不同类装载器装载的类看不见对方,从而实现了有效的屏蔽。



2, 执行引擎:它或者在执行字节码,或者执行本地方法



要说执行引擎,就不得不的指令集,每一条指令包含一个单字节的操作码,后面跟0个或者多个操作数。(一)指令集以栈为设计中心,而非以寄存器为中心



这种指令集设计如何满足Java体系的要求:



平台无关性:以栈为中心使得在只有很少register的机器上实现java更便利



compiler一般采用stack向连接优化器传递编译的中间结果,若指令集以stack为基础,则有利于运行时进行的优化工作与执行即时编译或者自适应优化的执行引擎结合,通俗的说就是使编译和运行用的数据结构统一,更有利于优化的开展。



网络移动性:class文件的紧凑性。



安全性:指令集中绝大部分操作码都指明了操作的类型。(在装载的时候使用数据流分析期进行一次性验证,而非在执行每条指令的时候进行验证,有利于提高执行速度)。



(二)执行技术



主要的执行技术有:解释,即时编译,自适应优化、芯片级直接执行



其中解释属于第一代JVM,即时编译JIT属于第二代JVM,自适应优化(目前Sun的HotspotJVM采用这种技术)则吸取第一代JVM和第二代JVM的经验,采用两者结合的方式



自适应优化:开始对所有的代码都采取解释执行的方式,并监视代码执行情况,然后对那些经常调用的方法启动一个后台线程,将其编译为本地代码,并进行仔细优化。若方法不再频繁使用,则取消编译过的代码,仍对其进行解释执行。



3,运行时数据区:主要包括:方法区,堆,java栈,PC寄存器,本地方法栈



(1)方法区和堆由所有线程共享



堆:存放所有程序在运行时创建的对象



方法区:当JVM的类装载器加载.class文件,并进行解析,把解析的类型信息放入方法区。



(2)Java栈和PC寄存器由线程独享,在新线程创建时间里



(3)本地方法栈: 存储本地方法调用的状态



上边总体介绍了运行时数据区的主要内容,下边进行详细介绍,要介绍数据区,就不得不说明JVM中的数据类型。



JVM中的数据类型:JVM中基本的数据单元是word,而word的长度由JVM具体的实现者来决定



数据类型包括基本类型和引用类型,



(1) 基本类型包括:数值类型(包括除boolean外的所有的java基本数据类型),boolean(在JVM中使用int来表示,0表示false,其他int值均表示true)和returnAddress(JVM的内部类型,用来实现finally子句)。



(2) 引用类型包括:数组类型,类类型,接口类型



前边讲述了JVM中数据的表示,下面让我们输入到JVM的数据区



首先来看方法区:



上边已经提到,方法区主要用来存储JVM从class文件中提取的类型信息,那么类型信息是如何存储的呢?众所周知,Java使用的是大端序(big—endian:即低字节的数据存储在高位内存上,如对于1234,12是高位数据,34为低位数据,则java中的存储格式应该为12存在内存的低地址,34存在内存的高地址,x86中的存储格式与之相反)来存储数据,这实际上是在class文件中数据的存储格式,但是当数据倒入到方法区中时,JVM可以以任何方式来存储它。



类型信息:包括class的全限定名,class的直接父类,类类型还是接口类型,类的修饰符(public,等),所有直接父接口的列表,Class对象提供了访问这些信息的窗口(可通过Class.forName(“”)或instance.getClass()获得),下面是Class的方法,相信大家看了会恍然大悟,(原来如此J)



getName(), getSuperClass(), isInterface(), getInterfaces(), getClassLoader();



static变量作为类型信息的一部分保存



指向ClassLoader类的引用:在动态连接时装载该类中引用的其他类



指向Class类的引用:必然的,上边已述



该类型的常量池:包括直接常量(String,integer和float point常量)以及对其他类型、字段和方法的符号引用(注意:这里的常量池并不是普通意义上的存储常量的地方,这些符号引用可能是我们在编程中所接触到的变量),由于这些符号引用,使得常量池成为java程序动态连接中至关重要的部分



字段信息:普通意义上的类型中声明的字段



方法信息:类型中各个方法的信息



编译期常量:指用final声明或者用编译时已知的值初始化的类变量



class将所有的常量复制至其常量池或者其字节码流中。



方法表:一个数组,包括所有它的实例可能调用的实例方法的直接引用(包括从父类中继承来的)



除此之外,若某个类不是抽象和本地的,还要保存方法的字节码,操作数栈和该方法的栈帧,异常表。



举例:



class Lava{



private int speed = 5;



void flow(){}



}



class Volcano{



public static void main(String[] args){



Lava lava = new Lava();



lava.flow();



}



}



运行命令java Volcano;



(1) JVM找到Volcano.class倒入,并提取相应的类型信息到方法区。通过执行方法区中的字节码,JVM执行main()方法,(执行时会一直保存指向Vocano类的常量池的指针)



(2) Main()中第一