一、java内存分区
一共分为6个区域:
1、方法区(也叫非堆区)和堆区,另外还有直接内存即堆外内存,这三个区域都是线程共享的内存区域。
2、虚拟机栈,本地方法栈,程序计数器。
这6个区域,出了程序计数器区域不可能发生内存问题,其他5个区域都可能发生内存问题。
这里指的内存问题包括内存泄漏和内存溢出。
内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
二、各区内存报错分类
1、虚拟机栈
虚拟机栈区域存放的是函数参数,动态链接,方法出口,局部变量表等等栈帧信息。
这个区域通常发生内存溢出,比如死循环,递归深度过深就会报错:StackOverflowError
另外极少情况下,栈内存不够用,才会出现OutOfMemeroyError
2、本地方法栈
这个区域和虚拟机栈非常类似,唯一区域是本地方法栈是为跨语言调用设计的。
一般的java程序员用得很少,一般在跨语言调用时出现,当java调用C的动态链接库里面的方法时,就会出现和虚拟机栈内存溢出一样的两种报错
3、Java堆
这个区域报错很常见,大量的java对象就存在这里。
这个区域中可以支持划分出部分给每个线程独享,叫Thread Local Allocation Buffer即TLAB。
内存溢出时,只会抛出OutOfMemoryErro异常
4、方法区
这个区域存放java类里面静态变量,常量,类加载信息,即时编译信息等。是线程共享的内存区域。也叫非堆(Non-Heap)
这个区域是用永久代的方式进行内存管理。也就是说只有触发Full GC才会回收。通常回收条件苛刻,一旦有问题,很容易内存报错。
内存溢出是,会抛出OutOfMemoryErro异常
另外运行时常量池是方法区的一部分。比如:
String a = "sdfdfsd" ;
String b = "sdfdfsd" ;
直接赋值字符串是在堆里申请空间,把引用存入常量池 ,
当再次赋值时,JVM会判断常量池中是否已经存在,如果存入,不会再到堆申请内存,而是直接返回其引用。所以 a == b 是true
String c = new String("sdfsdf");
String d = new String("sdfsdf");
这个就是完全在堆里。由于c和d是不同的指针,指向的内存区域不一致,所以c == d 为false
5、直接内存(堆外内存)
这个区域也是容易发生内存溢出。通常是在使用JDK的NIO的方法时用到。
HeapByteBuffer heapByteBuffer = ByteBuffer.allocate(1024); DirectByteBuffer directByteBuffer = ByteBuffer.allocateDirect(1024);
这段代码只是为了说明区别,其实HeapByteBuffer 和 DirectByteBuffer是ByteBuffer的内部类,不能被外部引用。上面的代码是不能用的。
allocateDirect就是用的是堆外内存,即当前java进程外部的操作系统剩余内存
allocate是申请java当前进程的正常堆区的内存
控制堆外内存的参数是: -XX: MaxDirectMemorySize,不指定时,和-Xmx即堆空间一样大小。
堆外内存溢出有一个明显特征:
在HeapDump文件中不会看见明显的异常,而且Dump文件很小。并且程序中直接或者间接使用了堆外内存。
比如spark1.6以后版本,在shuffer阶段各节点使用netty进行传输磁盘文件数据,很容易堆外内存溢出。
这次写到这里。