深入理解java虚拟机之自动内存管理机制笔记
之前看周志明的《深入理解java虚拟机》总感觉有点脑袋晕晕的感觉,最近又拿起书来看了看。感觉思路清晰了不少,于是写了个课件,但转念一想这些总归是个人的理解难免会存在一定的局限性于是就把课件写成笔记,让它成为众矢之的,从中想学到写自己未曾注意到或者没想到的东西,这本身就是个不断进步的过程...
Java内存划分
程序计数器:当前线程所执行的字节码的行号指示器
Java虚拟机栈:描述Java方法执行的内存模型,每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息。
本地方法栈:为虚拟机使用的native方法服务。
Java堆:被所有线程共享的一块内存区域,在虚拟机启动时创建。所有的对象实例以及数组都要在堆上分配。
方法区:线程共享的内存区域,存储已被虚拟机加载的类信息、常量、静态变量即时编译器编译后的代码数据等(这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载)。
StackOverflowError:虚拟机栈、本地方法栈 。
OutOfMemoryError:Java堆溢出、运行时常量池溢出、方法区溢出、本机直接内存溢出。
垃圾收集
GC需要完成的三件事:
那些内存需要回收?
什么时候回收?
如何回收?(垃圾收集算法,忽略)
哪些内存需要回收?
GC应该回收这样一些对象,这些对象没有任何引用指向即对象已死。 Java使用根搜索算法判断对象是否存活。基本思路是:通过一系列的名为“GC Roots”的对象作为起始点,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。(个人理解:也就是说在GCRoot中被调用的对象就是活对象) Java语言里,可作为GC Roots的对象包括下面几种: 虚拟机栈(栈帧中的本地变量表)中的引用的对象。 方法区中的类静态属性引用的对象。 方法区中的常量引用的对象。 本地方法栈中Native方法的引用的对象。
什么时候回收?(回收堆区)
GC要回收一个对象至少要经历两次标记过程: 根搜索后发现没有与GC Roots相连接的引用链,进行第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。(当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要”) 如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为F-Queue的队列之中,并在稍后由一条由虚拟机自动建立的、低优先级的Finalizer线程去执行(但虚拟机并不承诺等待它运行完成)。finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue队列中的对象进行第二次小规模的标记。
例:请分析本类的内存存储结构以及关注内存回收问题
public class FinalizeEscapeGC{
public static FinalizeEscapeGC SAVE_HOOK = null; // 静态属性引用,作为GC Roots
public void isAlive () {
System.out.println("yes,i am still alive :)");
}
protected void finalize() throws Throwable {
super.finalize();
System.out.println("called finalize method.");
FinalizeEscapeGC.SAVE_HOOK = this; // 建立引用链
}
public static void main(String[] args) throws Throwable {
SAVE_HOOK = new FinalizeEscapeGC(); // 建立引用链
SAVE_HOOK = null; // 剪短引用链
System.gc();
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("no, i am dead :(");
}
// 任何一个对象的finalize()方法都只会被系统自动调用一次
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("no, i am dead :(");
}
}
}
......(此处省略实战内容)
2012-07-25