Java虚拟机

Published: by Creative Commons Licence (Last updated: )

Java虚拟机

参考:《深入理解Java虚拟机:JVM高级特性与最佳实践》、《Java虚拟机规范》
Java虚拟机规范英文版 Java Language and Virtual Machine Specifications
Java虚拟机是什么
深入理解Java Class文件格式(一)

对于本书的一个笔记

第一章:走近Java

Java技术体系

Java技术体系包括以下组成部分:

  • Java程序设计语言
  • 各种硬件平台上的Java虚拟机
  • Class文件格式
  • Java API类库
  • 第三方Java类库

JDK是最小开发环境 = Java程序设计语言 + Java虚拟机 + Java API类库

JRE是支持Java程序运行的标准环境 = Java SE API子集 + Java虚拟机

根据所服务的领域来划分:

  • Java Card : 运行在小内存设备(比如智能卡)
  • Java ME(Micro Edition):移动终端
  • Java SE:桌面级应用
  • Java EE:多层架构的企业级应用

Java虚拟机:

  • 1996,JDK提供了一个纯解释执行的Java虚拟机实现(Sun Classic VM)
  • 1999,HotSpot虚拟机发布;它最初是由一家小公司开发,后Sun将其收购。后成了默认虚拟机。
  • 2006,Sun把Java开源,并建立了OpenJDK组织对这些源码进行独立管理。在JDK 1.7中,Sun JDK和OpenJDK代码基本一样。
  • 2009.4,Oracle以74亿美元收购Sun。此时Oracle取得了当前三大商业虚拟机中的两个JRockit和HotSpot;并最终打算将其合二为一,该项目叫做HotRoekit

商用高性能虚拟机: Sun HotSpot、 BEA JRockit、 IBM J9
其他虚拟机: Sun Classic、Sun Exact、Apache Harmony(间接催生了Android的 Dalvik)
嵌入式虚拟机: Dalvik、 …

Java技术未来:

  • 模块化
  • 混合语言:

    除了催生大量新的基于JVM的语言外,许多已有语言也出现了基于JVM实现的版本。
  • 多核并行

第二章:Java内存区域与内存溢出异常

运行时数据区

Java虚拟机管理的内存包括以下几个运行时数据区:

Java虚拟机.jpg

另一个图:

程序计数器

每个线程私有。字节码解释器工作时就是通过改变程序计数器的值来选取下一条需要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。

由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行事件的方式来实现,在某个时刻,一个处理器之后执行一条线程中的指令;因此为了线程换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器。

Java虚拟机栈(方法调用栈)

每个线程私有。它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。

每个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

栈帧由以下3部分组成:

  • 局部变量表:存放局部变量和方法参数。
  • 操作栈:是线程的工作区,用来存放运算过程中生成的临时数据。
  • 栈数据区:为线程执行指令提供相关的信息。包括如何定位到位于堆区和方法区的特定数据,以及如何正常退出方法或者异常中断方法。

栈帧是方法运行期间的基础数据结构。

局部变量表:存放了编译期间可知的各种基本数据类型、对象引用、returnAddress类型(指向一条字节码指令的地址)。

对象引用(reference类型)根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置。

局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的句柄变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

该区域的两种异常:

  • 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常
  • 如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常

本地方法栈

本地方法栈是为虚拟机使用到的Native方法服务。

也会抛出虚拟机栈一样的两个异常。

Java堆

Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。 唯一目的就是存放对象实例

Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。在实现上可以是固定大小或可扩展的;一般都是可扩展的。当堆无法扩展时,会抛出OutOfMemoryError异常。

Java堆是垃圾收集器管理的主要区域,因此也称 GC堆。

方法区

各线程共享。

用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它有个别名叫Non-Heap(非堆)。

会抛出OutOfMemoryError异常。

运行时常量池

它是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池;常量池用于存放编译期生成的各种字面量和符号引用,这部分将在类加载后存放到方法区的运行时常量池中。

运行时常量池 --相对于-- Class文件常量池

前者具备动态性。

会抛出OutOfMemoryError异常。

对象访问

重要

与《Java面向对象编程》第13章:多线程的第一节Java线程运行机制 相互结合 学习。

对于下面的语句:

//一条出现在 方法体 中的语句
Object obj = new Object();

其中Object obj的语义将反映到Java栈本地变量表中,作为一个reference类型数据出现。

new Object()的语义会反映到Java堆中,形成一块存储了Object类型所有实例数据值(Instance Data,对象中各个实例字段的数据)的结构化内存。

另外Java堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。

通过reference类型访问对象的两种主流的实现方式:使用句柄直接指针

使用句柄方式访问: 在Java堆中将会划分出一块内存来作为句柄池,reference类型中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。

reference类型  --->    句柄地址
句柄   ---->   对象实例数据地址  +   类型数据地址

通过句柄访问对象.png

优点:在对象被移动(在垃圾搜集时对象往往会被移动)时只会改变句柄中的实例数据指针,而不需要改变reference本身。

使用直接指针方式访问: 此时Java堆对象的布局就必须考虑如何放置访问类型数据的相关信息,reference中直接存储对象的地址。

通过直接指针访问对象.png

优点:速度快

Sun HotSpot虚拟机,使用直接指针的方式进行对象访问。

《Java面向对象编程》Java线程的运行机制

详情见相关笔记

注意:方法区的内容、程序计数器与方法区之间的关系。 Java示例中运行时数据区的状态.png

### 实战:如何制造相关异常

重要。。。


## 第3章:垃圾收集器与内存分配策略



## 第6章:类文件结构


## 第7章:虚拟机类加载机制


## 第8章:虚拟机字节码执行引擎


## 第9章:类加载及执行子系统的案例与实战