JVM内存区域划分与介绍

关于JVM内存区域分布的知识是Java相关岗位面试的常考问题,通过学习《深入理解Java虚拟机》书籍相关部分内容后,写下了这篇博客。



Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。

每个区域有每个区域的用途和每个区域分别的存在周期。

总体上来说,JVM的内存区域划分如下:

JVM内存区域划分
其中以竖线为分割,竖线左边为所有线程共享数据区,右边为线程私有数据区

接下来我们对JVM内存区域中的几个部分进行解释:

程序计数器:

程序计数器是一块较小的内存空间。是程序控制流的指示器程序进行分支、循环、跳转、异常处理都需要用到计数器来完成

程序计数器并不是像其名字那样简单,程序计数器是用来完成线程切换的重要部分。

程序计数器是当前所执行字节码的行号指示器,字节码在解释器工作时需要通过改变计数器的值来确定下一条需要执行的字节码指令。

因此才有了程序计数器是程序控制流的指示器的说法。

值得注意的是,在多线程环境中,由于会不断切换所执行的线程,为了使线程在切换后能恢复到原来执行的位置,每个线程的程序计数器是隔离的。每个线程的程序计数器之间互不影响,独立存储,这部分区域就称为“线程独有的内存区域”。

总的来说程序计数器有几个要点:

  1. 程序计数器是一块较小的内存空间,是程序控制流的指示器。
  2. 每个线程的程序计数器是隔离的
  3. 程序计数器是唯一一个没有规定任何越界数据(OutOfMemoryError)的区域

Java虚拟机栈

Java虚拟机栈描述的是Java方法执行的线程内存模型,每个方法在执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息

虚拟机栈
每个线程都有一个独立的虚拟机栈,每个方法都在虚拟机栈中开辟一个新的栈帧,而每个栈中存储的数据就是方法的对应属性。

在线程运行中,位于栈顶的栈帧才是有效的,因此称为当前有效栈帧,与这个栈帧相关联的方法称为当前有效方法

接下来解释一下一个栈帧中的几个元素:

  1. 局部变量表:顾名思义,其作用是用来存储局部变量和方法参数的内存区域。(对于定义在类中的成员变量放在方法区中),局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double),对象引用(reference类型) 和 returnAddress类型(它指向了一条字节码指令的地址)

  2. 操作数栈:用来存放操作数的内存区域。

  3. 动态连接:动态连接是一个引用,相当于C语言中的指针,指向该方法在运行时常量池中的位置。动态连接通过运行时常量池的符号引用(指向堆),完成将符号引用转化为直接引用

在Java虚拟机规范中,对这个区域规定了两种异常状况

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

如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,抛出OutOfMemoryError异常

本地方法栈:

本地方法栈与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务而本地方法栈则为虚拟机使用到的Native方法服务

所谓Native方法简单地讲就是:

一个Native Method就是一个java调用非java代码的接口

一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C等等

堆:

Java虚拟机中的堆的唯一目的就是存放对象实例几乎所有的对象实例都在这里分配内存

在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。但是也有极少数情况对象实例不再堆上分配。

在程序运行过程中,可以理解为Java 程序在运行时创建的所有类型对象和数组都存储在堆中

Java堆是被所有线程共享的一块区域,在虚拟机被启动时创建。当然堆的空间大小时有限的,当对象实例达到一定限度时,堆应该怎么办呢?

这就引出了堆区域的另一个重要知识:堆区域的垃圾收集器管理机制

不过本文只是介绍内存区域,对堆中的垃圾回收算法知识不做过多介绍。

为了提高对象分配空间时的效率,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)

根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可

方法区:

方法区和Java堆一样,都是线程共享的内存区域

方法区的作用是用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据

在Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是方法区却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。

方法区存储的内容与堆区最大的不同就是方法区中的内容很少发生内存回收,也就是堆区内存中的永生代

例如已被虚拟机加载的类型信息,在类依次被加载后便不再离开方法区,在创建类实例时不断重复引用该类信息。

在方法区中一个重要的部分就是运行时常量池:

运行时常量池:

运行时常量池(Runtime Constant Pool)是方法区的一部分。所存储的数据部分顾名思义,就是存放一些用final和static存放的成员变量。最为经典的例子就是String一个字符串,会将它直接放在常量池中,并且在后续如果创建相同的String便会直接引用该常量池中的地址。

除此之外,常量池还存放class文件中的常量池表,class文件中的常量池表不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。

猜你喜欢

转载自blog.csdn.net/Nimrod__/article/details/114799977