概念

内存泄漏Memory Leak:是指程序在申请内存后,无法释放已经获取的内存;一次内存泄漏似乎没什么影响,但是多次堆积后会发生内存溢出

内存溢出Out of Memory:是指程序在运行时获取内存,没有足够的内存供请求者请求

:在java虚拟机中,除了程序计数器,其他几个运行时区域都有可能发生内存溢出

Java堆溢出

java堆用于存储对象实例,只要不断地创建对象,并且保证GC避免清除这些对象,那么随着总容量到达堆的最大容量就会产生内存溢出

代码实现堆溢出(提前设置好XMS和XMX)

static class OOMbject{

    }
    public static void HeapOOM(){
        List<OOMbject> list = new ArrayList<>();
        while (true){
            list.add(new OOMbject());
        }
    }

java堆内存的Out of MemoryError异常是实际应用中最常见的内存溢出异常情况 出现堆内存溢出时,会伴随Java heap space

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

关于虚拟机栈和本地方法栈,在《java虚拟机规范》中描述了两种异常

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,抛出StackOverflowError异常
  • 如果虚拟机的栈允许动态扩展,当前栈容量无法申请足够的内存时,将抛出OutOfMemoryError

以下使用两种代码方式实现内存溢出:

private int stackLength = 0;
private void stackLeak(){
    stackLength++;
    stackLeak();
}
 public static void main(String[] args) {
        JVMDemo jvmDemo = new JVMDemo();
     //  HeapOOM();
        try{
            jvmDemo.stackLeak();
        }catch (Throwable e){
            System.out.println("stackLength="+jvmDemo.stackLength);
            throw e;
        }


    }

yarn nodemanager报内存溢出无法创建线程 堆内存溢出代码_sed

结果:抛出stackOverflowError异常

public  void test() {
    long unused1, unused2, unused3, unused4, unused5,
            unused6, unused7, unused8, unused9, unused10,
            unused11, unused12, unused13, unused14, unused15,
            unused16, unused17, unused18, unused19, unused20,
            unused21, unused22, unused23, unused24, unused25,
            unused26, unused27, unused28, unused29, unused30,
            unused31, unused32, unused33, unused34, unused35,
            unused36, unused37, unused38, unused39, unused40,
            unused41, unused42, unused43, unused44, unused45,
            unused46, unused47, unused48, unused49, unused50,
            unused51, unused52, unused53, unused54, unused55,
            unused56, unused57, unused58, unused59, unused60,
            unused61, unused62, unused63, unused64, unused65,
            unused66, unused67, unused68, unused69, unused70,
            unused71, unused72, unused73, unused74, unused75,
            unused76, unused77, unused78, unused79, unused80,
            unused81, unused82, unused83, unused84, unused85,
            unused86, unused87, unused88, unused89, unused90,
            unused91, unused92, unused93, unused94, unused95,
            unused96, unused97, unused98, unused99, unused100;

    stackLength++;
    test();

    unused1 = unused2 = unused3 = unused4 = unused5 =
    unused6 = unused7 = unused8 = unused9 = unused10 =
    unused11 = unused12 = unused13 = unused14 = unused15 =
    unused16 = unused17 = unused18 = unused19 = unused20 =
    unused21 = unused22 = unused23 = unused24 = unused25 =
    unused26 = unused27 = unused28 = unused29 = unused30 =
    unused31 = unused32 = unused33 = unused34 = unused35 =
    unused36 = unused37 = unused38 = unused39 = unused40 =
    unused41 = unused42 = unused43 = unused44 = unused45 =
    unused46 = unused47 = unused48 = unused49 = unused50 =
    unused51 = unused52 = unused53 = unused54 = unused55 =
    unused56 = unused57 = unused58 = unused59 = unused60 =
    unused61 = unused62 = unused63 = unused64 = unused65 =
    unused66 = unused67 = unused68 = unused69 = unused70 =
    unused71 = unused72 = unused73 = unused74 = unused75 =
    unused76 = unused77 = unused78 = unused79 = unused80 =
    unused81 = unused82 = unused83 = unused84 = unused85 =
    unused86 = unused87 = unused88 = unused89 = unused90 =
    unused91 = unused92 = unused93 = unused94 = unused95 =
    unused96 = unused97 = unused98 = unused99 = unused100 = 0;
}

yarn nodemanager报内存溢出无法创建线程 堆内存溢出代码_java_02

结果:抛出stackOverflowError异常

两种方式表明:无论栈帧太大还是虚拟机栈容量太小,当新的栈帧内存无法分配的时候,都会抛出StackOverflowOver,但是如果允许动态扩展栈容量大小的虚拟机上,相同的代码会导致不一样情况,譬如远古时代的classic虚拟机,这个虚拟机支持动态扩展栈内存的容量.

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

Set<String> set = new HashSet<String>();
int i = 0;
while (true){
    set.add(String.valueOf(i++).intern());
}

在运行时常量池溢出时,在OutOfMemoryError异常后面跟随的提示信息时"PermGenspace’

补充:运行时常量池是方法区的一部分,String.intern()是一个本地方法,它的作用是如果字符串常量池已经包含一个等于此String对象的字符串,则会返回代表其引用对象,否则就会把此string对象包含的字符串添加到字符串常量池中,并返回String对象

Intern测试一:

String str1 = "Intern测试";
System.out.println(str1.intern()==str1);
String str2 ="java";
System.out.println(str2.intern()==str2);
输出结果:true
        true

Intern测试二:

String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
String s4 = s3.intern();

System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s1.equals(s3));
System.out.println(s1 == s4);
输出结果:true
    false
    true
    true

intern测试三:

String str1 = new StringBuilder("Intern").append("测试").toString();
System.out.println(str1.intern()==str1);
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern()==str2);
输出结果:true
   	 false

str2比较返回false,这是因为StringBuilder.toString函数就是调用String的构造方法,new了一个String对象,"java"这个字符串在执行StringBuilder.toString()之前就已经出现过了,字符串常量池中已经有了它的引用,不符合intern方法要求首次遇到的原则,而"intern测试"不在常量池中,要加入到常量池中,再返回原来字符串的引用.也就是说intern方法只有第一次加入常量池才会返回原字符串的引用