JVM基础及结构
2017年02月28日

一、初识JVM

    本章讲述JVM概念、发展历史等。

1、虚拟机(Virtual Machine)概念

    JRE是由Java API和JVM组成的。JVM的主要作用是通过Class Loader来加载Java程序,并且按照Java API来执行加载的程序。

    虚拟机是通过软件的方式来模拟实现的机器(比如说计算机),它可以像物理机一样运行程序。设计虚拟机的初衷是让Java能够通过它来实现WORA(Write Once Run Anywhere 一次编译,到处运行),尽管这个目标现在已经被大多数人忽略了。因此,JVM可以在不修改Java代码的情况下,在所有的硬件环境上运行Java字节码。

    Java虚拟机的特点如下:

  • 基于栈的虚拟机:Intel x86和ARM这两种最常见的计算机体系的机构都是基于寄存器的。不同的是,JVM是基于栈的。

  • 符号引用:除了基本类型以外的数据(类和接口)都是通过符号来引用,而不是通过显式地使用内存地址来引用。

  • 垃圾回收机制:类的实例都是通过用户代码进行创建,并且自动被垃圾回收机制进行回收。

  • 通过对基本类型的清晰定义来保证平台独立性:传统的编程语言,例如C/C++,int类型的大小取决于不同的平台。JVM通过对基本类型的清晰定义来保证它的兼容性以及平台独立性。

  • 网络字节码顺序:Java class文件用网络字节码顺序来进行存储:为了保证和小端的Intel x86架构以及大端的RISC系列的架构保持无关性,JVM使用用于网络传输的网络字节顺序,也就是大端

    虽然是Sun公司开发了Java,但是所有的开发商都可以开发并且提供遵循Java虚拟机规范的JVM。正是由于这个原因,使得Oracle HotSpot和IBM JVM等不同的JVM能够并存。Google的Android系统里的Dalvik VM也是一种JVM,虽然它并不遵循Java虚拟机规范。和基于栈的Java虚拟机不同,Dalvik VM是基于寄存器的架构,因此它的Java字节码也被转化成基于寄存器的指令集。

2、JVM发展历史

1.  Java发展史

1996年:SUN JDK 1.0 Classic VM

  • 纯解释运行,使用外挂进行JIT

1997年:JDK1.1 发布

  • AWT、内部类、JDBC、RMI、反射

1998年:JDK1.2 Solaris Exact VM

  • JIT 解释器混合    

  • Accurate Memory Management 精确内存管理,数据类型敏感

  • 提升了GC性能

  • 注:JDK1.2开始,称为Java 2,于是有了J2SE J2EE J2ME 的出现,同时加入Swing Collections。

2000年:JDK 1.3,Hotspot 作为默认虚拟机发布

  • 加入JavaSound

2002年:JDK 1.4【Java真正走向成熟的一个版本】,Classic VM退出历史舞台

  • 加入:Assert  正则表达式  NIO  IPV6  日志API  加密类库

2004年:JDK 1.5【至关重要的版本】。即 JDK5 、J2SE 5 、Java 5

  • 泛型、注解、枚举、自动装箱、可变长参数、Foreach循环

2006年:JDK 1.6发布,JDK6

  • 脚本语言支持、JDBC 4.0、Java编译器 API

2011年:JDK7发布

  • G1(全新的GC收集器)

  • 动态语言增强

  • 64位系统中的压缩指针

  • NIO 2.0

2014年:JDK8发布【重要的版本】

  • Lambda表达式

  • 语法增强  Java类型注解

2016年:计划发布JDK9

  • 模块化

2. 重大历史事件

    使用最为广泛的JVM为HotSpot:HotSpot 为Longview Technologies开发,被SUN收购

2006年:Java开源,并建立OpenJDK,

  •   HotSpot  成为Sun JDK和OpenJDK中所带的虚拟机

2008年:Oracle收购BEA

  •     得到JRockit VM

2009年:Oracle公司正式宣布以74亿美金的价格收购Sun公司。  

  •    得到Hotspot

Oracle宣布在JDK8时整合JRockit和Hotspot,将这两款优秀的虚拟机取长补短,最终合二为一。

  •  在Hotspot基础上,移植JRockit优秀特性

3、JVM种类

1. KVM:

  • SUN发布

  • IOS Android兴起之前,广泛用于手机系统

2. CDC/CLDC HotSpot:

  • J2ME的重要组成部分

  • 手机、电子书、PDA等设备上建立统一的Java编程接口

3. JRockit:

  • BEA 

4. IBM J9 VM:

  • IBM内部

5. Apache Harmony:

  • 兼容于JDK 1.5和JDK 1.6的Java程序运行平台

  • 与Oracle关系恶劣,退出JCP ,Java社区的分裂

  • OpenJDK出现后,受到挑战,2011年退役

  • 没有大规模商用经历

  • 对Android的发展有积极作用

4、JVM规范

JVM语言规范主要体现在以下几点:

  •  Class文件类型

  •  运行时数据

  •  帧栈

  •  虚拟机的启动

  •  虚拟机的指令集

此外,JVM需要对Java Library 提供以下支持:(因为这些东西没有办法通过java语言本身来实现)

  •  反射 java.lang.reflect

  •  ClassLoader

  •  初始化class和interface

  •  安全相关 java.security

  •  多线程

  •  弱引用


二、Java字节码(Java bytecode) 和 Class文件

    为了保证WORA,JVM使用Java字节码这种介于Java和机器语言之间的中间语言。字节码是部署Java代码的最小单位。

参见:http://www.importnew.com/1486.html

参见:http://localhost:9500/zoa/collect/#?id=z449aa

三、JVM结构

    JVM本身是一个规范,所以可以有多种实现,除了Hotspot外,还有诸如Oracle的JRockit、IBM J9也都是非常有名的JVM。通常我们基于Hotspot虚拟机来讲。

    JVM的结构 主要由类加载器、运行时数据区(也叫做内存区)、执行引擎、本地方法接口等4部分组成,有点类似于操作系统的结构。如下图所示:

jvm2.png

1543311151403010280.png

jvm3.png

主要包括4部分:

1. 类加载器:在JVM启动时或者在类运行时将需要的class加载到JVM中。

2. 执行引擎:负责执行class文件中包含的字节码指令;

3. 本地方法接口:主要是调用C或C++实现的本地方法及返回结果。

4. 内存区(也叫运行时数据区):是在JVM运行的时候操作所分配的内存区。

生动易懂的说明,参见:http://blog.csdn.net/yfqnihao/article/details/8289363

四、运行时数据区(Runtime Data Areas)

运行时内存区包含如下 5个部分:

  1. The pc Register(程序计数器,Program Counter Register)

  2. JVM Stacks(虚拟机栈,包含很多 “栈帧Frame”)

    —— 在JVM Spec v1.0版本中 叫做 “Java Stack”

  3. Heap(堆)

  4. Method Area(方法区)

    方法区包括:Run-Time Constant Pool(运行时常量池)

  5. Native Method Stacks(本地方法栈)


由于这部分内容比较多,单独开了一篇,见《JVM运行时数据区》

五、其他

1、对象的内存布局

    在 Hotspot 虚拟机中,对象在内存中的布局可以分为3块区域:对象头、实例数据和对齐填充。

    Hotspot虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的自身运行时数据(哈希码、GC分代年龄、锁状态标志等等),另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。

    实例数据部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。

    对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

2、对象的访问定位

    建立对象就是为了使用对象,我们的Java程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式有虚拟机实现而定,目前主流的访问方式有使用句柄和直接指针两种:

1)句柄

    如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。

2)直接指针

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

    这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。