跳至主要內容

一、初识 JVM

安图新大约 6 分钟

一、初识 JVM

一、什么是 Java 虚拟机

虚拟机:指以软件的方式模拟具有完整硬件系统功能、运行在一个完全隔离环境中的完整计算机系统 ,是物理机的软件实现。常用的虚拟机有 VMWare,Visual Box,Java Virtual Machine(Java 虚拟机,简称 JVM)

Java 程序的跨平台特性主要是指字节码文件可以在任何具有 Java 虚拟机的计算机或者电子设备上运行,Java 虚拟机中的 Java 解释器负责将字节码文件解释成为特定的机器码进行运行。因此在运行时,Java 源程序需要通过编译器编译成为.class 文件。

 
 

Java 虚拟机根本不关心运行在其内部的程序到底是使用何种编程语言编写的,它只关心“字节码”文件。

虚拟机的启动:

Java 虚拟机的启动是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成的,这个类是由虚拟机的具体实现指定的。

虚拟机的退出,有如下的几种情况:

  • 某线程调用 Runtime 类或 System 类的 exit 方法,或 Runtime 类的 halt 方法,并且 Java 安全管理器也允许这次 exit 或 halt 操作。
  • 程序正常执行结束
  • 程序在执行过程中遇到了异常或错误而异常终止
  • 由于操作系统出现错误而导致 Java 虚拟机进程终止

JVM 中的数据类型:

 
 

Java 语言中的所有基本类型同样也都是 Java 虚拟机中的基本类型。但是 boolean 有点特别,虽然 Java 虚拟机也把 boolean 看做基本类型,但是指令集对 boolean 只有很有限的支持,当编译器把 Java 源代码编译为字节码时,它会用 int 或者 byte 来表示 boolean。在 Java 虚拟机中,false 是由整数零来表示的,所有非零整数都表示 true,涉及 boolean 值的操作则会使用 int。另外,boolean 数组是当做 byte 数组来访问的,但是在“堆”区,它也可以被表示为位域。

Java 虚拟机还有一个只在内部使用的基本类型:returnAddress,Java 程序员不能使用这个类型,这个基本类型被用来实现 Java 程序中的 finally 子句。该类型是 jsr, ret 以及 jsr_w 指令需要使用到的,它的值是 JVM 指令的操作码的指针。returnAddress 类型不是简单意义上的数值,不属于任何一种基本类型,并且它的值是不能被运行中的程序所修改的。

Java 虚拟机的引用类型被统称为“引用(reference)”,有三种引用类型:类类型、接口类型、数组类型,它们的值都是对动态创建对象的引用。类类型的值是对类实例的引用;数组类型的值是对数组对象的引用,在 Java 虚拟机中,数组是个真正的对象;而接口类型的值,则是对实现了该接口的某个类实例的引用。还有一种特殊的引用值是 null,它表示该引用变量没有引用任何对象。

二、JVM 的整体结构

1、简图

 
 

这个架构可以分成三层看:

  • 最上层:javac 编译器将编译好的字节码 class 文件,通过 java 类装载器执行机制,把对象或 class 文件存放在 jvm 划分内存区域。
  • 中间层:称为 Runtime Data Area,主要是在 Java 代码运行时用于存放数据的,从左至右为方法区(永久代、元数据区)、堆(共享,GC 回收对象区域)、栈、程序计数器、寄存器、本地方法栈(私有)。
  • 最下层:解释器、JIT(just in time)编译器和 GC(Garbage Collection,垃圾回收器)

方法区、堆,由所有线程共享:

  • 堆(Heap):存放所有程序在运行时创建的对象,可以认为 Java 中所有通过 new 创建的对象的内存都在此分配,Heap 中的对象的内存需要等待 GC 进行回收。堆是 JVM 中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了 new 对象的开销是比较大的。
  • 方法区(Method Area):存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为 final 类型的常量、类中的 Field 信息、类中的方法信息,当开发人员在程序中通过 Class 对象中的 getName、isInterface 等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在一定的条件下它也会被 GC,当方法区域需要使用的内存超过其允许的大小时,会抛出 OutOfMemory 的错误信息。

程序计数器、本地方法栈、虚拟机栈,由线程独享 :

  • 程序计数器(ProgramCounter):每一个线程都有它自己的 PC 寄存器,也是该线程启动时创建的。PC 寄存器的内容总是指向下一条将被执行指令的饿地址,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。若 thread 执行 Java 方法,则 PC 保存下一条执行指令的地址。若 thread 执行 native 方法,则 PC 的值为 undefined。
  • 本地方法栈(Native Method Stack):java 调用非 java 代码的接口,存储 native 方法进入区域的地址。
  • 虚拟机栈:虚拟机栈是一个后入先出的栈。每创建一个线程,虚拟机就会为这个线程创建一个虚拟机栈,虚拟机栈表示 Java 方法执行的内存模型,每调用一个方法就会为每个方法生成一个栈帧(Stack Frame),用来存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法被调用和完成的过程,都对应一个栈帧从虚拟机栈上入栈和出栈的过程。虚拟机栈的生命周期和线程是相同的。

2、详细图

 
 

三、常见问题

1、为什么不把基本类型放堆中?

1、 首先是栈、堆的特点不同(堆比栈要大,但是栈比堆的运算速度要快堆存储,栈运行);
2、 将复杂数据类型(引用类型)放在堆中的目的是为了不影响栈的效率,而是通过引用的方式去堆中查找(八大基本类型的大小创建时候已经确立大小三大引用类型创建时候无法确定大小);
3、 简单数据类型比较稳定,并且它只占据很小的内存,将它放在空间小、运算速度快的栈中,能够提高效率;

上次编辑于:
贡献者: Andy