JVM-1:内存结构

一、 瞎掰

最近在系统地梳理Java虚拟机的知识,想想还是把自己的一些想法记录下来,不然这些所谓的理解迟早都会被懒惰的大脑神经元全部抛弃。尽管记录了也会抛弃,但至少为自己保存了一个搜寻的线索。

二、JVM内存结构概述

首先需要指出的是文章是从HotSpot JVM出发的,Java虚拟机家族有众多虚拟机版本,感兴趣的可以自行查阅相关资料。讲的是JVM内存结构,更确切地说是JVM的运行时数据区结构。JVM的运行时数据区包含了多个内存区域 ,先来看图。


JVM-1:内存结构_第1张图片
JVM run time areas

上图图源

JVM的多个内存区域既包括了线程私有的区域,也包括了线程共享的区域。如上图所示,线程私有的内存区域有:

  • PC Register(程序计数器)
  • JVM Stack(Java虚拟机栈)
  • Native Method Stack(本地方法栈)

线程共享的内存区域有:

  • Heap(堆)
  • Method Area(方法去)
    • Runtime Constant Pool(运行时常量区)

三、线程私有内存

1. 程序计数器:PC Register

这部分内存的目的是为了记录当前线程执行的字节码行号。结合操作系统的寄存器(Register)就不难理解这部分内存的功能,不要被翻译程序计数器带跑偏,当然为了语言统一我还是会继续使用程序计数器。程序计数器说白了就是 程序运行追踪器,记录线程运行到了字节文件(Class文件)的什么位置。

值得注意的是,这部分内存并不会记录Native方法,当Native方法执行时,计数器值为空。

2. Java虚拟机栈:JVM Stack

这部分内存区域和堆(Heap)应该是我们最为熟悉也最为关注的内存区域了。该内存区域为线程私有,所以它和线程有着一样的生命周期。每个方法(method)在运行时,都会在虚拟机栈中拥有一个自己的栈帧(Stack Frame),栈帧随着方法的执行入栈,随着方法执行结束出栈。栈帧中存有如下数据:

  • 局部变量表
  • 操作数栈
  • 动态链接
  • 方法出口

其中局部变量表在栈帧中的大小是在编译期间就可以确定的,并且这局部变量表的大小在方法运行期间不会被改变。局部变量表当中存放有编译期可知的各种基本数据类型:boolean, byte, char, short, int, float, long, double, 以及对象引用(Reference类型)。这部分两个比较另类的基本数据类型是longdouble,因为两个数据类型都是64位的,在变量表中会占用两个局部变量空间(slot)。但是在所以的时候只使用第一个是空间的值,例如,一个long型数据占用了第3,第4个slot,那寻址的时候只需要使用第3个slot的地址。

因为局部变量表的大小在编译时间就已经确定,所以局部变量表在运行时不会产生StackOverFlowError异常和OutOfMemoryError异常。但是虚拟机栈会产生以上两个异常,主要是基于以下原因:

    1. 线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverFlowError异常
    1. 虚拟机无法给扩展的栈申请到足够的内存,抛出OutOfMemoryError异常

3. 本地方法栈:Native Method Stack

要理解这部分内存区域首先要理解Native Method。本地方法(Native Method) 指的是Java语言中调用的其他语言,简而言之这部分内存是给非Java语言的代码使用的。在HotSpot虚拟机中,这部分内存其实是和虚拟机栈合二为一的。

四、线程共享内存

1. 方法区:Method Area

方法区用于存放已经被虚拟机加载了的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区在HotSpot中的具体实现可以参考深入理解Java虚拟机 第二版2.2.5,这里不再赘述。

同时,方法区中还拥有运行时常量池(Runtime Constant Pool)。该部分内存用于存放编译期生成的各种字面量和符号引用,通常这些内容在相应的类被加载后放入运行时常量池。

2. 堆:Heap

Java堆是Java虚拟机管理的最大一块内存区域,也是内存收集器的主要活动场所。该内存区域存在的唯一目的就是存放对象实例,几乎所有对象都被存放在这里。请注意,这里是几乎,具体的介绍请看Hollis的对象并不一定都是在堆上分配内存的。

因为Java堆是GC活动的主要场所,涉及到相关的垃圾回收机制这里也没法全面概述。前段时间刚好看了一篇介绍Java垃圾回收机制的教程,点击阅读不收费。其中讲述了堆中内存的具体分配,更新迭代,看完一定能堆垃圾回收有一个初步的了解。此外,Java是如何在堆中为对象分配内存、又如何从堆中获取对象都是相对复杂的操作,无法在这一篇概述中详细说明,在深入理解Java虚拟机 第二版2.3中有详细的介绍,翻一下不浪费时间:)。

五、总结

个人认为深入理解JVM内存对现实编程有巨大帮助,比如帮助理解StackOverFlowError等异常,提高编码水平,通过调整虚拟机的内存获取更高的性能等等。这些都可以作为另外一片文章讲解,因为这篇文章只是概述JVM内存的相关结构,具体的如何对内存错误定位,如何通过分配内存提高虚拟机性能等下一篇文章再总结吧。私以为直接内存并不属于JVM管理的内存,也就不再赘述。

六、参考资料

  • Java Garbage Collection Basics
  • 深入理解Java虚拟机 第二版
  • 对象并不一定都是在堆上分配内存的
  • Java虚拟机的内存组成以及堆内存介绍
  • 从0到1起步-跟我进入堆外内存的奇妙世界
  • Java 内存之方法区和运行时常量池

你可能感兴趣的:(JVM-1:内存结构)