`

《深入Java虚拟机》·书评

阅读更多
最近在读这本书,没要找到书评的,还好今天找到一篇了
谢谢cantellow 的书评


想为我每一本看过的技术书籍都写一个书评,虽然自不量力,写出来的东西更是没有水准,但是我还是想写,因为这样可以加深我印象。本来这本书读完已经快一星期了,但我还是不忍翻翻,觉得很值得回味。我觉得对于我们Java程序员来说,有些章节是没必要看的,你又不是实现JVM,没有必要深入到每个指令都要熟悉。所以我整理了一下,觉得:第1章 Java体系结构介绍、第2章 平台无关、第3章 安全、第5章 Java虚拟机、第7章 类型的生命周期、第8章 连接模型、第9章 垃圾收集章节是必须看的,其他的都可以选看。

为这本书写书评其实也没什么好写的,所以我把以前在读此书时的笔记全部糅合进来,希望大家多多支持,呵呵。

==============================================================================================

书名:《Inside Java Virtual Machine》

中文译名:《深入Java虚拟机》

作者:Bill Venners

出版社:机械工业出版社





一、什么是Java虚拟机
   当你谈到Java虚拟机时,你可能是指:
   1、抽象的Java虚拟机规范
   2、一个具体的Java虚拟机实现
   3、一个运行的Java虚拟机实例
  二、Java虚拟机的生命周期
   一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序。程序开始执行时他才运行,程序结束时他就停止。你在同一台机器上运行三个程序,就会有三个运行中的Java虚拟机。
   Java虚拟机总是开始于一个main()方法,这个方法必须是公有、返回void、直接受一个字符串数组。在程序执行时,你必须给Java虚拟机指明这个包换main()方法的类名。
   Main()方法是程序的起点,他被执行的线程初始化为程序的初始线程。程序中其他的线程都由他来启动。Java中的线程分为两种:守护线程 (daemon)和普通线程(non-daemon)。守护线程是Java虚拟机自己使用的线程,比如负责垃圾收集的线程就是一个守护线程。当然,你也可 以把自己的程序设置为守护线程。包含Main()方法的初始线程不是守护线程。
   只要Java虚拟机中还有普通的线程在执行,Java虚拟机就不会停止。如果有足够的权限,你可以调用exit()方法终止程序。

三、Java虚拟机的体系结构
   在Java虚拟机的规范中定义了一系列的子系统、内存区域、数据类型和使用指南。这些组件构成了Java虚拟机的内部结构,他们不仅仅为Java虚拟机的实现提供了清晰的内部结构,更是严格规定了Java虚拟机实现的外部行为。
   每一个Java虚拟机都由一个类加载器子系统(class loader subsystem),负责加载程序中的类型(类和接口),并赋予唯一的名字。每一个Java虚拟机都有一个执行引擎(execution engine)负责执行被加载类中包含的指令。
   程序的执行需要一定的内存空间,如字节码、被加载类的其他额外信息、程序中的对象、方法的参数、返回值、本地变量、处理的中间变量等等。Java虚拟机将 这些信息统统保存在数据区(data areas)中。虽然每个Java虚拟机的实现中都包含数据区,但是Java虚拟机规范对数据区的规定却非常的抽象。许多结构上的细节部分都留给了 Java虚拟机实现者自己发挥。不同Java虚拟机实现上的内存结构千差万别。一部分实现可能占用很多内存,而其他以下可能只占用很少的内存;一些实现可 能会使用虚拟内存,而其他的则不使用。这种比较精炼的Java虚拟机内存规约,可以使得Java虚拟机可以在广泛的平台上被实现。
   数据区中的一部分是整个程序共有,其他部分被单独的线程控制。每一个Java虚拟机都包含方法区(method area)和堆(heap),他们都被整个程序共享。Java虚拟机加载并解析一个类以后,将从类文件中解析出来的信息保存与方法区中。程序执行时创建的 对象都保存在堆中。
   当一个线程被创建时,会被分配只属于他自己的PC寄存器“pc register”(程序计数器)和Java堆栈(Java stack)。当线程不掉用本地方法时,PC寄存器中保存线程执行的下一条指令。Java堆栈保存了一个线程调用方法时的状态,包括本地变量、调用方法的 参数、返回值、处理的中间变量。调用本地方法时的状态保存在本地方法堆栈中(native method stacks),可能再寄存器或者其他非平台独立的内存中。
   Java堆栈有堆栈块(stack frames (or frames))组成。堆栈块包含Java方法调用的状态。当一个线程调用一个方法时,Java虚拟机会将一个新的块压到Java堆栈中,当这个方法运行结束时,Java虚拟机会将对应的块弹出并抛弃。
   Java虚拟机不使用寄存器保存计算的中间结果,而是用Java堆栈在存放中间结果。这是的Java虚拟机的指令更紧凑,也更容易在一个没有寄存器的设备上实现Java虚拟机。

方法区到底存放些什么?
1、类型信息(Type Information)
每一个被加载的类型,在Java虚拟机中都会在方法区中保存如下信息:
          1)、类型的全名(The fully qualified name of the type)
          2)、类型的父类型的全名(除非没有父类型,或者弗雷形式java.lang.Object)(The fully qualified name of the typeís direct superclass)
          3)、给类型是一个类还是接口(class or an interface)(Whether or not the type is a class )
          4)、类型的修饰符(public,private,protected,static,final,volatile,transient等)(The typeís modifiers)
          5)、所有父接口全名的列表(An ordered list of the fully qualified names of any direct superinterfaces)
类型全名保存的数据结构由虚拟机实现者定义。除此之外,Java虚拟机还要为每个类型保存如下信息:
          1)、类型的常量池(The constant pool for the type)
          2)、类型字段的信息(Field information)
          3)、类型方法的信息(Method information)
          4)、所有的静态类变量(非常量)信息(All class (static) variables declared in the type, except constants)
          5)、一个指向类加载器的引用(A reference to class ClassLoader)
          6)、一个指向Class类的引用(A reference to class Class)

下面我们简单描述下这些数据,相信这些数据在日常编程中是很常见的:

1)、类型的常量池(The constant pool for the type)
          常量池中保存中所有类型是用的有序的常量集合,包含直接常量(literals)如字符串、整数、浮点数的常量,和对类型、字段、方法的符号引用。常量池 中每一个保存的常量都有一个索引,就像数组中的字段一样。因为常量池中保存中所有类型使用到的类型、字段、方法的字符引用,所以它也是动态连接的主要对象。
2)、类型字段的信息(Field information)
          字段名、字段类型、字段的修饰符(public,private,protected,static,final,volatile,transient等)、字段在类中定义的顺序。
3)、类型方法的信息(Method information)
          方法名、方法的返回值类型(或者是void)、方法参数的个数、类型和他们的顺序、字段的修饰符(public,private,protected,static,final,volatile,transient等)、方法在类中定义的顺序
          如果不是抽象和本地本法还需要保存
          方法的字节码、方法的操作数堆栈的大小和本地变量区的大小、异常列表  

4)、类(静态)变量(Class Variables)
          类变量被所有类的实例共享,即使不通过类的实例也可以访问。这些变量绑定在类上(而不是类的实例上),所以他们是类的逻辑数据的一部分。在Java虚拟机使用这个类之前就需要为类变量(non-final)分配内存
          常量(final)的处理方式于这种类变量(non-final)不一样。每一个类型在用到一个常量的时候,都会复制一份到自己的常量池中。常量也像类变 量一样保存在方法区中,只不过他保存在常量池中。(可能是,类变量被所有实例共享,而常量池是每个实例独有的)。Non-final类变量保存为定义他的 类型数据(data for the type that declares them)的一部分,而final常量保存为使用他的类型数据(data for any type that uses them)的一部分。

5)、指向类加载器的引用(A reference to class ClassLoader)
          每一个被Java虚拟机加载的类型,虚拟机必须保存这个类型是否由原始类加载器或者类加载器加载。那些被类加载器加载的类型必须保存一个指向类加载器的引 用。当类加载器动态连接时,会使用这条信息。当一个类引用另一个类时,虚拟机必须保存那个被引用的类型是被同一个类加载器加载的,这也是虚拟机维护不同命 名空间的过程。
6)、指向Class类的引用(A reference to class Class)
          Java虚拟机为每一个加载的类型创建一个java.lang.Class类的实例。你也可以通过Class类的方法:
public static Class forName(String className)来查找或者加载一个类,并取得相应的Class类的实例。通过这个Class类的实例,我们可以访问Java虚拟机方法区中的信息。具体参照Class类的JavaDoc。
一个类的Class对象代表这个类运行时的类信息,之所以要为每个类生成一个class对象,那是因为我们有时在运行时需要这个类的类信息。

为了更有效的访问所有保存在方法区中的数据,这些数据的存储结构必须经过仔细的设计。所有方法区中,除了保存了上边的那些原始信息外,还有一个为了加快存 取速度而设计的数据结构,比如方法列表。每一个被加载的非抽象类,Java虚拟机都会为他们产生一个方法列表,这个列表中保存了这个类可能调用的所有实例 方法的引用,报错那些父类中调用的方法。

Java堆中到底存放些什么?
当Java程序创建一个类的实例或者数组时,都在堆中为新的对象分配内存。虚拟机中只有一个堆,所有的线程都共享他。Java中所有的对象都存放在堆中,包括class对象和异常对象。

那么这些对象中有存放些什么呢?实例数据是肯定的,还有就是当通过对象访问类信息时就必须有一个指针将对象和方法区中的类信息关联起来,关联的方法有多种。一个可能的堆的设计是将堆分为两个部分:引用池和对象池。一个对象的引用就是指向引用池的本地指针。每一个引用池中的条目都包含两个部分:指向对象池中对 象数据的指针和方法区中对象类数据的指针。这种设计能够方便Java虚拟机堆碎片的整理。当虚拟机在对象池中移动一个对象的时候,只需要修改对应引用池中 的指针地址。但是每次访问对象的数据都需要处理两次指针。

另一种堆的设计是:一个对象的引用就是一个指向一堆数据和指向相应对象的偏移指针。这种设计方便了对象的访问,可是对象的移动要变的异常复杂。

无论虚拟机实现者使用哪一种设计,他都可能为每一个对象保存一个类似方法列表的信息。因为他可以提升对象方法调用的速度,对提升虚拟机的性能非常重要,但 是虚拟机的规范中比没有要求必须实现类似的数据结构。

除此之外,堆上的对象数据还有一种逻辑部分,那就是对象锁,这是一个互斥对象。虚拟机中的每个对象都有一个对象锁,它被用于协调多个线程访问同一个对象时的同步。只有当第一次需要加锁的时候才分配对应的锁数据,但这时虚拟机需要用某种间接方法来联系对象数据和对应的锁数据。这也是为什么很多对象在其整个生命周期内都没有被任何线程加锁。除了实现锁所需要的数据外,每个Java对象逻辑上还与实现等待集合(wait set)相关联。

最后一种数据类型-是与垃圾收集器有关的数据。垃圾收集器必须以某种方式跟踪程序引用的每个对象,这个任务不可避免的要附加一些数据给这些对象。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics