Java虚拟机及运行时数据区

1、JVM定义

Java虚拟机(Java Virtual Machine),简称JVM。当我们说起Java虚拟机时,可能指的是如下三种不同的东西:.

  • 抽象的虚拟机规范
  • 规范的具体实现
  • 一个运行中的虚拟机实例

Java虚拟机抽象规范仅仅是一个概念,在《The Java Virtual Machine Specification》中有详细的描述。

该规范的实现,可能来自多个提供商,并存在于多个平台上,它或者是全部由软件实现,或者是以硬件和软件相结合的方式来实现。JVM的实现有很多,广为使用的主要有三个(由于Sun和BEA被Oracle收购,故HotSpot、JRockit已都为Oracle所有):

  • Sun HotSpot(服务端、桌面、嵌入式 通用)
  • IBM J9(服务端、桌面、嵌入式 通用)
  • BEA JRockit(专注于服务端)
  • 其他:安卓的KVM、微软的Microsoft JVM 等

当运行一个Java程序的时候,也就在运行一个Java虚拟机实例。注意,我们所说的Java平台无关性是指class文件的平台无关性,JVM是和平台相关的,不同操作系统对应不同的JVM。

2、JVM架构

HotSpot虚拟机是官方的、最常用的JVM规范实现,这里以HotSpot虚拟机为例

2.1、HotSpot JVM架构

如图,JVM可以分为几部分:类加载子系统、运行时数据区、执行引擎、本地方法接口/库。

