java中OutofMemoryError和JVM内存结构

OutOfMemoryError在开发过程中是司空见惯的,遇到这个错误,新手程序员都知道从两个方面入手来解决:

1:是排查程序是否有BUG导致内存泄漏;

2:是调整JVM启动参数增大内存。

OutOfMemoryError有好几种情况,每次遇到这个错误时,观察OutOfMemoryError后面的提示信息,就可以发现不同之处,如:

引用

java.lang.OutOfMemoryError: Java heap space

java.lang.OutOfMemoryError: unable to create new native thread

java.lang.OutOfMemoryError: PermGen space

java.lang.OutOfMemoryError: Requested array size exceeds VM limit

虽然都叫OutOfMemoryError,但每种错误背后的成因是不一样的,解决方法也要视情况而定,不能一概而论。只有深入了解JVM的内存结构并仔细分析错误信息,才有可能做到对症下药,手到病除。

JVM规范JVM规范对Java运行时的内存划定了几块区域,有:

  • JVM栈(Java Virtual Machine Stacks)、
  • 堆(Heap)、
  • 方法区(Method Area)、
  • 常量池(Runtime Constant Pool)、
  • 本地方法栈(Native Method Stacks),

但对各块区域的内存布局和地址空间却没有明确规定,而留给各JVM厂商发挥的空间。

HotSpot JVMSun自家的HotSpot JVM实现对堆内存结构有相对明确的说明。按照HotSpot JVM的实现:

堆内存分为3个代:

  • Young Generation、
  • Old(Tenured) Generation、
  • Permanent Generation。

众所周知,GC(垃圾收集)就是发生在堆内存这三个代上面的。Young用于分配新的Java对象,其又被分为三个部分:Eden Space和两块Survivor Space(称为From和To),Old用于存放在GC过程中从Young Gen中存活下来的对象,Permanent用于存放JVM加载的class等元数据。详情参见HotSpot内存管理白皮书。堆的布局图示如下:

根据这些信息,我们可以推导出JVM规范的内存分区和HotSpot实现中内存区域的对应关系:JVM规范的Heap对应到Young和Old Generation,方法区和常量池对应到Permanent Generation。对于Stack内存,HotSpot实现也没有详细说明,但HotSpot白皮书上提到,Java线程栈是用宿主操作系统的栈和线程模型来表示的,Java方法和native方法共享相同的栈。因此,可以认为在HotSpot中,JVM栈和本地方法栈是一回事。

经过上述铺垫,可以得知,JVM进程的内存大致分为Heap空间和Stack空间两部分。Heap又分为Young、Old(Tenured)、Permanent三个代。Stack分为Java方法栈和native方法栈(不做区分),在Stack内存区中,可以创建多个线程栈,每个线程栈占据Stack区中一小部分内存,线程栈是一个LIFO数据结构,每调用一个方法,会在栈顶创建一个Frame,方法返回时,相应的Frame会从栈顶移除(通过移动栈顶指针)。在这每一部分内存中,都有可能会出现溢出错误。回到开头的OutOfMemoryError,下面逐个说明错误原因和解决方法(每个OutOfMemoryError都有可能是程序BUG导致,因此解决方法不包括对BUG的排查)。

java.lang.OutOfMemoryError: Java heap space

原因:Heap内存溢出,意味着Young和Old generation的内存不够。

解决:调整java启动参数 -Xms -Xmx 来增加Heap内存。

java.lang.OutOfMemoryError: unable to create new native thread

原因:Stack空间不足以创建额外的线程,要么是创建的线程过多,要么是Stack空间确实小了。

解决:由于JVM没有提供参数设置总的stack空间大小,但可以设置单个线程栈的大小;而系统的用户空间一共是3G,除了Text/Data/BSS/MemoryMapping几个段之外,Heap和Stack空间的总量有限,是此消彼长的。因此遇到这个错误,可以通过两个途径解决:

  • 1.通过-Xss启动参数减少单个线程栈大小,这样便能开更多线程(当然不能太小,太小会出现StackOverflowError);
  • 2.通过-Xms -Xmx 两参数减少Heap大小,将内存让给Stack(前提是保证Heap空间够用)。

