垃圾收集器与内存分配策略
一、 GC要做的事情
- 哪些内存需要回收 ?
- 什么时候回收 ?
- 如何回收 ?
二、 什么时候需要GC ?
- 内存溢出、内存泄漏等;
- 垃圾收集收成系统达到更高并发量的瓶颈时。
三、 正文
1 哪些内存需要回收 ?
-
基本不考虑
- 部分: 程序计数器、虚拟机栈、本地方法栈
- 原因: 在编译期基本确定了内存大小
-
主要考虑
- 部分: Java堆和方法区
- 原因: 运行期间动态分配内存。
2 什么时候回收 ?
-
答: 对象已死的时候
#### 问题来了,如何判断对象已死 ?
-
引用计数算法
- 定义: 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值就减1;
任何时刻计数器为0的对象就是不可能再被使用的。 - 好处: 实现简单,效率高
- 缺陷: 很难解决对象之间互相循环引用的问题
- 定义: 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值就减1;
-
可达性分析算法
- 定义: 通过一系列的称为 GC Roots 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用连,
当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。 -
java中,GC Roots对象
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即Native方法)引用的对象
- 定义: 通过一系列的称为 GC Roots 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用连,
-
引用
- 第一和第二种算法判定对象是否存活都与引用有关。
-
JDK 1.2 后,引用有了新的定义,以下顺序引用由强变弱
- 强引用(Strong Reference)
Object obj = new Object(); 程序中类似这样的就是强引用。
- 软引用(Soft Reference)
软引用关联的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进 回收范围之中进行第二次回收;
- 弱引用(Weak Reference)
弱引用关联的对象,只能生存到下一次垃圾收集发生之前。
- 虚引用(Phantom Reference)
虚引用存在的唯一意义: 在对象被回收的时候,收到一个系统通知;
-
补充:
- 强引用是普遍存在。
- 软引用和弱引用都是用来描述非必须的对象。
- 虚引用又称为幽灵引用或者幻影引用。
-
生存还是死亡 ?
- 需要注意的是,通过可达性分析算法得到的不可达的对象,也不是非死不可的。
-
一个对象的死亡,至少经历两次标记过程:
-
1 可达性分析不可达的对象,被第一次标记并且进行一次筛选;
-
筛选的条件:此对象是否有必要执行finalize()方法,,虚拟机将以下两种情况都视为 没有必要执行。
- 1 对象没有覆盖finalize()方法;
- 2 finalize()方法已经被虚拟机调用过
-
-
2 筛选被判定有必要执行finalize()方法,进入 F-Queue的队列,等待被线程执行该方法。
- 线程: 虚拟机自动创建的、优先级低的Finzlizer线程。
-
执行:所谓的执行是指虚拟机会触发这个方法,但不会一直等待它运行结束。
- 原因:如果对象在Finzlize()方法中执行缓慢 或 发生死循环;可能导致F-queue队列中其他对象
永远处于等待,甚至导致整个内存回收系统崩溃。
- 原因:如果对象在Finzlize()方法中执行缓慢 或 发生死循环;可能导致F-queue队列中其他对象
-
-
finalize() 方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记
- 如果对象重新与引用链上的任何一个对象建立关联,(例如: 对象把自己的值 -赋值给某个类的变量或对象的成员变量),移出即将回收的集合;
- 如果对象没有移出回收的集合,基本上确定了被回收。
- 任何一个对象的finalize()方法只会被系统自动调用一次。
- finalize(): 运行代价高昂,不确定性大,无法保证各个对象的调用顺序。
- 使用 try-finally 完全可以替代 finalize()方法。
-
回收方法区
- 方法区(HotSpot虚拟机中的永久代)
-
java 虚拟机规范中,不要求在方法区进行垃圾收集。 因为收集性价比比较低。
- 堆新生代中,一次垃圾回收可以回收 70%- 95%的空间。
- 永久代(方法区) 收集效率远低于此。
-
永久代的垃圾收集分为两部分:
-
废弃常量
- 定义: 回收废弃常量与回收Java堆中的对象类似。
- 判断依据: 常量池中的字面量的回收为例, 一个字符串”a“在常量池中,当前系统中没有String对象是"a",也没有其他
地方引用这个字面量,这时候如果发生内存回收,没有必要的话,这个“a”常量会被系统清理出常量池。 常量池中的其他类(接口)、方法、字段的符号引用也与此类似。
-
无用的类
-
判断依据:
- 该类所有的实例已经被回收,换句话说就是Java堆中不存在该类的任何实例。
- 加载该类的ClassLoader已经回收。
- 该类对应的java.lang.class对象没有在任务地方被引用,无法在任何地方通过反射访问该类的方法。
- 虚拟机可以对满足上述三个条件的对象进行回收,不是必然的。
-
-
- 大量使用反射、动态代理、CGLib 等ByteCode框架、动态生成JSP以及OSGj这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。