(图片来源:Java Garbage Collection Introduction

更详细的结构:

(图片来源:The JVM Architecture Explained

2.2、HotSpot JVM在Java SE中的位置

(图片来源:Jave SE Platform at a Glance

题外话:

1、Oracle收购Sun后,Oracle JDK从8u211、8u212起许可协议从BCL换成了OTN,该协议要求若将Oracle JDK用于商用则需要收费。为了白嫖,可以用Open JDK,比较流行的有:

  • 阿里巴巴的dragonwell
  • Amazon的Corrett
  • 华为的毕昇JDK
  • 腾讯的Kona
  • IBM的JDK

2、Oracle JDK下载(Oracle改变了JDK的下载方式,如隐藏较深、需要登录等,下载越来越麻烦,故记于此),可参阅 此文章

从Oracle官网(https://www.oracle.com/cn/java/technologies/javase-downloads.html)下载:需要登录,可下载所有版本的JDK、JRE。

从java官网(https://www.java.com/zh-CN/download/)下载:无需登录、只能下载JRE 8版本、只能下载JRE。实际上背后也是从Oracle官网下的。

 

 

3、JVM运行时数据区

JVM规范定义了若干种程序运行时使用到的运行时数据区:

 分为两种,一种随虚拟机的启动和退出而创建和销毁(方法区、堆),一种随线程的创建和销毁而创建和销毁(虚拟机栈、本地方法栈、程序计数器):

  • 方法区:类信息(版本、字段、方法、接口等描述信息,类的Class对象也存在此)、常量、静态变量、即时编译后的代码、运行时常量池(存放编译器生成的字面量和符号引用,运行过程动态放入的常量等)等。(OutOfMemoryError异常)
  • 堆:实例对象(及数组)。注意不是所有对象,如上面所说类的Class对象存放在方法区。(OutOfMemoryError异常)
  • 栈(HotSpot对虚拟机栈和本地方法栈不加以分区,二者合二为一)::存放线程执行产生的栈帧信息(OutOfMemoryError异常、StackOverFlow异常)
    • 虚拟机栈(线程栈): 局部变量表(编译期可知的基本数据类型、引用类型、returnAddress)、操作数栈、动态链接、方法出口信息等
    • 本地方法栈:与虚拟机栈类似
  • 程序计数器:所属线程下一条要执行的指令的地址。(没有规定异常)
方法区、堆区、栈区 均可实现成固定大小容量或实现成可动态扩展容量的。
对象主要三种:Class对象、数组对象、普通实例对象,并不都是在堆区。
 

3.1、程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,它可以看做是当前线程所执行的字节码的信号指示器。若线程正在执行的是一个Java方法,则程序计数器保存的是当前所在线程下一条要执行的字节码指令的地址;若正在执行的是Native方法,则计数器值为空(undefined)。

线程私有,每个JVM线程都有自己的程序寄存器。

此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

3.2、Java虚拟机栈

Java虚拟机栈(Java Virtual Machine Stack)即线程运行栈,描述的是Java方法执行的内存模型:每个方法被执行时会同时创建一个栈帧(Stack Frame)用于局部变量表(存编译期可知的基本数据类型、引用类型、returnAddress)、操作数栈、动态链表、方法出口信息等。每一个方法被调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

线程私有,每一个JVM线程都有自己的java虚拟机栈,这个栈与线程同时创建,它的生命周期与线程相同

JVM stack 可以实现成固定大小,也可以实现成可动态扩展的。若实现成固定大小,则每一条线程的JVM Stack容量应该在线程创建时就指定了大小,JVM实现应该提供调节JVM Stack初始容量的手段;若实现成可动态扩展的方式,则应该提供调节最大、最小容量的手段。

能创建的栈的数量(即线程的数量)受所在机器物理内存的限制,但实际上OS对一个进程能使用的内存大小(Windows 2GB)是有限制的,故能创建栈的数量受OS的这个限制。换句话说,JVM并没有支持调节各栈能用的总内存而是只提供了对一个栈大小的调节。

两种Error(两者实际上有重叠):

StackOverflowError:纵向无法分配,即无法分配新的栈帧时 抛出此异常。

OutOfMemoryError:横线无法分配,超过了OS对一个进程能使用的内存大小的限制。两种情况:

不能满足内存分配(栈大小固定的情形)——在创建新线程时没有足够内存来创建对应的虚拟机栈(也即无法建立新的线程)时;

不能满足内存扩展(栈大小可动态扩展的情形)——JVM Stack可以动态扩展但在尝试扩展时无法申请到足够的内存来完成扩展时。

3.3、本地方法栈

Java虚拟机可能会使用到传统的栈来支持native方法(使用Java语言以外的其它语言编写的方法)的执行,这个栈就是本地方法栈(Native Method Stack)。

线程私有。

本地方法栈发挥的作用与Java虚拟机栈的相似。有的虚拟机(如HotSpot)不对两者加以区分,合二为一

3.4、方法区

方法区(Method Area)用于存储已被虚拟机加载的类信息(Class对象、版本、字段、方法、接口等描述信息)、常量、静态变量、即时编译后的代码、运行时常量池等。

各线程共享。

方法区的容量可以实现成固定大小的,也可以实现成可动态扩展的,并在不需要过多空间时候自动收缩。

方法区可以处于物理上不连续的内存空间中,只要逻辑连续即可。

OutOfMemoryError异常: 与线程栈类似,两种情况会抛出此异常:

不能满足内存分配(方法区大小固定的情形)——方法区的内存空间不能满足内存分配请求时;

不能满足内存扩展(方法区大小可动态扩展的情形)——方法区可动态扩展但无法申请到足够空间完成扩展时。

运行时常量池:是方法区的一部分,存放编译器生成的各种字面量和符号引用

.java文件编译后生成的各种字面量和符号引用放在.class文件的常量池中,这部分内容在类加载后进入方法区的运行时常量池。

与.class文件常量池相比,运行时常量池具备动态性:不要求常量只有在编译期才能产生,即并非预置入.class文件中常量池的内容才能进入运行时常量池,运行期间也可将新常量放入运行时常量池,如String的intern()方法(见Java小记-MarchOn)。

OutOfMemoryError异常:当创建类和接口时,如果构造运行时常量池所需的内存空间超过了方法区所能提供的最大内存空间后就会抛出此异常。

注:方法区/永久代/元空间:

方法区常又被称为永久代(PermGen space)、元空间(Metaspace),它们并不等价,区别是:方法区为JVM的规范,永久代、元空间是方法区在HotSpot中的一种实现;对于其他类型的虚拟机实现,如 JRockit(BEA)、J9(IBM),其并没有永久代、元空间这些概念,其只要不触碰到进程可用内存上限即可。

    • 从JDK1.7开始,逐渐开始去永久代工作:JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。(如符号引用(Symbols)转移到了native heap;字面量(interned strings)、类的静态变量(class statics)转移到了java heap);
    • 从JDK1.8起,改用本地内存实现方法区,称为元空间。元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制(本地内存大小则受OS对进程最大内存的限制)。

3.5、Java堆

Java堆是几乎所有对象(有例外,如类的Class对象置于方法区)实例分配内存的区域。堆在虚拟机启动的时候被创建,堆中储存了各种对象,这些对象被自动内存管理系统(Automatic Storage Management System,也即是常说的“Garbage Collector(垃圾回收器)”)所管理。这些对象无需、也无法显式地被销毁。

各线程共享。

堆的容量可以实现成固定大小的,也可以实现成可动态扩展的,并在不需要过多空间时候自动收缩。

Java堆可以处于物理上不连续的内存空间中,只要逻辑连续即可。

OutOfMemoryError异常: 与线程栈类似,两种情况会抛出此异常:

不能满足内存分配(堆大小固定的情形)——若在堆中没有足够内存完成实例分配;

不能满足内存扩展(堆大小可动态扩展的情形)——堆大小可动态扩展但无法申请到足够空间完成扩展。

 

3.6、堆外内存/本地直接内存(不属运行时数据区)

本地直接内存(Direct Memory)不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范定义的内存区域。但也可被使用,而且也可能出现OutOfMemoryError异常。其不受堆大小的限制,但受到本机进程可用内存的限制。

使用堆外内存的原因:

  • 对垃圾回收停顿的改善。由于堆外内存是直接受操作系统管理而不是JVM,所以当我们使用堆外内存时,即可保持较小的堆内内存规模。从而在GC时减少回收停顿对于应用的影响。
  • 提升程序I/O操作的性能。通常在I/O通信过程中,存在堆内内存到堆外内存的数据拷贝操作,对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据,都建议存储到堆外内存。

4、JVM各区域内存分配参数设置

上述各区域内存大小的配置可参看 HotSpot JVM各区域内存分配参数设置-MarchOn

5、参考资料

http://chenzhou123520.iteye.com/blog/1585224,(其即参考自《深入理解Java虚拟机——JVM高级特性与最佳实践》)

 

posted @ 2016-12-31 15:33  March On  阅读(575)  评论(0编辑  收藏  举报
top last
Welcome user from
(since 2020.6.1)