运行时数据区域
1.程序计数器
字节码的行号指示器,分支,循环,太哦转,异常,线程恢复都需要这个,CPU中上下文切换就涉及到线程恢复
是唯一一个在JVM中没有规定OutOfMemoryError的情况
2.Java虚拟机栈
生命周期和线程相同,描述Java方法执行时的内存模型,包括局部变量表,操作数栈,动态链接,方法出口等
局部变量存放基本类型,对象引用(对象的指针),returnAddress
long和double会占用两个局部变量空间(Slot)
栈深大于虚拟机要求,*;若可动态扩展,申请不到足够内存,OutOfMemoryError(OOM)
3.本地方法栈 Native Method Stack
native是与C++联合开发的时候用的!
使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用
。 这些函数的实现体在DLL中,JDK的源代码中并不包含,你应该是看不到的。对于不同的平台它们也是不同的。这也是java的底层机制,实际上java就是在不同的平台上调用不同的native方法实现对操作系统的访问的。总而言之:
- native 是用做java 和其他语言(如c++)进行协作时使用的,也就是native 后的函数的实现不是用java写的。
- 既然都不是java,那就别管它的源代码了,我们只需要知道这个方法已经被实现即可。
- native的意思就是通知操作系统, 这个函数你必须给我实现,因为我要使用。 所以native关键字的函数都是操作系统实现的, java只能调用。
- java是跨平台的语言,既然是跨了平台,所付出的代价就是牺牲一些对底层的控制,而java要实现对底层的控制,就需要一些其他语言的帮助,这个就是native的作用了
具体和java虚拟机栈类似,会OOM
4.Java堆
堆是被所有线程共享的内存区域,主要就是负责对象示例的空间分配,以及垃圾回收,垃圾回收分代但是归根结底是对象。
通过-Xmx和-Xms控制内存的扩展,不需要连续的内存,不够也会OutOfMemortError
5. 方法区
用于储存类信息,常量,静态变量,即时编译器编译后的代码等数据。被描述成堆的一个逻辑部分,但是有个‘非堆’的别名,会OOm
6.运行时常量池
是方法区的一部分,用于存放编译期生成的各种字面量和符号引用,具备动态性,运行期间也可能将新的常量放入。
也包括字符串常量,例子 String类的intern()方法,会OOM
7.直接内存
nio,New input/output 直接内存,基于Channel和Buffer方式,在一些场景中避免在Java堆和Native堆中来回复制数据
也会OOm
OOM实例
1.Java堆溢出
-Xms20m -Xmx20m 表示最小值和最大值都是20M,不可扩展
-XX:+HeapDumpOnOutOfMemoryError 内存异常时Dump出快照进行分析
/** * VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError * @author zzm */
public class HeapOOM {
static class OOMObject {
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
while (true) {
list.add(new OOMObject());
}
}
}
2. 虚拟机栈和本地方法栈
-Xss128k 设置栈内存容量
//单线程*
/** * VM Args:-Xss128k * @author zzm */
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) throws Throwable {
JavaVMStackSOF oom = new JavaVMStackSOF();
try {
oom.stackLeak();
} catch (Throwable e) {
System.out.println("stack length:" + oom.stackLength);
throw e;
}
}
}
多线程OOM,此时不断申请线程,因为每个线程有自己的栈,所以导致系统内存减去堆内存,方法区内存,如果每个线程要求很大的内存,能申请的线程就少了,OOM。只能通过减小堆最大内存和栈容量来处理。
/** * VM Args:-Xss2M (这时候不妨设大些) * @author zzm */
public class JavaVMStackOOM {
private void dontStop() {
while (true) {
}
}
public void stackLeakByThread() {
while (true) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
}
public static void main(String[] args) throws Throwable {
JavaVMStackOOM oom = new JavaVMStackOOM();
oom.stackLeakByThread();
}
}
3.方法区和运行时常量池溢出
运行时常量池时方法区的一部分,String#intern
方法中看到,这个方法是一个 native 的方法。“如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回”。
JDK 1.6 以前常量池在永久代PermSize,1.7以后在Java Heap区,所以下面的代码在1.6中会OOM
/** * VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M * @author zzm */
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
// 使用List保持着常量池引用,避免Full GC回收常量池行为
List<String> list = new ArrayList<String>();
// 10MB的PermSize在integer范围内足够产生OOM了
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}
一个有趣的例子
//jdk 1.6 false false string.intern在永久代,StringBuilder在Java堆
//jdk 1.7 true false(String#intern 方法时,如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象。但是"java"字符串不是首次出现)
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
public static void main(String[] args) {
String str1 = new StringBuilder("中国").append("钓鱼岛").toString();
System.out.println(str1.intern() == str1);
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2);
} }
}
方法区用来存放Class相关的信息,如类名,访问修饰符,常量池,字段描述,方法描述。用CGLib生成动态类。方法区的OOM比较常见,类被回收判定比较苛刻。
/** * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M * @author zzm */
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 {
}
}
4. 本机直接内存溢出
DirectMemory容量-XX:MaxDirectMemorySize指定,如果不指定,默认Java堆最大值-Xmx
/** * VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M * @author zzm */
public class DirectMemoryOOM {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while (true) {
unsafe.allocateMemory(_1MB);
}
}
}
DM导致内存溢出的一个明显特征是Dump文件很小。