Java内存区域与内存溢出异常

Java的内存管理是一个老生常谈的问题,虽然Java号称能够自动管理本身的内存,使程序员从内存管理的围墙解放出来,可是一连串的内存泄漏和溢出方面的问题,使得咱们不得不去深刻了解Java的内存管理机制。本篇文章将从Java的内存区域开始剖析Jvm的内存机制,阐述内存溢出异常产生的缘由以及解决办法。程序员

运行时数据区域

众说周知,Java程序是运行在Java虚拟机中的,虚拟机顾名思义,就是一个虚拟的计算机。因此Java虚拟机也拥有一些与真实计算机相近的概念,好比栈,堆,程序计数器等,一般咱们在这些概念面前加上虚拟机,以代表特指Java虚拟机的栈。数组

Java程序运行时,Java虚拟机会对内存进行管理,划分为若干个不一样的数据区域,每一个数据区域都有其不一样的功能。根据《Java虚拟机规范(Java SE 7版)》的规定,Java虚拟机所管理的区域会包括如下几个运行时区域,以下图所示。多线程

下面咱们一一介绍每一个区域的不一样功能。架构

程序计数器

程序计数器,即PC。学过计算机组成原理的同窗必定对这个概念不陌生,在计算机组成原理中PC指的是PC寄存器,用来存放计算机执行的指令的所在内存区域的地址。而在Java虚拟机中,PC也有相似的做用,它的做用是存储当前线程所执行的字节码的行号指示器,经过改变这个计数器的值来选取下一条须要执行的字节码指令。与计算机PC不一样的是,在Java虚拟机中,PC只是一块较小的内存空间,而不是寄存器。框架

因为Java虚拟机是多线程的,为了在线程之间进行隔离,每个线程都会拥有一个独立的程序计数器。所以,在进行线程调度的时候,每一个线程的执行互不影响。咱们称这类内存区域为“线程私有”的内存。性能

程序计数器记录的只是正在执行的虚拟机字节码指令的地址,若是执行的是Native方法,那么计数器的值则为空。该内存区域是惟一一个在Java虚拟机规范中没有规定任何OutOfMemoryError状况的区域。学习

Java虚拟机栈

Java虚拟机栈与系统栈也有些相似,都用来存储程序运行过程当中建立的栈帧,不过Java虚拟机栈存储的是方法的栈帧而已,它与程序计数器同样,都是线程私有的。spa

在Java方法执行时建立的栈帧是用来存储局部变量表、操做数栈、动态连接,方法出口等信息,咱们日常所说的方法的入栈和出栈就是一个方法从执行到结束的过程。虚拟机栈的特性与通常的栈同样,一样是后进先出,递归调用的原理就是基于此。操作系统

通常来讲,对于Java虚拟机栈,咱们主要关心的部分是它的局部变量表的存储。在咱们定义一个变量的时候,变量到底被存放在哪里是咱们常常遇到的问题。对于基本数据类型,如boolean、byte等以及对象的引用(reference类型,一个指向对象的指针或者是一个句柄,不是对象自己)和returnAddress类型(指向了一条字节码指令的地址)。线程

局部变量表的大小是在编译期就已经彻底肯定下来的,在方法运行期间不会改变局部变量表的大小。同时,对于64位长度的long和double类型的数据会占用两个局部变量空间,其他的只占用一个。

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

本地方法栈

本地方法栈与虚拟机栈相似,惟一的区别是本地方法栈是用来执行Native方法的。

Java堆

Java堆是咱们在编写Java程序中所能使用的最大的一块的内存区域了,也是咱们常常须要调整的区域。这个区域的惟一目的就是存放对象实例,几乎全部的对象实例都在这里分配内存,包括数组(由于数组也是引用数据类型)。Java堆与虚拟机栈不一样,它是被全部线程共享的区域,在虚拟机启动的时候建立。

Java堆还能够进一步分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等,这些更细致的分区是在Java堆垃圾收集器进行垃圾管理的时候须要考虑的。

Java堆的大小能够是固定的,也能够是不固定的,能够经过-Xmx和-Xms控制,前者是最大值,后者是最小值,在二者相同时,堆的大小就是固定的。在内存中若是没有足够的空间来分配,将会抛出OutOfMemoryError异常。

方法区

方法区和Java堆同样,是各个线程共享的内存区域,用来存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

这个区域也是属于须要进行垃圾回收的区域,主要是回收常量池和对类型的卸载,通常来讲,回收的效果不会太理想,可是倒是必须的。

在此我向你们推荐一个架构学习交流圈:830478757  帮助突破瓶颈 提高思惟能力 

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

运行时常量池

该区域是方法区的一部分,用于存放编译期生成的各类字面量和符号引用,有时候直接引用也会放入,这部份内容将在类加载后进入方法区的运行时常量池中存放。

运行时常量池有必定的动态性,对String类有所了解的同窗应该明白,在运行期间经过String类的intern()方法能够动态往常量池里动态添加常量。

直接内存

直接内存不属于Java虚拟机运行时数据区的一部分,而是属于操做系统管理的区域。这部分的使用很频繁,利用的好,能够大大提高程序的运行效率,比较优秀的使用例如基于NIO的Netty框架等。

为何使用直接内存能够提高性能呢,由于能够避免在Java堆和Native堆中来回复制数据的开销。

这部分的内存使用不会收到Java堆大小的限制,但会收到本机的内存大小限制。所以,在操做这部份内存时须要谨慎,一旦出问题,可能会影响到本机的其它服务。 当各个内存区域总和大于物理内存限制,抛出OutOfMemoryError异常。

相关文章
相关标签/搜索