JAVA内存区域可分为两大类:线程共享区和线程隔离区。
线程共享区:顾名思义是所有线程公有的一块区域,这块区域包含两个部分:方法区和堆
线程隔离区:每个线程独占一份的区域,这块区域包含三个部分:虚拟机栈、本地方法栈和程序计数器
方法区:存放类的元数据(类的方法代码,变量名,方法名,访问权限,返回值等等),常量和静态变量,以及及时编译器编译后的代码。
虚拟机栈:存储当前线程方法的执行情况,每个方法执行的时候都会创建一个栈帧,方法开始被调用到调用结束对应着栈帧的入栈和出栈的过程。栈帧会存储方法内的局部变量表,操作数栈(用来进行加减乘除等操作符计算的地方。比如int a= 1+2。1入操作数栈,2入操作数栈,计算和为3,3出操作数栈,将3复制给局部变量表中的变量a)、动态链接(指向方法区的该方法的元数据区域)、方法出口(方法的返回地址)等信息
本地方法栈:同虚拟机栈,不过针对的是本地方法,也就是native修饰过的方法
程序计数器:线程执行的字节码行号指示器(表示下一个该执行哪行代码)
堆:除去方法区的非基础数据类型的实例数据都在堆上存储(Class对象也在堆上存储)。堆同时分为新生代和老年代
老年代:无法在新生代分配的大对象,在新生代经过多次垃圾回收依然存活的对象可晋升至老年代
新生代:一般情况下对象直接在新生代分配空间(大对象除外)。新生代又分为两个Survivor空间和一个Eden
Eden:一直在使用,新生代gc时复制完后会清空该区域。空间大小一般为Survivor:Eden:Survivor = 1:8:1
Survivor:同一时间只有一个Survivor空间在被使用,另一个空闲状态。每当新生代gc时,会把Eden和使用中的Survivor空间中的存活对象复制到空闲Survivor中,gc结束之后,两个Survivor空间状态切换
首先区分一下平时排查问题时遇到的两个概念:内存泄漏和内存溢出
内存泄漏:不再使用的对象依然占据着内存空间无法被GC回收。(此时内存空间可能依然充足,只是泄漏问题一般在内存不足排查问题时才会被发现)。解决办法:改代码,不想改代码而又财大气粗的直接加内存空间
内存溢出:内存空间不足以支撑程序继续正常运行。解决办法:加内存空间,如果经费有限的可以考虑优化代码(没有bug的情况下 难度较大收益较小)。
1、方法区溢出:存储的类或常量信息太多。一般情况下不会出现,但是在CGLib等动态创建类的时候,可以关注该区域的内存使用情况。
JDK1.6之前的常量溢出导致的方法区溢出(为什么要求1.6,具体缘由可参见String类型的存储方式):
/**
* VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
*/
// 方法区设置的越小越容易出现内存溢出,设置过大可能会抛int超过限制的异常
public class RuntimeConstantPoolOOM {
public static void main(String[]args) {
List list = new ArrayList();
int i = 0;
while(true) {
list.add(String.valueOf(i++).intern());
}
}
}
类信息太多导致的溢出:
/**
* VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
*/
public class JavaMethodAreaOOM {
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object obj, Method method,
Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
}
}
static class OOMObject {
}
}
2、堆的溢出:对象实例过多,不停的创建gc不能回收的实例对象即可。
/**
* VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
*/
public class HeapOOM {
static class OOMObject {
}
public static void main(String[] args) {
List list = new ArrayList();
while (true) {
list.add(new OOMObject());
}
}
}
3、虚拟机栈的溢出:栈帧过多导致溢出(无限递归调用)。栈帧过大导致溢出(局部变量非常多,存储的是基本数据类型,很难复现,理论情况下存在)。同时因为虚拟机栈是线程私有的,所以当线程过多,无足够内存分配时也会溢出(无限创建线程)。具体案例可自行思考,如有需要可留言。