java.lang.OutOfMemoryError: PermGen space

原因:Permanent Generation空间不足,不能加载额外的类。

解决:调整-XX:PermSize= -XX:MaxPermSize= 两个参数来增大PermGen内存。一般情况下,这两个参数不要手动设置,只要设置-Xmx足够大即可,JVM会自行选择合适的PermGen大小。对于spring,hibernate这些需要动态类型支持的框架,这个区域需要足够的空间。

java.lang.OutOfMemoryError: Requested array size exceeds VM limit

原因:这个错误比较少见(试着new一个长度1亿的数组看看),同样是由于Heap空间不足。如果需要new一个如此之大的数组,程序逻辑多半是不合理的。

解决:修改程序逻辑吧。或者也可以通过-Xmx来增大堆内存。

在GC花费了大量时间,却仅回收了少量内存时,也会报出OutOfMemoryError,我只遇到过一两次。当使用-XX:+UseParallelGC或-XX:+UseConcMarkSweepGC收集器时,在上述情况下会报错,在HotSpot GC Turning文档上有说明:

The parallel(concurrent) collector will throw an OutOfMemoryError if too much time is being spent in garbage collection: if more than 98% of the total time is spent in garbage collection and less than 2% of the heap is recovered, an OutOfMemoryError will be
thrown.

对这个问题,一是需要进行GC turning,二是需要优化程序逻辑。

java.lang.StackOverflowError

原因:这也内存溢出错误的一种,即线程栈的溢出,要么是方法调用层次过多(比如存在无限递归调用),要么是线程栈太小。

解决:优化程序设计,减少方法调用层次;调整-Xss参数增加线程栈大小。

JVM中堆被分为了3个大的区间,同时JVM也提供了一些选项对Young,Tenured的大小进行控制。

-Xms :指定了JVM初始启动以后初始化内存

-Xmn :参数设置了年轻代的大小

-Xmx:指定JVM堆得最大内存,在JVM启动以后,会分配-Xmx参数指定大小的内存给JVM,但是不一定全部使用,JVM会根据-Xms参数来调节真正用于JVM的内存

-XX:NewRatio=8意味着tenured 和 young的比值8:1,这样eden+2*survivor=1/9

-XX:SurvivorRatio=32意味着eden和一个survivor的比值是32:1,这样一个Survivor就占Young区的1/34.

-XX:PermSize=16M -XX:MaxPermSize=64M

Thread Stack

-XX:Xss=128K

时间: 2024-08-07 04:34:06

java中OutofMemoryError和JVM内存结构的相关文章

java复习基础篇—-JVM内存结构(转)

主要内容如下: JVM启动流程 JVM基本结构 内存模型 编译和解释运行的概念 一.JVM启动流程: JVM启动时,是由java命令/javaw命令来启动的. 二.JVM基本结构: JVM基本结构图: <深入理解Java虚拟机(第二版)>中的描述是下面这个样子的: Java中的内存分配: Java程序在运行时,需要在内存中的分配空间.为了提高运算效率,就对数据进行了不同空间的划分,因为每一片区域都有特定的处理数据方式和内存管理方式. 具体划分为如下5个内存空间:(非常重要) 栈:存放局部变量

面试官,Java8中JVM内存结构变了,永久代到元空间

在Java8和以后版本中JVM的内存结构慢慢发生了变化.作为面试官如果你还不知道,那么面试过程中是不是有些露怯?作为面试者,如果知晓这些变化,又将成为面试中的亮点. 如果在网络上搜索JVM内存结构,90%的可能会搜到Java7及以前的内存图,本篇文章将会对JVM内存结构再次细化,深入理解Java8之后的内部变化.现在意×××"的好处了吧.在这里可以不断的刷新你的知识和认知. JVM内存结构的细化 来看一下JVM中的内存结构图. 为了更细化的讲解,我们将该图进行进一步的优化调整.针对java7及以

JVM内存结构 VS Java内存模型 VS Java对象模型

