java-内存溢出(OutOfMemoryError)的可能性分析

384 阅读4分钟

最近在看《深入理解Java虚拟机》,总结了一下可能出现内存溢出的原因,如果有不完善或者理解不对的地方,还请各位大神补充,一起进步,非常感谢。

Jva 虚拟机运行会将内存分为一下几个数据区域:

1.程序计数器

程序计数器是一块较小的内存空间,它可以被看作是当前线程所执行的字节码的行号指示器。每个线程都需要一个独立的程序计数器,独立存储,因此,程序计数器是“线程私有”的内存。也是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

2.Java虚拟机栈

线程私有。生命周期与线程相同。每个方法在执行的同时,都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。,其中局部变量表存放了编译期可知的基本数据类型和对象引用。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的。在方法运行期间不会改变局部变量表的大小。

在java虚拟机规范中,对这个区域规定了两种异常的状况:如果线程请求的栈深度大于虚拟机所允许的深度,将会抛出StackOverFlowError异常。如果虚拟机栈可以动态扩展(当前大部分的java虚拟机都可以动态扩展,只不过java虚拟机规范中也允许固定长度的虚拟机栈),如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

3.本地方法栈

本地方法栈与虚拟机栈所发挥的作用非常相似。区别是:虚拟机栈为虚拟机执行java方法服务,而本地方法栈是为虚拟机使用到的native方法服务。与虚拟机一样,本地方法栈区域也会排除StackOverFlowError和OutOfMemoryError异常。

4.java堆

java堆是java虚拟机所管理的内存中最大的一块。java对是线程共享的。在虚拟机启动时创建。所有的对象实例和数组都要在堆上进行分配。java虚拟机规范规定,java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

5.方法区

方法区是各个线程共享的内存区域。他用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的数据。java虚拟机规范对于方法区的限制非常宽松,除了和java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。根据java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

6.运行时常量池

运行时常量池是方法区的一部分。用于存放编译器生成的各种字面量和符号引用。这部分内容将在类加载后进入方法区的运行时常量池中存放。既然常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时将会抛出OutOfMemoryError异常。

7.直接内存(非虚拟机运行时数据区的一部分)

直接内存并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。但是这部分内存也被频繁使用,而且也会导致OutOfMemoryError异常。

本机直接内存的分配不会受到java堆大小的限制,但是会受到本机总内存大小以及处理器寻址空间的限制。服务器管理员在配置虚拟机参数时,会根据实际内存设置-Xmx等参数信息,但经常忽略直接内存,是的各个内存区域总和大于物理内存限制,从而导致OutOfMemoryError异常。