天天看点

Java虚拟机(一) - 内存区域与内存溢出异常

运行时数据区

1、 PC寄存器/程序计数器(Program Counter Register)

概念:

程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。

由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(多核处理器来说是一个内核)都只会执行一条线程中的指令。

为了线程切换后能恢复到正确的执行位置,需要程序计数器记录当前线程运行的字节码指令地址。

每个线程都有一个独立的程序计数器。

线程私有内存:各个线程之间互不影响,独立存储。

程序计数器就属于线程私有内存

异常:

不会有OutOfMemoryError的情况

2、Java虚拟机栈(Java Vrtual Machine Stacks)

类型:线程私有

概念:

描述Java方法执行的内存模型。

每个方法在执行时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

每个方法从调用直至执行完成的过程,对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

  • 局部变量表

    存放:基本数据类型、对象引用(reference类型)和returnAddress类型(指向了一条字节码指令的地址)

    64位长度的数据(long和double)会占用2个局部变量空间(Slot),其余类型都占1个。

异常:

(1)如果线程请求的栈深度大于虚拟机所允许的深度,抛出 StackOverflowError

(2)如果虚拟机可以动态扩展(大部分都可以),如果扩展时无法申请到足够的内存,抛出 OutOfMemoryError

3、本地方法栈

本地方法栈类似于Java虚拟机栈

有些虚拟机(例如Sun HotSpot虚拟机)直接把Java虚拟机栈和本地方法栈合二为一

区别:

本地方法栈执行native方法

Java虚拟机栈执行Java方法

4、Java堆(Java Heap)

类型:线程共享的内存区域

概念:

Java虚拟机所管理的内存中最大的一块。

Java堆的唯一目的就是存放对象实例。

Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像磁盘空间一样。

通过-Xmx和-Xms控制堆内存的扩展。

Java堆时垃圾手机和管理的主要区域,有时候也被称为 “GC堆”。

异常:

如果在堆中没有内存完成实例分配,并且堆无法再扩展时,抛出OutOfMemoryError

5、方法区

类型:线程共享的内存区域

概念:

用于存储每个类的结构信息。

例如:运行时常量池(接下来会讲)、字段、方法数据、构造函数和普通方法的字节码内容。还包括一些在类、实例、接口初始化时用到的特殊方法。

方法区是堆的逻辑组成部分,但区别于Java堆。虚拟机在方法区可以不实现垃圾收集与压缩。

注意:有的博客上写 “方法区又称静态区”,但本人并没有在权威的资料里看到这个翻译,所以建议大家可以结合这个名词理解,但不要这么称呼。(如果有在权威权威资料里看到这个翻译,敬请告知)

异常:

当方法区无法满足内存分配需求时,抛出,OutOfMemoryError异常。

6、运行时常量池

概念:

方法区的一部分,用于存放编译器生成的各种字面量和符号引用。

在程序运行期间,也可能有新的常量放入池中。

  • 字面量
int i = 1;把整数1赋值给int型变量i,整数1就是Java字面量,
String s = "abc";中的abc也是字面量。
           
  • 符号引用

    符号引用以一组符号来描述所引用的目标, 符号可以是任何形式的字面量, 只要使用时能够无歧义的定位到目标即可. 例如, 在Java中, 一个Java类将会编译成一个class文件. 在编译时, Java类并不知道所引用的类的实际地址, 因此只能使用符号引用来代替. 比如org.simple.People类引用了org.simple.Language类, 在编译时People类并不知道Language类的实际内存地址, 因此只能使用符号org.simple.Language来表示Language类的地址.

  • 直接引用

    直接引用可以是:

    直接指向目标的指针.(个人理解为: 指向方法区中类对象, 类变量和类方法的指针)

    相对偏移量. (指向实例的变量, 方法的指针)

    一个间接定位到对象的句柄.

    我觉得直接引用说白了, 就是程序运行时可以定位到引用的东西(类, 对象, 变量或者方法等)的地址.

其他

1、直接内存

直接内存不是Java虚拟机运行时数据区的一部分。

在JDK1.4中,新加入的NIO类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,通过存储在堆中的DirectByteBuffer对象作为这块内存的引用进行操作。

异常

如果各个内存区域(Java虚拟机内存区域)总和大于物理内存限制(物理和操作系统级的限制),会导致动态扩展时 抛出,OutOfMemoryError。

[1] 周志明 · 深入理解Java虚拟机 :机械工业出版社

[2] 爱飞翔 周志明 等译 · Java虚拟机规范(Java SE 8 版)机械工业出版社

[3] 字面量,符号引用,直接引用的概念摘自 https://blog.csdn.net/BraveLoser/article/details/82500474