Java 虚拟机是支持 Java 语言运行的基础,避开过多的 JVM 和实现的技术细节,我们对基础
架构进行了解,是进行应用程序优化必不可少的。如下图所示:
图 1. Java 虚拟机体系结构
类装载子系统:通过类的全限定名(包名和类名,网络装载还包括 URL)将 Class 装载进运行
时数据区;
方法区:Class 对于所有方法和 static 静态数据的定义存储在这里,它就像一张表或数组,
让程序执行时在这里找到相应方法的 Java 字节码和静态数据;
Java 堆:Java 对象的持久化存储区,从类实例化而来的对象存储在此,垃圾收集也在此进行
,若是空间不够容纳当前所有对象,Out Of Memory 的异常将会抛出,对 Java 堆和垃圾收集
的认识对应用性能调优很关键;
Java 栈:Java 方法的字节码执行的地方,方法中局部变量的生命周期都在栈中,栈的大小是
我们要考虑的一个关键点,它直接决定了方法调用的层数,这对递归程序来说尤为重要。我们
所用的 JVM 都是基于 Java 栈的运行机制,而有一个例外的实现,Google 移动设备操作系统
Android 的虚拟机 Dalvik 则是基于寄存器的机制(Dalvik 虽然支持 Java 语言开发,但从虚
拟机的角度看,并不符合 Java 标准)。
程序计数器:对于基于栈实现的 JVM,这几乎是唯一寄存器了,它用来指示当前 Java 执行引
擎执行到哪条 Java 字节码,指针指向方法区的字节码;
本地方法栈:这是 Java 调用操作系统本地库的地方,用来实现 JNI(Java Native Interface
,Java 本地接口);
执行引擎:JVM 的心脏,控制装入 Java 字节码并解析;
本地接口:连接了本地方法栈和操作系统库。
Java 字节码是 JVM 的指令,所有 Java 平台虚拟机有各自的指令集,而大部分指令相同,共
200 条左右,Java Card 虚拟机由于支持的数据类型少,相应的指令较少。部分虚拟机实现商
为了优化性能,增加了一些自己特有的指令,当对于 Java 程序员来说,是透明的。下面是一
段 Java 方法的字节码示例:
清单 1. Java 字节码例- /* 0x000092c4:0x04a7: */ _SCONST_0,
- /* 0x000092c5:0x04a8: */ _SCONST_0,
- /* 0x000092c6:0x04a9: */ _INVOKESTATIC, HIGH(0x08e8), LOW(0x08e8),
- /* 0x000092c9:0x04ac: */ _POP,
- /* 0x000092ca:0x04ad: */ _INVOKESTATIC, HIGH(0x8046), LOW(0x8046),
- /* 0x000092cd:0x04b0: */ _IFEQ, 84,
- /* 0x000092cf:0x04b2: */ _INVOKESTATIC, HIGH(0x8044), LOW(0x8044),
- /* 0x000092d2:0x04b5: */ _GOTO, 79,
- /* 0x000092d4:0x04b7: */ _ASTORE, 7,
复制代码 当程序计数器中值为 0x000092ca:0x04ad,表明下一条即将执行字节码为 _INVOKESTATIC,
HIGH(0x8046), LOW(0x8046),该字节码表明将调用某个静态方法。
Java 语言一大好处就是不用关心对于内存的分配和回收,一切由垃圾收集器搞定。然而这并不
代表 Java 程序员可以高枕无忧,再高效的收集器也可能因为滥用而导致性能问题。我们已经
知道,Java 程序所涉及的空间分配和回收包括:
Java 堆,创建的 Java 对象(包括数组,数组也是一种对象)分配在堆中,垃圾收集对象来释
放空间;
Java 栈,栈划分为操作数栈、栈帧数据和局部变量区,方法中分配的局部变量在栈中,同时每
一次方法的调用都会在栈中分配栈帧,因此程序员在设计和开发应用时需考虑调用层数。
来看一段字节码在 Java 栈中的执行示例,100 与 98 相加:
清单 2. 整数加法运算的 Java 字节码- iload_0 // 载入局部变量 0,整型,压入栈中
- iload_1 // 载入局部变量 1,整型,压入栈中
- iadd // 弹出两个整型数,相加,将结果压入栈
- istore_2 // 弹出整型数,存入局部变量 2
复制代码 图 2. 整数加法运算 Java 栈行为
此外,对于 JVM,还需了解支持的数据类型和它们占用的空间:
图 3. Java 数据类型
|