章节内容概要
本章主要讲解 “Java内存区域与内存溢出” 的一些常见原因及分析方法;以应对线上环境服务器出现内存溢出时该知道如何着手去排查。
先了解虚拟机运行时数据区域
运行时数据区域程序计数器:
程序计数器是一块较小的内存空间,可以把它看做是程序当前执行线程字节码的行号指示器;因为单核CPU同一时刻只能执行一个线程中的指令,为了保证线程切换后可以恢复到当前线程 “上一次执行到的字节码位置” 继而继续执行。每个线程都拥有一个独立的程序计数器,各线程间互不影响。
虚拟机栈:
Java方法执行时的内存模型,每个方法在被调用执行时都会创建一个叫 “栈帧” 的内存块,用于存储 局部变量,操作数栈,动态链接,方法出口等信息。每个方法从调用至执行完成的过程就对于这一个栈帧在虚拟机中入栈到处栈的过程。它与线程的生命周期相同。
本地方法栈:
与“虚拟机栈”相似,不同的是本地方法栈只为虚拟机使用到的 Native 方法服务。Native 方法可以理解为java调用的非本平台的接口,如:调用C的xxx方法。
堆:
虚拟机使用最多也是分配内存最大的一块,主要用于存放程序中创建的对象实例。
Java虚拟机规范描述:所有的对象实例以及数组都要在堆上进行分配。
方法区:
用于存储已被虚拟机加载的 类信息,常量,静态变量,即时编译器编译后的代码等数据。
运行时常量池:属于方法区的一部分,用于存放编译期生成的各种字面量和符号引用,这部分内容在类加载后进入方法区的运行时常量池中。
补充部分:
直接内存:不属于虚拟机运行时数据区的一部分,Java NIO包中涉及到可以使用Native函数库分配虚拟机之外内存的方式。
虚拟机可能出现的内存溢出及异常
OutOfMemoryError
-
堆溢出
由于堆中创建的对象数量达到设置的最大堆容量限制后出现。表象:java.lang.OutOfMemoryError: Java heap space ..
public class HeapOOM {
/**
* VM Args: -Xms20m -Xmx20m -XX:HeapDumpOnOutOfMemoryError
*/
static class OOMObject {}
public static void main(String[] args) {
List list =new ArrayList();
while(true) {
list.add(new OOMObject());
}
}
}
-
方法区和运行时常量池溢出
常量池中过多的添加常量或使用了类似CGLib的字节码技术,动态加载过多的Class到方法区,都可能导致如下异常。
表象:java.lang.OutOfMemoryError: PermGen space ..
/**
* VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
*/
public class RuntimeConstanctPoolOOM {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}
StackOverflowError
-
虚拟机栈或本地方法栈溢出
单线程情况,栈中使用内存超过设置内存,可能原因: 栈帧太大 或 虚拟机栈容量设置太小而导致的内存无法分配。
表象:java.lang.StackOverflowError
优化思路:
配置增加栈内存分配大小 (-Xss);
优化代码,减少方法中的(局部变量,操作数栈..),也就是减小栈帧大小;
多线程情况,创建线程导致的内存溢出;
表象:OutOfMemoryError: unable to create new native thread
优化思路:
在不能减少线程数的情况下,只能通过减少最大堆内存和减少每个栈的容量来换取更多的线程数;
直接内存溢出
此原因导致的内存溢出一般因使用NIO导致,上文中也提到NIO可直接使用机器内存(DirectByteBuffer 类);
如何分析由直接内存导致的内存溢出,明显特征是在 Heap Dump 文件中不会看见明显的异常,如果出现 OOM 后 Dump 文件又很小,程序中直接或间接使用了NIO,则可以排查下这方面的原因。
Socket异常
-
IOException: Too many open files
原因分析: 每个Socket连接都拥有 Receive 和 Send 两个缓存区,占用大小各种占用一定大小( 如:37KB,25KB),当创建的连接过多时,内存已无法进行再分配,就会抛出此异常。 -
SocketException:Connection reset
原因分析: 调用服务响应时间延迟,导致等待的线程和Socket连接大量创建及等待,超过虚拟机可承受的量时,虚拟机进程崩溃。
优化思路:
优化服务提供方响应时间;
优化系统交互方式,减少系统实时连接,调整为消息队列实现;
参考资料
书籍:深入理解Java虚拟机,操作系统概念
虚拟机讲解连载文章
待更新..
网友评论