千里之行始于足下

《深入Java虚拟机》第1-9章读书笔记

Posted on By Peter Yang

最近花了一些时间读了这本介绍Java虚拟机的经典著作的前面1-9章,感觉这是一本非常赞的书,如作者所说,详细介绍了Java虚拟机的方方面面,可以作工具参考书使用。下面作一些笔记和理解。

这本书共20章,第1章综述;第2章到第5章描述Java的体系结构;第6章简介Java编译生成的class文件结构;第7章介绍运行时的类型生命周期,包括装载、连接、初始化以及卸载的过程;第8章深入Java的连接模型,介绍Java类型是如何被连接的;第9章介绍了垃圾收集的几种不同技术。从第10章开始一直最后第20章,全部都是介绍Java虚拟机的指令集,可以当作工具书来作查阅和参考。

Java体系结构的最大特点就是平台无关性,它采用虚拟机的方式,将Java应用程序(即编译生成的class文件)读取到虚拟机中并解释运行(这是一种方式,另外也有JIT方式的,即Just-In-Time即时编译运行)。Java虚拟机的这种思想也是计算机分层思想的一种体现,并且带来的好处就是隔离与解耦,因而可以看到其带来了平台无关性的好处。

然而平台无关性也同样赋予了Java其它优点,即网络移动性,即可以在网络中传递其程序文件并四处运行。当然这并不是平台无关性这一点就能决定的,同时还因为它的class文件结构设计的很紧凑,文件体积通常比较小,便于网络传输。

缺点就是效率低下,在早期版本中尤为明显,现在由于Java本身发展其性能逐步得到优化,另外机器性能也在不断提升,使其对性能要求不太高的上层应用中得以迅速发展。

如果要在Java中使用系统的API或是其它程序语言接口,Java提供了JNI(Java Native Interface)作为两者交互的接口。不过使用了这些接口的Java程序在可移植性上会有额外开销,需要对不同系统作部分移植工作。

Java程序的网络移动性虽然带来了不少便利,但同时也引入一个非常重要的问题,就是安全性。程序可以四处运行,也就意味着哪都不能随便运行。Java体系结构在安全性方面作了很多工作,早期1.0版本的沙箱模型,1.1版本的代码签名认证到1.2版相对完善的细粒度访问控制模型。沙箱模型过于严格,很多操作都不可以执行;代码签名认证倒是可行,只是粒度太粗,代码要么全部接受,要么全部拒绝;细粒度访问控制模型就比较合适了,提供了足够细致的访问控制。这里就不细述了,只是要知道用户可以自定义访问控制策略,通过继承一些Java体系结构内的安全策略类型并在程序起始时加载到安全策略管理器中就可以,具体使用可以到时查阅。

Java的class文件的结构被设计的非常紧凑,而且概念清晰和简单,通篇采用类型表和引用来描述,语法结构非常简单明了。

Java虚拟机的体系结构大体包括类装载器子系统、方法区、堆区、栈区和执行引擎等。其中类装载器负责装载连接和管理类型信息,方法区加载方法字节码,堆区存储对象数据,栈区存储函数调用时的一些栈引用变量的值,执行引擎解析Java指令并执行。

一个Java类型的生命周期包括以下几个过程,装载、验证、准备、解析、初始化和卸载。其中装载过程就是简单的把类型信息从文件装载中进来。验证是验证类型的安全性,完整性等一些信息。准备阶段为类型的类属变量分配内存空间并赋予该类型的默认值(并不是程序指定的值,此时尚未运行任何类型相关的代码)。解析过程顾名思义,就是把类型class文件中的各种引用字段,例如引用到的其它类型,引用到的方法或是字段,将这些符号引用替换为直接引用(这一步有点像C++的编译过程中的连接步骤,不过是即时执行的)。初始化就是对类属变量进行正确的赋值,以及运行类的静态构造函数(如果存在或是编译器生成的话)。卸载类型一般情况下是不会发生的,除非使用了用户自定义的类型装载器,那么就有可能发生卸载,卸载机制与垃圾回收类似。

Java的类型连接(即前面所述的解析过程)是动态进行的,即一个类型可以仅在被使用到的时候才进行连接,更有甚者,其中的方法连接还可以进一步滞后,直到被使用的时候才进行解析连接(类似于C++下的动态链接库)。解析过程前面已说明过,书中主要是针对各种常量池类型的解析分别进行说明,需要了解详细信息再查阅。另外需要注意的是装载约束,这一机制的引入是为了破坏者写恶意代码以读取到类型对象的私有变量的值。我想其实这是在提供了自定义类型装载机制之后提出的补丁性质的解决方案,没有深入了解过,不知道现在有没有更好更完美的解决方案。

垃圾回收是Java的一大特色,对Java程序员来说,它不需要程序员关注内存的申请与释放,这些全部被虚拟机内部处理。这可以降低程序的开发周期,Bug数量,让程序员有更多精力关注上层逻辑,好处不言自明。现有很多服务器上应用程序采用Java程序书写,这也是原因之一(当然也是服务器性能足够强大,使Java程序本身带来的性能缺失可以忽略不计)。缺点也很明显,对象释放时机不明,因此编写的程序正确性不能依赖于对象析构。似乎后来也有补充机制来弥补这一缺陷,不过也算是补丁性质的东西。

由于虚拟机来决定析构对象,因而也引入了更多的复杂性。例如,用户自定义的析构函数中可能使用到了其它的一些对象,甚至自己,因而可能让自己在析构过程中复活。Java对此作了详细的状态约束和规定,保证最终可以释放对象。

书中提到了几种垃圾回收算法,最基本的引用计数,由于不能处理循环引用而基本弃用。跟踪收集采用标记-清除方法,即从对象引用森林(由于根对象不至一个,因此是森林不是树)中遍历作标记,再将无标记的清除,这是个基本可以用的算法,当然性能上有所欠缺。还有一类是拷贝算法,即将堆空间分成两部分,每次只用一半,当要用尽的时候,按对象引用森林进行拷贝,然后清除。这种方法不需要作标记,但是对于长久存在的对象会一直拷贝,对此进行优化的算法即按代收集,将对象按存在时间长短进行分类,对不同类分别进行垃圾回收。这一分治的思想使得其可以进一步扩展,不同代使用不同算法,即自适应算法。

最后书中介绍当时Sun公司的Hotspot虚拟机使用的火车算法,这是针对同代中垃圾收集的算法。这一算法的优化目标是减小垃圾回收本身带来的破坏性(即对正常程序运行的影响,之前的一些算法需要在某个时刻停止程序运行来进行垃圾回收)。算法对内存进行更细粒度的区分,另外也使用了记忆集合(额外空间)以加速处理,从而提高了整体的回收效率,并且可以分段回收,每次只回收一部分。

书中还介绍了Java的引用机制,这部分我没怎么看懂,所谓的强可触及引用(即一般情况下的对象引用)、软可触及引用、弱可触及引用和影子可触及引用。只知道后面这三种是为了规范映射等机制而使用的,例如HashMap。在我看来,不够完美,这种机制的引入基本上就是为了某种特性。除了这些不知道还有别的什么会用不?或者是我理解错了,欢迎指正。

好了,基本上就看了这么多,指令集基本没有看,有需要时再看吧。我的关注点在体系结构,下面再去看看.net的框架结构,理解下JIT是怎么运作的。