JVM 简介与运行时数据区

我也不知道为什么还要写一篇这个文章,毕竟这种文章一搜一大堆,可能只是为了让这个栏目尽可能完整吧,也可能是为了自己加深印象,其实是强迫症发作。其实只要知道jvm运行时数据区划分就可以略过这篇文章。

在学习Java之初,我们肯定会先下载安装JDK,然后写我们的Hello,World程序,运行程序。我们也了解了关于java的三个重要的名词:JDK,(Java Development Kit)是Java语言的软件开发工具包,JRE,(java runtime environment) 是运行JAVA程序所必须的环境集合,包含JVM标准实现及Java核心类库,jvm(java virtual machine)是Java虚拟机,也是我们下面要详细介绍的内容。其中JDK包含有JRE,JRE则包含有JVM。如下图为官方JDK的图解:

JVM 简介与运行时数据区

Java 虚拟机是整个 Java 平台的基石,是 Java 技术用以实现硬件无关与操作系统无关的关键部分,Java 虚拟机可以看作是一台抽象的计算机。如同真实的计算机那样,它有自己的指令集以及各种运行时内存区域。Java虚拟机在在执行Java程序的过程中会将他所管理的运行时内存区域划分为若干个不同的数据区域,这些区域有各自的用途,以及创建和销毁时间。有的区域随着虚拟机进程的启动而存在,有的区域则依赖用户线程的启动和结束而创建和销毁。多数情况下,Java所管理的内存分为以下几个区域:

JVM 简介与运行时数据区

程序计数器:

程序计数器也被称为PC寄存器,首先,它是线程私有的。每个线程启动的时候,都会创建一个PC(Program Counter,程序计数器)保存当前正在执行的JVM指令的地址,用于指示当前线程所执行的字节码执行到了第几行,也可以理解为是当前线程的行号指示器。字节码解释器在工作时,会通过改变这个计数器的值来取下一条语句指令。因为它是线程私有的,每个程序计数器只用来记录一个线程的行号。如果程序执行的是一个Java方法,则计数器记录的是正在执行的虚拟机字节码指令地址;如果正在执行的是一个本地(native,由C语言编写 完成)方法,则计数器的值为Undefined。该部分是唯一不存内存溢出的内存区域。

虚拟机栈:

每个线程对应着一个虚拟机栈,因此虚拟机栈也是线程私有的;一个线程的每个方法在执行的同时,都会创建一个栈帧(Statck Frame),栈帧中存储的有局部变量表、操作站、动态链接、方法出口等,当方法被调用时,栈帧在JVM栈中入栈,当方法执行完成时,栈帧出栈。线程内Java方法的调用过程就是栈帧不断入栈出栈的过程。后续会对Java方法栈进行详细讲解。

本地方法栈:

本地方法栈也是线程私有的;本地方法栈在作用,运行机制,异常类型等方面都与虚拟机栈相同,唯一的区别是:虚拟机栈是执行Java方法的,而本地方法栈是用来执行native方法的,换句话说,它是用来调用通过JNI调用的C/C++代码。

堆:

在 Java 虚拟机中,堆(Heap)是可供各条线程共享的运行时内存区域,也是供所有类实例和数组对象分配内存的区域,几乎所有的对象实例都在堆上分配。这里我们知道堆上存的是对象就行了,同时是JVM管理的内存中最大的一块,也是垃圾收集器管理的主要区域即可,后续也会对堆进行详细的讲解。

方法区:

方法区也被很多人称为永久代,对于有些虚拟机这种说法并不是十分准确。方法区是各个线程共享的区域,用于存储已经被虚拟机加载的类信息(即加载类时需要加载的信息,包括版本、field、方法、接口等信息)、final常量、静态变量、编译器即时编译的代码等。方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。除此之外,运行时常量池也是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。

直接内存:

直接内存并不是JVM管理的内存,可以这样理解,直接内存,就是 JVM以外的机器内存,比如,你有4G的内存,JVM占用了1G,则其余的3G就是直接内存,JDK中有一种基于通道(Channel)和缓冲区 (Buffer)的内存分配方式,将由C语言实现的native函数库分配在直接内存中,用存储在JVM堆中的DirectByteBuffer来引用。 由于直接内存收到本机器内存的限制,所以也可能出现OutOfMemoryError的异常。