《深入理解Java虚拟机》——内存自动管理:内存分配与回收

来源:互联网 发布:asp.net 的优势php 编辑:程序博客网 时间:2024/06/06 20:15

标签(空格分隔): JVM


先讲讲自动内存回收——GC

GC的三大问题——What & When & How

  1. 哪些内存需要回收?
  2. 何时回收?
  3. 如何回收?

什么是内存溢出,内存泄露?

GC的三大问题(一)——哪些内存需要回收?

回收区域:Java堆和方法区(对象的创建是动态的,不确定性)(线程私有区域不考虑)回收对象:“死亡的”对象实例

如何定义“死亡”(Java堆)

1. 引用计数法

引用计数器简单,效率高,不可靠无法解决“对象之间循环引用”
/*示例代码*/public class ReferenceCountingGC {    public ReferenceCountingGC instance = null;    private static final int _1MB = 1024 * 1024;    private byte[] bigSize = new byte[2 * _1MB];    public static void testGC() {        ReferenceCountingGC objA = new ReferenceCountingGC();        ReferenceCountingGC objB = new ReferenceCountingGC();        objA.instance = objB;        objB.instance = objA;        objA = null;        objB = null;        System.GC();    }}

2. 可达性分析

GC Roots + 引用链被Java采用在Java中,可作为GC Roots的对象有:    1.    2.    3.    4.

3. 引用类型:强软弱虚

Java中引用的定义:    如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表中一个引用。判断对象存活仅与引用有关,太过”狭隘““食之无味,弃之可惜”——缓存
Type Purpose GC Strong Object obj = new Object() 只要强引用存在,就永远不会回收被引用的对象 Soft 有用但非必需 发生内存溢出异常之前,将此类对象列进回收范围进行第二次回收 Weak 非必需对象 下一次GC被回收 Phanton 无实际用途 唯一的作用是在该对象被回收时,给出一个系统通知

如何定义“死亡”(方法区)

废弃常量

1. 常量池中的常量不被引用

无用的类

1. 该类的所有实例都已经被回收——Java堆中不存在该类的实例 2. 加载该类的ClassLoader已经被回收3. 该类对应的java.lang.class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

GC的三大问题(二)——何时回收?

两个阶段:    1.第一次标记    2.第二次标记

第一次标记:

1. 可达性分析:若某一对象“不可达”,则标记2. 筛选: 若对象被选中,则加入F-Queue队列    当对象未覆盖finalize()方法或者此方法已经被虚拟机调用过,则不被选中

第二次标记:

3. GC对F-Queue队列中的对象执行第二次小规模标记    对象在finalize()中实现“自我拯救”,退出F-Queue队列4. 虚拟机自动建立”低优先级“的Finalizer线程执行回收动作——调用对象的finalize()方法

“自我拯救”????

public class FinalizeEscapeGC {    public static FinalizeEscapeGC SAVE_HOOK = null;    public void isLive() {        System.out.println("I am still alive!");    }    @Override    protected void finalize() throws Throwable {        super.finalize();        System.out.println("finalize method executed!");        SAVE_HOOK = this;    }    public static void main(String[] args) {        SAVE_HOOK = new FinalizeEscapeGC();        /* 实现自我拯救 */        SAVE_HOOK = null;        System.GC();        Thread.sleep(500);        if (SAVE_HOOK != null) {            isLive();        } else {            System.out.println("I'm dead!");        }        /* finalize()只能执行一次 。不会被回收???? */        SAVE_HOOK = null;        System.GC();        Thread.sleep(500);        if (SAVE_HOOK != null) {            isLive();        } else {            System.out.println("I'm dead!");        }    }}

finalize()

Forget it!

GC的三大问题(三)——如何回收?

GC算法

1. Mark-Sweep:效率低 + 内存碎片多    适合对象存活率较高的情况,适合回收老年代2. Copying:把活着的对象复制到另一块内存空间,再清理已使用的那块;    适合对象存活率较低的情况,被主流商业虚拟机采用回收新生代。(“朝生夕死”)    Eden:Survivor:Survivor = 8:1:1,每次新生代可用内存空间占整个新生代容量的90%    老年代分配担保(Handle Promotion)3. Mark-Compact:让存活的对象向内存的一端移动,直接清理掉端边界以外的内存;    适合对象存活率较高的情况,适合回收老年代4. Generational Collection    “因地制宜”   

Hotpot的GC算法实现

我们始终关注算法的执行效率!

枚举根节点

安全点

安全区域

垃圾收集器(了解)

GC日志(了解)

内存分配策略

1. 大多数情况下,对象在新生代的Eden区上分配。当Eden区上没有足够空间时,虚拟机发起一次 Minor GC(Copying)2. 大对象直接进入老年代    需要大量连续内存空间的Java对象,例如很长的字符串和byte[4 * _1MB]    "朝生夕死"的“短命大对象”是JVM最不想看到的,写程序也应尽量避免    经常出现大对象导致内存即使仍有不少空间,也需要提前触发GC以获得足够多的连续空间3. 长期存活的对象将进入老年代    对象年龄计数器。    如果对象在Eden区出生,经过第一次Minor GC后仍然存活,并且能被Survivor区容纳,将被移动到Survivor空间中,对象年龄变为1.    接下来,对象在Survivor区每“熬过”一次Minor GC,年龄加一,到达阈值(15),进入老年代4. 动态对象年龄判定    如果Survivor空间中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于等于该年龄的对象可以直接进入老年区,无需达到“阈值年龄”!5.  空间分配担保
阅读全文
0 0
原创粉丝点击