深刻理解JVM —— Java运行时数据区域

引一句周志明老师对 Java 中的内存管理机制的描述:算法

Java 与 C++ 之间有一堵有内存动态分配和垃圾收集技术所围成的「高墙」,墙外面的人想进去,墙里面的人却想出来。数组

运行时数据区域

Java虚拟机在执行Java程序的过程当中把它所管理的内存划分为若干个不一样的数据区域。这些区域都有各自的用途,以及建立和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而创建和销毁。缓存

image.png


线程私有的:

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈

线程共享的:微信

  • 方法区
  • 直接内存

程序计数器

程序计数器是一块较小的内存空间,能够看做是当前线程所执行的字节码的行号指示器。字节码解释器工做时经过改变这个计数器的值来选取下一条须要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都须要依赖这个计数器来完。函数

为了线程切换后能恢复到正确的执行位置,每条线程都须要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,咱们称这类内存区域为“线程私有”的内存。性能

程序计数器也是惟一一个不会出现OutOfMemoryError的内存区域,它的生命周期随着线程的建立而建立,随着线程的结束而死亡。线程

虚拟机栈

Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是 Java 方法执行的内存模型。咱们能够把Java 内存能够粗糙的区分为堆内存(Heap)栈内存(Stack),其中栈就是如今说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。 而实际上,Java虚拟机栈是由一个个栈帧组成,描述的是Java方法执行的内存模型:每一个方法在执行的同时,都会建立一个栈帧,用于存储局部变量表、操做数栈、动态连接、方法出口信息。)设计

局部变量表主要存放了编译器可知的基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不一样于对象自己,多是一个指向对象起始地址的引用指针,也多是指向一个表明对象的句柄或其余与此对象相关的位置)和 returnAddress类型(指向了一条字节码命令地址)。指针

Java 虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。code

  • StackOverFlowError: 若Java虚拟机栈的内存大小不容许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError异常。
  • OutOfMemoryError: 若 Java 虚拟机栈的内存大小容许动态扩展,且当线程请求栈时内存用完了,没法再动态扩展了,此时抛出OutOfMemoryError异常。

本地方法栈

和虚拟机栈所发挥的做用很是类似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。与虚拟机栈同样,本地方法栈区域也会抛出StackOverFlowError 和 OutOfMemoryError异常。
**

Java 虚拟机所管理的内存中最大的一块,Java 堆是全部线程共享的一块内存区域,在虚拟机启动时建立。此内存区域的惟一目的就是存放对象实例,几乎全部的对象实例以及数组都在这里分配内存。

Java 堆是垃圾收集器管理的主要区域,所以也被称做GC堆(Garbage Collected Heap).从垃圾回收的角度,因为如今收集器基本都采用分代垃圾收集算法,因此Java堆还能够细分为:新生代和老年代:在细致一点有:Eden空间、From Survivor、To Survivor空间等。

Java堆能够处于物理上不连续的内存空间中,只要逻辑上是连续的便可。若是在堆中咩有内存完成实例分配,而且堆也没法在拓展时,将会抛出OutOfMemoryError异常。

方法区

方法区与 Java 堆同样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,可是它却有一个别名叫作 Non-Heap(非堆),目的应该是与 Java 堆区分开来。

HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上二者并不等价。仅仅是由于 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就能够像管理 Java 堆同样管理这部份内存了。可是这并非一个好主意,由于这样更容易遇到内存溢出问题。相对而言,垃圾收集行为在方法区是比较少出现的,但并不是数据进入方法区后就“永久存在”了


根据Java 虚拟机规范的规定,当方法区没法知足内存分配需求时,将抛出OutOfMemoryError 异常。

运行时常量池

运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各类字面量和符号引用)
既然运行时常量池时方法区的一部分,天然受到方法区内存的限制,当常量池没法再申请到内存时会抛出 OutOfMemoryError 异常。

JDK1.7及以后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
**

直接内存

直接内存并非虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,可是这部份内存也被频繁地使用。并且也可能致使OutOfMemoryError异常出现。
JDK1.4中新加入的 NIO(New Input/Output) 类,引入了一种基于通道(Channel)缓存区(Buffer) 的 I/O 方式,它能够直接使用Native函数库直接分配堆外内存,而后经过一个存储在 Java 堆中的 DirectByteBuffer 对象做为这块内存的引用进行操做。这样就能在一些场景中显著提升性能,由于避免了在 Java 堆和 Native 堆之间来回复制数据
本机直接内存的分配不会收到 Java 堆的限制,可是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。

参考文献《深刻理解Java虚拟机》 周志明 著


微信公众号:

相关文章
相关标签/搜索