您的位置:首页 > 编程语言 > Java开发

[jvm解析系列][一]Java内存区域分配和内存溢出异常OOM

2016-06-02 10:48 405 查看
学过操作系统的同学应该比较清楚,一个操作系统必须要有完善的内存管理系统(页/段式的管理),相应的jvm全称java虚拟机应该也有类似的一种管理内存的方式,这种方式是建立在真实的操作系统内存管理方式之上的,他把内存分配成了不同的区域,形成了java内存模型。

那么,对于其他博客讲解这种题目要先抛一个图解出来,我并不想这样。因为这种模型的出现肯定是要解决问题的,我们需要顺延着前人设计jvm内存模型的脚步,看看到底为什么要这样设计,才能更好的理解它。

首先,我们思考一下,java需要什么区域呢?

1、必不可少的,一个程序计数器,用来记录当前程序运行的字节码指令,jvm使用了栈而不是寄存器结构(往后看相信你会理解的)。对于线程来讲,每个线程肯定是需要记录自己的执行位置,所以程序计数器是线程私有的

2、既然有了程序计数器我们还需要一个区域来存放当前运行的程序,那就是Java虚拟机栈,每一个方法的执行就会添加一个栈帧在虚拟机栈里(如果想要详细的了解栈帧请关注博客,随着系列的加深,我会一个个讲解)。对于线程来讲虚拟机栈同样的是线程私有。

3、一样的常用jvm的都知道还有一种jni的调用,为了这种方法的运行,设计了一种跟java虚拟机栈的双胞胎区域叫本地方法栈,除了运行native方法外其他的几乎一致。(在有的虚拟机里不区分native和java)

4、大家都知道java是面向对象的,既然方法都出来了,那么类存储在那呢?没错,jvm也分化出来了这样的一个区域叫做方法区(也称为永久代,因为这个区域很长时间不会发生gc)。这个区域里存储了虚拟机家在的所有的类信息,常量和静态变量等。(内含一个运行时常量池主要用于存放编译器生成的字面量和符号引用)他是线程公有的。

5、如果类都有了,那么对象呢?没错这就是jvm的重头戏,很多工作都在这个地方完成,他就是传说中的java堆,在有的地方甚至把jvm笼统的分为堆栈,可见java堆的重要性。java堆里面存放了几乎所有的对象实例,在java虚拟机规范中这样说:所有的对象实例和数组都要在堆上分配。随着JIT编译器等的发展也不那么绝对了。(java堆仍然很复杂,后期单开一篇博客详解)。它肯定是线程公有的。

6、到这里,好像java所有的需要都被分配了,以前确实是的,但是在JDK1.4之后,java引入了nio技术(不在本章范围内讲解),这就要求需要分配一个新的区域,没错,他叫直接内存。值得一提的是,这一区域并不是jvm的规范中的一部分,也不是虚拟机运行时数据区的一部分,但是他会引起OOM。

那么至此所有的内存区域分配完毕,配图如下帮助理解,相信你已经差不多的了解和理解了jvm分配的方式和目的了。

内存区域分配配图



上节说到~jvm的内存分配,所以对应的应该有6种溢出方式,实际上程序计数器一般不会溢出,我又把两个方法栈合在一起讲,所以溢出的可能性有4种

嘿嘿嘿嘿,我要去考试了,等我回来再补吧~

回来了继续补

1、Java堆溢出

java堆溢出一般都是因为在for或者递归里调用了太多次new对象的操作,并且可以被GCROOT查找到不能GC,而导致java堆内存不足报错,报错一般如下:

OutOfMemoryError:Java heap spce

2、虚拟机栈和本地方法栈溢出

虚拟机栈和本地方法溢出一般也是陷入了无限循环里并且在无限循环里一直在调用一个方法,因为一般情况下栈深能有1000~2000,报错一般都是StackOverflowError,OOM反而不常见,在处理这种异常时很棘手,需要减少其他区域的内存,因为这块内存是用总内存减去堆和方法区得到的。报错如下:

StackOverflowError.

3、方法区和运行时常量池溢出

这个区域想要溢出单单使用创建类的方法和常量是困难的,所以可以使用String.intern()方法直接将字符串加入到常量池。报错一般如下

OOM:PermGen space

4、本机直接内存溢出

由于上文知道直接内存一般用于NIO操作,所以使用了native方法而且因为这个区域的特殊性Heap dump并没有明显的报错。错误信息如下:

OutOfMemory。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: