Java 内存溢出和内存泄漏的区别

来源:互联网 发布:js递归树形数据结构 编辑:程序博客网 时间:2024/05/29 18:53


Edit

Java 内存溢出和内存泄漏的区别

内存溢出 out of memory

是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。

内存泄露 memory leak

是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

内存溢出

下面给出个内存区域内存溢出的简单测试方法


对象实例化分析

对内存分配情况分析最常见的示例便是对象实例化:Object obj = new Object();

这段代码的执行会涉及java栈、Java堆、方法区三个最重要的内存区域。假设该语句出现在方法体中,及时对JVM虚拟机不了解的Java使用这,应该也知道obj会作为引用类型(reference)的数据保存在Java栈的本地变量表中,而会在Java堆中保存该引用的实例化对象,但可能并不知道,Java堆中还必须包含能查找到此对象类型数据的地址信息(如对象类型、父类、实现的接口、方法等),这些类型数据则保存在方法区中。

内存区域模型小结:

1)线程私有的区域:程序计数器、虚拟机栈、本地方法栈;(2)所有线程共享的区域:Java堆、方法区;(注:直接内存不属于虚拟机内存模型的部分)(3)没有异常的区域:程序计数器;(4)StackOverflowError异常:Java虚拟机栈、本地方法栈;(5)OutOfMemoryError异常:除程序计数器外的其他四个区域,Java虚拟机栈、本地方法栈、Java堆、方法区;直接内存也会出现OutOfMemoryError。

对象的创建、对象内存布局、对象的访问定位

2.1 对象的创建过程
Java在语言层面,通过一个关键字new来创建对象。在虚拟机中,当遇到一条new指令后,将开始如下创建过程:

1)判断类是否加载、解析、初始化虚拟机遇到一条new指令时,先去检查这个指定的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那先执行相应的类加载过程。(2)为新对象分配内存前面说到,对象的内存分配是在Java堆中的。为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来,此时Java堆中的情况有两种可能,一种是Java堆中内存是绝对规整的,一种是Java堆中的内存并不是规整的。因此有两种分配方式:1)Java堆内存是规整的,即所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,此时,分配内存仅需要把这个指针向空闲空间那边挪动一段与对象大小相等的距离,这种方式也称为“指针碰撞”(Bump the Pointer);2)Java堆内存不是规整的,即已使用的内存和空闲的内存相互交错,就没有办法简单地进行指针的移动,此时的分配方案是,虚拟机必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的控件划分给对象实例,并更新列表上的记录,这种方式也称为“空闲列表”(Free List);

选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。因此,对于Serial、ParNew等带Compact过程的垃圾收集器,系统采用的是指针碰撞算法;对于CMS这种基于Mark-Sweep算法的收集器,通常采用空闲列表算法。

3)解决并发安全问题确定了如何划分内存空间之后,还有一个问题就是,对象的创建在虚拟机中是非常频繁的行为,比如,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况,解决这种并发问题,一般有两种方案:1)对分配内存空间的动作进行同步处理,比如,虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;2)另一种方式是,把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),哪个线程要分配内存,就在哪个线程的TLAB上分配。只有TLAB用完并分配新的TLAB时,才需要同步锁定,虚拟机是否使用TLAB,可以通过-XX:+/-UserTLAB参数来设定。