JVM(一)- 组成部分及详解

一、JVM简介

JVM全称Java Virtual Machine ,Java虚拟机,也就是在计算机上虚拟一个计算机,且运行于内存中。计算机的基本构成:运算器、控制器、存储器、输入和输出设备,JVM也有同样成套的元素。为了达到“一次编译,随处运行”,JVM根据不同的CPU,翻译成不同的机器语言,因此java的命令集随处都可以运行。

1、 JVM与计算机的交互

JVM与计算机的交互如图:
在这里插入图片描述
从图中可以看出,JVM是运行在操作系统之上的,它与硬件没有直接交互。

JVM的种类

在这里插入图片描述
如图所示:

  • 灰色部分的虚拟机基本已经退出市场,
  • 红色部分的两款虚拟机运行于特定的硬件
  • 绿色部分由sun公司开发的两款虚拟机,均用于移动设备;另外两款由Apache和Google公司研发的两款虚拟机,并不能叫做“java虚拟机”,其中Dalvik VM目前是Android平台的核心组成部分。
    想了解详细信息的,详见《深入理解java虚拟机》周志明著 第1章 1.4java虚拟机发展史

HotSpot VM & Open JDK & Oracle JDK

  • HotSpot VM:在2006年的JavaOne大会上,Sun公司宣布最终会把Java开源,并在随后的一年,陆续将JDK的各个部分(其中当然也包括了HotSpot VM)在GPL协议下公开了源码,
    并在此基础上建立了OpenJDK。这样,HotSpot VM便成为了Sun JDK和OpenJDK两个实现极度接近的JDK项目的共同虚拟机。
  • 在2008年和2009年,Oracle公司分别收购了BEA公司和Sun公司,这样Oracle就同时拥有了两款优秀的Java虚拟机:JRockit VM和HotSpot VM。
    以后本文提到内容,基于HotSpot VM探讨。

二、JVM组成

JVM主要分为四个部分,ClassLoader(类装载器)、Execution Engine 执行引擎、Native Interface 本地接口、Runtime data area 运行数据区。
在这里插入图片描述

1、类装载器

类装载器的作用是加载通过javac编译的class文件到内存中。Class Loader 加载的class 文件是有格式要求,在《JVM Specification 》中式这样定义Class 文件的结构:

ClassFile {
      u4 magic;
      u2 minor_version;
       u2 major_version;
      u2 constant_pool_count;
      cp_info constant_pool[constant_pool_count-1];
      u2 access_flags;
      u2 this_class;
      u2 super_class;
      u2 interfaces_count;
      u2 interfaces[interfaces_count];
      u2 fields_count;
      field_info fields[fields_count];
      u2 methods_count;
      method_info methods[methods_count];
      u2 attributes_count;
      attribute_info attributes[attributes_count];
    }

注:Class Loader 只管加载符合文件结构的class文件,至于说能不能运行,则不是它负责的,那是由Execution Engine 负责的。
类的加载原理及过程会在后续细化详见link.

2、Execution Engine 执行引擎

执行引擎也叫作解析器(Interpreter),负责解析命令,提交操作系统执行。

3、Native Interface 本地接口

本地接口的作用是融合不同的编程语言为Java 所用,初衷是融合C/C++ 程序。Java 诞生的时候是C/C++ 横行的时候,要想立足,必须有一个聪明的、睿智的调用C/C++ 程序,于是就在内存中专门开辟了一块区域处理标记为native 的代码。具体做法是Native Method Stack 中登记native 方法,在Execution Engine 执行时加载native libraies(本地方法库) 。
目前该方法使用的是越来越少了,除非是与硬件有关的应用,比如通过Java 程序驱动打印机,或者Java 系统管理生产设备,在企业级应用中已经比较少见,因为现在的异构领域间的通信很发达,比如可以使用Socket 通信,也可以使用Web Service 等等,不多做介绍。

4、Runtime data area 运行时数据区

运行数据区是整个JVM的重点,java虚拟机在执行java程序的过程中会把他所管理的内存划分为若干个不同的数据区域:Method Area(方法区)、VM stack(虚拟机栈)、Native Method Stack(本地方法栈)、Heap(堆)、Program Counter Register(程序计数器)。
在这里插入图片描述

