java虚拟机内存分布与使用

虚拟机运行时数据区域分布及使用

博主之前有一年多的时间使用过C++进行编程,内存的管理是个很让人头大的问题,内存的释放需要自己去delete,不然会发生内存泄漏的问题。开发过程当中内存操作不当导致的bug占了很大的比例,更不用说说,在多线程情况下对内存的管理了。自从开始使用Java之后,深感Java虚拟机自动内存管理的便利性。最近重读《深入理解Java虚拟机》一书,对本书内容进行一些记录。首先我们就来看看Java虚拟机的内存分布以及使用。

虚拟机的运行时数据区域

虚拟机运行时数据区域

虚拟机各内存区域的使用

虚拟机分配的内存大致可以分为两个部分,一部分是线程私有的内存区域,一部分是进程内线程共有的内存区域。此外,还有一块直接内存,这部分内存不是虚拟机运行是数据区的一部分,也不是Java虚拟机规范中所定义的内存区域,但是这部分内存也会被使用到,比如NIO时会在该区域进行内存分配。

程序计数器

相信学习过计算机组成原理与汇编知识的读者都知道,在指令的执行过程当中,存在一个计数器,用来指示当前执行到了哪一条指令,其实程序计数器可以看作是当前线程执行的字节码的行号指示器,保存着当前指令的地址。由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定时刻,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,个线程之间程序计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

虚拟机栈

虚拟机栈描述的是Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从调用直到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈和出栈的过程。
在Java虚拟机规范中,对该区域规定了两种异常。如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机可以动态国展,如果扩展是无法申请到足够的内存,就会抛出OutOfMemoryEror异常。

本地方法栈

与虚拟机栈所发挥的作用是非常相似的,只不过虚拟机栈执行为虚拟机执行Java服务,而本地方法栈则为虚拟机使用到的Native方法服务。有的虚拟机(如Sun的HotSpot需积极)直接将本地方法栈和虚拟机栈合二为一。

方法区

方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。该部分内存可以成为“永生区”。根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

运行时常量池

在方法区中,存在一块内存区域——运行时常量池,用于存放编译期间生成的各种字面量和符号引用,这部分内容将在类加载进入方法去的运行时常量池中存放。

堆区

Java对是所有线程共享的一筐内存区域,也是Java虚拟机所管理的内存中最大的一块,所有对象实例及数组都要在对上分配内存。Java堆是垃圾收集器管理的主要区域,根据Java虚拟机规范的规定,Java堆可以处于物理上不联系的内存空间,只要逻辑上是连续的即可。
从内存回收的角度看,Java对中可以细分为:

  • 新生代
    • Eden空间
    • From Survivor空间
    • To Survivor空间
  • 老生代

该部分内容将在下一篇博客《虚拟机垃圾收集算法及垃圾收集器》中介绍

从内存分配的角度看,线程共享的Java队中可能划分出多个线程私有的分配缓冲区(TLAB)。
如果在堆中没有内存完成实例的分配,并且堆无法再扩展时,将会抛出OutOfMemoryError异常。

直接内存

直接内存并不是虚拟及运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。在JDK1.4中加入的NIO类,引入了一种基于通道与缓冲区的I/O方式,它何以通过Native函数库直接分配堆外内存,也就是直接内存部分。

欢迎关注个人公众号:
个人公号