Java作为一种面向对象的,跨平台语言,其对象.内存等一直是比较难的知识点.而且很多概念的名称看起来又那么相似,很多人会傻傻分不清楚.比如本文我们要讨论的JVM内存结构.Java内存模型和Java对象模型,这就是三个截然不同的概念,但是很多人容易弄混.可以这样说,很多高级开发甚至都搞不不清楚JVM内存结构.Java内存模型和Java对象模型这三者的概念及其间的区别.甚至我见过有些面试官自己也搞的不是太清楚.不信的话,你去网上搜索Java内存模型,还会有很多文章的内容其实介绍的是JVM内存结构.首

【转】JVM内存结构 VS Java内存模型 VS Java对象模型

JVM内存结构 我们都知道,Java代码是要运行在虚拟机上的,而虚拟机在执行Java程序的过程中会把所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途. 其中有些区域随着虚拟机进程的启动而存在,而有些区域则依赖用户线程的启动和结束而建立和销毁.在<Java虚拟机规范(Java SE 8)>中描述了JVM运行时内存区域结构如下: 各个区域的功能不是文本重点,就不在这里详细介绍了.这里简单提几个需要特别注意的点: 1.以上是Java虚拟机规范,不同的虚拟机实现会各有不同,但是一般会遵守

[转帖]JVM内存结构 VS Java内存模型 VS Java对象模型

JVM内存结构 VS Java内存模型 VS Java对象模型 https://www.hollischuang.com/archives/2509 Java作为一种面向对象的,跨平台语言,其对象.内存等一直是比较难的知识点.而且很多概念的名称看起来又那么相似,很多人会傻傻分不清楚.比如本文我们要讨论的JVM内存结构.Java内存模型和Java对象模型,这就是三个截然不同的概念,但是很多人容易弄混. 可以这样说,很多高级开发甚至都搞不不清楚JVM内存结构.Java内存模型和Java对象模型这三者

JVM内存结构、Java内存模型和Java对象模型

Java作为一种面向对象的,跨平台语言,其对象.内存等一直是比较难的知识点.而且很多概念的名称看起来又那么相似,很多人会傻傻分不清楚.比如本文要讨论的JVM内存结构.Java内存模型和Java对象模型,这就是三个截然不同的概念,但是很多人容易弄混. 首先,这三个概念是完全不同的三个概念.本文主要目的是对这三个概念加以区分以及做简单的介绍.而这每一个知识点都是又都是比较复杂的.以后会单独写文章做详细介绍. Jvm内存结构 我们都知道,Java代码是要运行在虚拟机上的,而虚拟机在执行Java程序的过

区分 JVM 内存结构、 Java 内存模型 以及 Java 对象模型 三个概念

本文由 简悦 SimpRead 转码, 原文地址 https://www.toutiao.com/i6732361325244056072/ 作者:Hollis 来源:公众号Hollis Java 作为一种面向对象的,跨平台语言,其对象.内存等一直是比较难的知识点.而且很多概念的名称看起来又那么相似,很多人会傻傻分不清楚.比如本文我们要讨论的 JVM 内存结构.Java 内存模型和 Java 对象模型,这就是三个截然不同的概念,但是很多人容易弄混. 可以这样说,很多高级开发甚至都搞不不清楚 JV

JVM内存结构

一.JVM内存结构 1.1 下面总体说说内存 Java虚拟机会将内存分为几个不同的管理区,这些区域各自有各自的用途,根据不同的特点,承担不同的任务以及在垃圾回收时运用不同的算法.总体分为下面几个部分: 程序计数器(Program Counter Register).JVM虚拟机栈(JVM Stacks).本地方法栈(Native Method Stacks).堆(Heap).方法区(Method Area) 1.2 下面说说具体各个结构的功能 1.2.1.程序计数器(Program Counte

JVM内存结构——运行时数据区

在Java虚拟机规范中将Java运行时数据划分为6种,分别为: PC寄存器(程序计数器) Java栈 堆 方法区 运行时常量池 本地方法栈 一.PC寄存器(程序计数器) PC寄存器(Program Counter Register)严格来说是一个数据结构,它用于保存当前正常执行的程序的内存地址. 线程私有. 每个线程启动的时候,都会创建一个PC(Program Counter,程序计数器)寄存器.PC寄存器里保存有当前正在执行的JVM指令的地址. 每个线程都需要一个独立的程序计数器,各条线程之间