(1)Program Counter Register(程序计数器)

  • 占用较小的内存空间,
  • 线程私有,生命周期与线程相同。每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储;
  • 当前线程执行的字节码的行号指示器,字节码解析器通过改变计数器的值来选择下一条需要执行的字节码指令;
  • 分支、循环、跳转、异常处理、线程恢复都依赖计数器来完成;
  • 唯一 一个在java虚拟机规范中没有规定任何OutOfMemeryError情况的区域;

(2)VM Stack(java虚拟机栈)

  • 线程私有,生命周期与线程相同,不存在垃圾回收,线程结束,内存释放
  • 每个方法在执行的同时会创建一个栈帧(方法运行时的基础数据结构);
  • 线程请求的栈深度超过虚拟机允许的最大深度,会抛出StackOverFlowError异常;
  • java虚拟机规范中运行固定长度的虚拟机栈;
  • 虚拟机栈允许动态扩展,扩展时申请不到足够的内存,则会抛出OutOfMemoryError异常;
  • 栈帧用来存储:局部变量表、操作数栈、动态链接、方法出口等;
    • 局部变量表:编译器可知的基本数据类型、对象引用、returnAddresss。所需内存在编译器完成分配,运行期间不会改变;
      • 基本数据类型:Boolean 、byte、short、char、int、float、long、double,64位长度的double和long占两个局部变量的空间(Slot),其余占一个;
        
      • 对象引用:可能指向一个对象的起始地址的引用指针、也可能指向一个代表对象的句柄或者于此对象相关的位置
        
      • returnAddress:指向一条字节码指令的地址;
        

(3)Native Method Stack(本地方法栈)

  • 登记native方法,在Execution Engine执行引擎执行时,通过Native Interface 本地接口调用已登记的native library;
  • 为虚拟机使用的Native方法服务,简单来说就是一个java调用非java代码。
  • java需要与一些底层系统如操作系统或某些硬件交换信息,如调用打印机;

(4)Heap(堆)也被称为“GC堆”

  • 所有线程共享
  • 虚拟机管理的内存中最大的一块。
  • 垃圾收集器管理的主要区域,因此很多时候也被焦作“GC堆”;
  • 存放对象实例,几乎所有的对象实例及数组都在堆中分配内存;

(5)Method Area (方法区)

  • 所有线程共享
  • Non-Heap(非堆);
  • 在HotSpot虚拟机又被称为“永久代”;
  • 存储已被虚拟机加载的类信息、静态变量、常量、即时编译器编译后的代码等数据;
  • 并非数据进入方法区就如“永久代”的名称一样永久存在了,这一区域的回收目标主要是针对常量池的回收和对类型的卸载。

(6)重要:栈和堆区分的原因

  • 从软件设计的角度看,栈代表了逻辑处理,堆代表数据。两者区分使得处理逻辑更为清晰
  • 堆中内存可以被多个栈共享(可以理解为多个线程可以访问同一个对象)
  • 栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于栈基本只能向上增长(占内存较小,所以动态扩展的可用内存有限),也就限制了栈存储内容的能力。而堆不同,堆可以根据需要动态增长。因此栈和堆得划分,也使得动态增长成为可能,相应的栈中只需要记录堆中的一个地址即可

(7)JDK7、JDK8内存模型

在这里插入图片描述
在这里插入图片描述

(8)java 对象内存示例

  • 一个Object对象的大小是8byte,这个大小只是保存在堆中一个没有任何属性的对象大小;
Object object = new Object();
  • 它所占的空间是:4byte+8byte。4byte是上面所说的在java栈中保存引用所需要的空间。而8byte为java在堆中的对象信息;
  • 因为java在对象内存的分配上都是以8的整数倍进行分配,因此大于12byte最接近8的倍数为16,因此该对象的大小为16byte。

JVM(二)- 类的加载原理及过程会在后续细化详见JVM(二)- 类的加载过程、类加载器(付示例代码).
JVM(三)- 垃圾收集器及内存分配策略详见link.


版权声明:本文为wendyyanan原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。