什么是Java虚拟机?
Java虚拟机是一种能够运行Java字节码的虚拟机,以堆栈结构机器来进行实际工作的。具有自己完善的硬件架构,例如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM屏蔽了与具体操作系统平台相关的信息(一次编译,到处运行),使得Java程序只需生成在Java虚拟机上运行的目标代码(即字节码),就能够在多种系统平台上不加修改的运行。通过CPU所执行的软件实现,实现能执行编译过的Java代码。
JVM内存模型
Java虚拟机在执行Java程序的过程中,会把它所管理的内存划分成若干个不同的数据区域。不同的区域都有各自的用途,以及创建和销毁的时间,有的区域会随着虚拟机进程的启动而创建,有些区域则以来用户县城的启动和结束来创建和销毁。我们着重探讨Java虚拟机运行时数据区域,其结构如图所示:
在上图中我们可以看到,JVM运行时数据区主要分为5个部分
- 方法区
- 堆
- 虚拟机栈
- 本地方法栈
-
程序计数器
其不同区域在多线程的情况下对线程是否共享也存在不同类别,分为线程共享数据区,线程隔离数据区。
运行时数据区
线程共享数据区(Java堆、方法区)
Java堆(Heap)
对于绝大多数应用来说,Java堆是虚拟机所管理的内存中最大的一块区域,它是被所有线程所共享的一块内存区域。在虚拟机启动的时候创建,所有的Java对象实例均存放于此区域。在Java虚拟机规范中的描述是:所有的对象实例以及数据均在堆上进行分配。同时它也是垃圾收集器管理的主要区域,因此很多时候又被称为"GC堆"。
- 从内存回收角度来看
由于现在垃圾收集器基本采用分代收集算法,堆还可细分为:新生代、老年代;再细致一点有Eden空间、From Survivor空间、To Surivor空间等。 - 从内存分配角度来看
作为线程共享的堆可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Bufferm,TLAB)
无论堆如何划分,存储的都是对象的实例或数组。
根据Java虚拟机规范的规定,堆可以处于物理上不连续的内存空间中,但是逻辑上必须是连续的。堆在Java虚拟机的实现上,既可以固定大小,也是可以动态拓展的(通过-Xmx和-Xms控制)。如果在堆中没有内存可以完成实例对象的分配,且堆也无法再进行拓展,将会抛出OOM(OutOfMemoryError)异常。
总结:Java堆存放的都是对象实例或是数组,其大小可固定也可拓展(通过-Xmx和-Xms进行控制),若内存不够分配对象或拓展,抛出OOM异常。
方法区(Method Area)
方法区和堆一样,也是所有线程共享的数据区域。主要存储加载的类信息、常量、静态变量以及即时编译器编译后的代码等数据。
很多人把方法区称之为"永久代",其实本质上两者并不等价。相对而言,虚拟机在进行垃圾收集时在这个区域出现较少而已,并不是说数据进入了方法区它就会永久存在。
同堆一样,当方法区内存无法满足分配时,同样也会抛出OOM异常。
总结:方法区主要存储加载的类信息、常量、静态变量以及即时编译器编译后的代码,当无法满足内存分配的需求时,会抛出OOM异常。
线程私有数据区(虚拟机栈、本地方法栈、程序计数器)
虚拟机栈(VM Stack)
虚拟机栈描述的是Java方法执行的内存模型,虚拟机栈是线程私有的,其生命周期同线程相同。任何一个方法再执行的时候都会创建一个栈帧(Stack Frame),主要存放局部变量、操作数栈、动态链接、方法出口等信息。一个方法从开始执行到执行结束都对应着一个栈帧在虚拟机栈中的入栈到出栈过程。
局部变量变量表存储编译期可知的基本数据类型(boolean、int、float、double、byte、char、long、short)、对象引用(refrence类型)、returnAddressl类型。(double和long类型会占用2个局部变量空间(slot),其余类型只占一个)。
在虚拟机栈中会存两种异常:
- StackOverflowError:当线程请求的栈的深度大于虚拟机所允许的深度时,则会抛出此类异常。
- OutOfMemoryError:若虚拟机栈配置为可动态拓展,当拓展时候无法再申请到可用内存的时候则会抛出此类异常。
总结:**虚拟机栈主要存放局部变量表、操作数栈、方法出口等信息。存在两种异常,StackOverflowError和OutOfMemoryError。
本地方法栈(Native Method Stack)
本地方法栈与虚拟机栈的作用十分相似。不同在于本地方法栈执行的虚拟机使用到的Native方法服务。而虚拟机栈则是调执行Java方法。
Native方法
一个Native方法其实指的是一个Java程序调用非Java代码的接口,其实现语言为非Java代码。Native方法可以简单理解为Java需要调用到操作系统层面的方法。
相同之处在于本地方法栈也会存在StackOverflowError和OutOfMemoryError两种异常。
程序计数器(Program Counter Register)
程序计数器在虚拟机中是一块较小的内存区域。可以看作为当前线程说执行的字节码的行号指示器。程序计数器是线程私有的,在Java虚拟机中,多线程是通过线程的轮流切换来分配处理器实现的,在任何一个确定的时刻,一个处理器只会执行一个线程(对于多核处理器来说是一个内核),因此,为了保证线程切换后能够恢复到正常的位置,每一个线程都会有其独立的程序计数器。各个线程之间的程序计数器不会相互影响,独立存储。
若当前线程执行的是Java方法,则计数器记录的为当前正在执行的虚拟机字节码的指令地址,若执行的是Native方法,那么程序计数器是空。
该内存区域是唯一一个没有OutOfMemoryError异常的区域。
总结:程序计数器为线程似有,且不会存在OutOfMemoryError异常。对于Native方法,计数器为空。
通过上面,了解了虚拟机运行时数据区的内存划分以及每个内存区域的大致特性。在最后,对各个区域进行总结如下图所示:
文章仅作为学习记录,可能BUG百出,还望大佬拍砖指点,以便改正,多谢。