`

内存溢出的种类

 
阅读更多
如果JVM里运行的程序, 它的内存堆和持久存储区域的都满了,这个时候程序还想创建对象实例的话,垃圾收集器就会启动,试图释放足够的内存来创建这个对象。这个时候如果垃圾收集器 没有能力释放出足够的内存,它就会抛出OutOfMemoryError内存溢出错误。

在抛出内存溢出错误的时候,一般都会提示内存泄露的种类,

1.堆(heap)内存泄漏
  java.lang.OutOfMemoryError: Javaheap space:
  大家都比较熟悉 ,通过设置-Xms2048m -Xmx4096m可以解决

2.栈(stack)内存泄漏:
  stack over flow当前线程运行期间维护的中间变量等信息过多,
  例如常见的死循环引起stack over flow

3.方法区(permanent heap)内存泄漏

  java.lang.OutOfMemoryError: PermGen space:
  发生的原因和类型装载、类型卸载有直接的关系,
  通过设置-XX:MaxNewSize=256m -XX:MaxPermSize=256m可以解决。  一般情况下,当服务器内存过小,而提供了大量的访问服务时,  可能会缓存过多的数据对象造成堆内存溢出,当web应用不断扩大,
  加载的lib库达到一定大小(4M)后,
  就容易报PermGen OOM,也就是方法区溢出。

在Linux服务器中将参数写入环境变量:
export CATALINA_OPTS="-Xms2048m -Xmx4096m"
export JAVA_OPTS="-XX:MaxNewSize=256m -XX:MaxPermSize=256m"

Xmx 最大不要超过服务器物理内存的80%


1. 栈溢出实验

什么时候会让 Java 栈溢出啊?栈的基本特点就是 FILO(First In Last Out),如果 in 的太多而 out 的太少,就好 overflow 了。而 Java Method Stack 的功能就是保存每一次函数调用时的“现场”,即为入栈,函数返回就对应着出栈,所以函数调用的深度越大,栈就变得越大,足够大的时候就会溢出。所以模拟 Java Method Stack 溢出,只要不断递归调用某一函数就可以。
public class Test {

    private int stackLength = 0;

    public void stackOverflow() {
        ++stackLength;
        stackOverflow();
    }

    public static void main(String[] args) throws Throwable {
        Test test = new Test();

        try {
            test.stackOverflow();
        } catch (Throwable e) {
            System.out.println("stack length: " + test.stackLength);
            throw e;
        }
    }
}

运行结果
stack length: 1052
Exception in thread "main" java.lang.StackOverflowError
    at com.sinosuperman.main.Test.stackOverflow(Test.java:8)
    at com.sinosuperman.main.Test.stackOverflow(Test.java:9)
    at com.sinosuperman.main.Test.stackOverflow(Test.java:9)
    at com.sinosuperman.main.Test.stackOverflow(Test.java:9)
    at com.sinosuperman.main.Test.stackOverflow(Test.java:9)
    ...

2. Heap 内存溢出

堆是用来存储对象的,当然对象不一定都存在堆里(由于逃逸技术的发展)。那么堆如果溢出了,一定是不能被杀掉的对象太多了。模拟 Heap 内存溢出,只要不断创建对象并保持有引用存在即可。
package com.sinosuperman.main;

import java.util.ArrayList;
import java.util.List;

public class Test {

    private static class HeapOomObject {
    }

    public static void main(String[] args) {
        List<HeapOomObject> list = new ArrayList<HeapOomObject>();
        while (true) {
           list.add(new HeapOomObject());
        }
    }
}

运行结果

[GC 17024K->14184K(49088K), 0.1645899 secs]
[GC 26215K->29421K(49088K), 0.0795283 secs]
[GC 35311K(49088K), 0.0095602 secs]
[Full GC 43400K->37709K(49088K), 0.1636702 secs]
[Full GC 49088K->45160K(49088K), 0.1609499 secs]
[GC 45312K(49088K), 0.0265257 secs]
[Full GC 49088K->49087K(49088K), 0.1656715 secs]
[Full GC 49087K->49087K(49088K), 0.1656147 secs]
[Full GC 49087K->49062K(49088K), 0.1976727 secs]
[GC 49063K(49088K), 0.0287960 secs]
[Full GC 49087K->49087K(49088K), 0.1901410 secs]
[Full GC 49087K->49087K(49088K), 0.1673056 secs]
[Full GC 49087K->316K(49088K), 0.0426515 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at com.sinosuperman.main.Test.main(Test.java:14)


3. Method Area 内存溢出

也就是 Non-heap,是用来存储 Object Class Data、常量、静态变量、JIT 编译后的代码等。如果该区域溢出,则说明某种数据创建的实在是太多了。模拟的话,可以不断创建新的 class,直到溢出为止。

以下代码使用到 cglib-2.2.2.jar 和 asm-all-3.0.jar。

package com.sinosuperman.main;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class Test {
    static class MethodAreaOomObject {
    }
    public static void main(String[] args) {
        while(true){
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(MethodAreaOomObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                public Object intercept(Object obj, Method method, Object[] args,
                        MethodProxy proxy) throws Throwable {
                    return proxy.invoke(obj, args);
                }
            });
            enhancer.create();
        }
    }
}

运行结果

Exception in thread "main" net.sf.cglib.core.CodeGenerationException:   java.lang.reflect.InvocationTargetException-->null
    at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:237)
    at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
    at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)
    at com.sinosuperman.main.Test.main(Test.java:24)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:384)
    at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:219)
    ... 3 more
Caused by: java.lang.OutOfMemoryError: PermGen space
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
    ... 8 more

4. Runtime Constant Pool in Method Area 内存溢出
在运行时产生大量常量就可以实现让 Method Area 溢出的目的。运行是常量可以用 String 类的 intern 方法,不断地产生新的常量。

package com.sinosuperman.main;

import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        int i = 0;
        while (true) {
            list.add(String.valueOf(i++).intern());
        }
    }
}

运行结果

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
    at java.lang.String.intern(Native Method)
    at com.sinosuperman.main.Test.main(Test.java:12)

结语
在实际编码中要尽量避免此类错误。不过大多数程序设计的结构比这里的示例要复杂的多,使得问题被隐藏。但 JVM 的内存溢出问题本质上大都可归结为以上这几种情况。


补充介绍 cglib 和 asm

JDK自从1.3版本开始,就引入了动态代理,并且经常被用来动态地创建代理。JDK的动态代理用起来非常简单,但它有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的继承的类,该怎么办?现在我们可以使用CGLIB包。

 CGLIB是一个强大的高性能的代码生成包。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。最流行的OR Mapping工具hibernate也使用CGLIB来代理单端single-ended(多对一和一对一)关联(对集合的延迟抓取,是采用其他机制实现的)。EasyMock和jMock是通过使用模仿(moke)对象来测试java代码的包。它们都通过使用CGLIB来为那些没有接口的类创建模仿(moke)对象。
  CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。除了CGLIB包,脚本语言例如Groovy和BeanShell,也是使用ASM来生成java的字节码。当不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
分享到:
评论

相关推荐

    maven内存溢出解决放啊

    ### Maven内存溢出解决方案 #### 一、引言 在使用Maven进行项目构建时,尤其是在处理大型项目或执行资源密集型任务(如`mvn site`)时,经常会遇到Java堆空间溢出的问题。这类问题通常是由于Maven运行所需的内存...

    Eclipse内存溢出

    ### Eclipse内存溢出问题解析与解决方案 #### 一、内存溢出概述 内存溢出(Memory Leak)是指程序运行过程中使用的内存超过系统所能提供的最大内存限制时所出现的问题。这种情况通常发生在Java等需要进行垃圾回收...

    java内存泄漏与内存溢出关系解析

    "java内存泄漏与内存溢出关系解析" java内存泄漏与内存溢出关系解析是java开发者需要了解的重要知识点之一。下面我们将详细介绍java内存泄漏与内存溢出的关系、原因和解决方法。 一、内存泄漏和内存溢出的定义 ...

    简单了解JAVA内存泄漏和溢出区别及联系

    内存溢出(Out of Memory)是指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存...

    缓冲溢出原理

    缓冲溢出是一种常见的计算机安全漏洞,它源于程序处理数据时对内存空间的不当管理。在程序设计中,当一个变量或数据结构(如数组)的存储空间被超过其预设容量的数据填充时,就会发生缓冲区溢出。这种现象可能导致...

    Q版缓冲区溢出教程.chm

    首先,我们需要了解缓冲区溢出的分类。常见的有栈溢出、堆溢出、全局变量溢出等。栈溢出是最常见的一种,由于函数调用时的返回地址通常保存在栈上,攻击者可以通过溢出修改这个地址,使程序跳转到恶意代码的位置执行...

    如何对大数据进行词频统计.pdf

    然而,直接用HashMap处理40亿个整数会导致内存溢出,因为每个记录需要8字节(4字节的键和4字节的值)。1GB内存只能容纳约1亿条记录,远不足以存储所有数据。 为了解决这个问题,我们需要将大数据分片处理。这可以...

    Linux C语言 内存越界问题总结

    一、内存越界的定义和分类 内存越界是指在计算机程序中,因为访问或修改了超出变量或数组的边界而导致的错误。内存越界可能会导致程序崩溃、内存泄漏、数据丢失等严重问题。在 Linux C 语言中,内存越界问题非常...

    浅谈VueJS SSR 后端绘制内存泄漏的相关解决经验

    Memory Leak 是最难排查调试的 Bug 种类之一,因为内存泄漏是个 undecidable problem,只有开发者才能明确一块内存是不是需要被回收。再加上内存泄漏也没有特定的报错信息,只能通过一定时间段的日志来判断是否存在...

    软件漏洞与缓冲区溢出奚PPT优秀资料.ppt

    **三、缓冲区溢出分类** 1. **栈溢出**:发生在栈内存区域,常因函数调用时的局部变量溢出。 2. **堆溢出**:发生在程序动态分配的内存(堆)上,溢出可能导致堆管理结构破坏。 3. **整型溢出**:当整数运算超出其...

    溢出攻击与防范.pdf

    #### 缓冲区溢出攻击的分类 缓冲区溢出攻击主要可以分为两大类: 1. **栈溢出**:通常发生在函数调用过程中,由于栈帧的特性,可以通过向较小的缓冲区写入超出其容量的数据来覆盖函数的返回地址。 2. **堆溢出**:...

    将idle内存转换为虚拟内存

    3. **易语言内存操作**:教授如何使用易语言的内建函数或模块来查询和操作内存,如读取、写入内存,以及如何处理内存溢出等问题。 4. **虚拟内存设置**:介绍如何在系统层面调整虚拟内存的大小和位置,以优化系统...

    缓冲区溢出攻击原理与防范的研究.pdf

    4. 缓冲区溢出攻击的分类 5. 缓冲区溢出攻击的防范策略 6. 编译器的安全检查 7. 边界检查 8. 地址空间布局随机化 9. 数据执行保护 10. 缓冲区溢出攻击的挑战 11. 缓冲区溢出攻击的防范需要多方面的合作

    QNX嵌入式系统的内存错误分析.pdf

    内存错误的种类很多,包括缓冲区 Underflow、缓冲区溢出、多次释放同一内存块和缓慢的内存泄漏。这些错误可以分为两大类:堆损坏和内存泄漏。 堆损坏是指在内存中分配的内存块被损坏或修改,导致程序崩溃或出现不可...

    dpdk内存池mempool的源码实现

    分配的对象包含保护字段,以帮助调试缓冲区溢出。 四、Stats统计信息 在调试模式中,DPDK Mempool库从池中获取、释放的统计信息存放在mempool结构体中。为了避免并发访问统计计数器,统计信息是per-lcore的。 五...

    行业分类-设备装置-二进制程序循环写内存安全漏洞的检测方法.zip

    1. 内存溢出:当程序试图写入超出分配内存边界的数据时,就可能发生溢出。这可能覆盖相邻的数据,破坏程序的正常运行,甚至使攻击者能够篡改关键信息。 2. 指针错误:循环写可能导致指针失效,使得指针不再指向合法...

    Linux+C语言+内存越界问题总结[文].pdf

    本文将对内存越界问题进行总结,包括内存越界的种类、现象及引起的原因。 进程地址空间的分配 在 Linux 中,每个进程都有其自己的地址空间,包括代码段、数据段、栈段和堆段。栈段是由操作系统自动分配的,用于...

Global site tag (gtag.js) - Google Analytics