摘自: http://www.ibm.com/developerworks/cn/java/j-lo-jvm-perf/
JVM 的实现
Sun Hotspot、IBM J9、Oracle JRockit
一. Java 堆和栈
- 类装载子系统:通过类的全限定名(包名和类名,网络装载还包括 URL)将 Class 装载进运行时数据区;
- 方法区:Class 对于所有方法和 static 静态数据的定义存储在这里,它就像一张表或数组,让程序执行时在这里找到相应方法的 Java 字节码和静态数据;
- Java 堆:Java 对象的持久化存储区,从类实例化而来的对象存储在此,垃圾收集也在此进行,若是空间不够容纳当前所有对象,Out Of Memory 的异常将会抛出,对 Java 堆和垃圾收集的认识对应用性能调优很关键; 创建的 Java 对象(包括数组,数组也是一种对象)分配在堆中,垃圾收集对象来释放空间; 最常设置的堆大小参数有:-Xms,设置堆的初始大小;-Xmx,设置堆空间的最大值;-Xmn,设置年轻代空间大小;-Xmo,设置年老代空间大小
- Java 栈:Java 方法的字节码执行的地方,方法中局部变量的生命周期都在栈中,栈的大小是我们要考虑的一个关键点,它直接决定了方法调用的层数,这对递归程序来说尤为重要。我们所用的 JVM 都是基于 Java 栈的运行机制,而有一个例外的实现,Google 移动设备操作系统 Android 的虚拟机 Dalvik 则是基于寄存器的机制(Dalvik 虽然支持 Java 语言开发,但从虚拟机的角度看,并不符合 Java 标准),关于虚拟机实现时,栈和寄存器机制的比较,请参考论文“Virtual Machine Showdown: Stack Versus Registers”; 栈划分为操作数栈、栈帧数据和局部变量区,方法中分配的局部变量在栈中,同时每一次方法的调用都会在栈中分配栈帧. 对于基于栈的 Java 虚拟机,方法的调用和执行伴随着压栈和出栈操作。每个线程有各自独立的栈,由虚拟机来管理栈的大小,但我们应该对它的大小有个概念。栈的大小是把双刃剑,如果太小,可能会导致栈溢出,特别是在该线程内有递归、大的循环时出现溢出的可能性更大,如果过大,就会影响到可创建栈的数量,如果是多线程的应用,就会导致内存溢出。通过 -Xss可以设置 Java 栈的最大值,默认值为 256K。
- 程序计数器:对于基于栈实现的 JVM,这几乎是唯一寄存器了,它用来指示当前 Java 执行引擎执行到哪条 Java 字节码,指针指向方法区的字节码;
- 本地方法栈:这是 Java 调用操作系统本地库的地方,用来实现 JNI(Java Native Interface,Java 本地接口);
- 执行引擎:JVM 的心脏,控制装入 Java 字节码并解析;
- 本地接口:连接了本地方法栈和操作系统库。
二. Java内存泄漏例子
下面的pop1会引起内存泄漏
public class Stack {
private static final int MAXLEN = 10;
private Object stk[] = new Object[MAXLEN];
private int stkp = -1;
public void push(Object p) {
stk[++stkp] = p;
}
public Object pop1() {
return stk[stkp--];
}
public Object pop2() {
Object p = stk[stkp];
stk[stkp--] = null;
return p;
}
}