内存回收对象:堆内存和方法区内存 (注:程序计数器、虚拟机栈和本地方法栈的内存随着方法或线程的结束时,自然被回收)
内存回收的问题:1.哪些内存需要回收?
2.什么时候回收?
3.如何回收?
1.哪些内存需要回收(或可以回收)?
a.堆中如何判断哪些还“或者”,哪些已经失去“死去”(不可能再被使用)
算法: 1.引用计数法
思想:给对象添加一个引用计数器,每当有一个对象引用它时,计数器加1,当引用失效时,计数器减1.
任何时刻计数器为0的对象就是不可能再被使用的。
优点:实现简单,判定效率高
问题:对象相互引用时,对象不能被有效回收
2.可达性分析算法 主流JVM采用的方法
思想:1.以一系列“GC Roots”对象为起点,从这些节点向下搜索,搜索走过的路径成为引用链,当一个对象到GC Roots没有任何引用链相连(或者说从GC Roots到该对象不可达)时,则证明该对象是不可用的。
2.当对象被认为不可达时,进行第一次标记,并判断对象是否有必要执行finalize()方法。
判断标准:1.对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机调用过。
则认为没有必要执行finalize方法否则认为有必要执行
注:任何一个对象的finalize()方法只会被系统自动调用一次。
3.若对象被认为没有必要执行finalize则对象应该被回收。否则对象进入一个叫F-Queue的队列当中。
虚拟机自动创建F-Queue队列,并且建立一个线程去执行finalize方法给对象一次“自救”机会,但不保证等待线程运行结束,
以防止对象finalize方法执行时间过长。
finalize线程执行一段后,虚拟机进行第二次标记:若finalize线程内,对象重新引用链上的某个对象简历关联。则
对象被移出“即将回收”的集合;否则对象基本上就真的被回收了。
GC Roots:1.虚拟机栈中(本地变量表)引用的对象.
2.方法区中类静态属性引用的对象。
3.方法区中常量引用的对象
4.本地方法(Native 方法)中引用的对象。
b.方法区回收
内容:废弃常量和无用的类
1.废弃常量:类似于可达性分析法 即没有对象引用该常量
2.无用的类:必须同时满足3个条件:a.该类的所有实例都被回收,即堆中不存在该类的实例。
b.加载该类的ClassLoader已被回收
c.该类对应的Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
注:无用的类仅代表该类可以被回收,不代表一定会被回收。
2.如何回收(回收策略)?
1.“标记-清除”(Mark-Sweep)算法 ————————最基础算法 后续算法均是对其的改进
思想:1标记阶段:判断哪些对象需要被回收
2.清除阶段:统一回收所有被标记的对象。
问题:1.效率问题,标记和清除两个过程效率都不高
2.空间问题,标记清除后产生大量不连续的内存碎片。碎片过多造成分配较大对象时无法找到合适对象而出发另一次垃圾收集动作。
2.复制(Copying)算法
思想:将内存划分为n块,每次只使用其中一块(或几块),当一块(或几块)内存使用完毕,就将其中存活的对象复制到另一块(或几块)上,然后把已使用过的内存全部清理。
优点:不会产生 碎片,实现简单 运行高效
缺点:1.内存浪费(只使用2部分空间来分配对象)
2.内存担保问题:假如内存分为A、B两块,当从A复制到B的内存大小超过B空间内存时,必须由其他内存来做分配担保,已保证从A复制出来的对象都可以被分配内存。
3.当对象存活率较高时需进行较多的复制操作,效率将变低。
应用:hotspot采用复制算法来收集新生代,内存分为一块大的Eden空间和两块小的Survivor空间,大小比例为8:1:1,每次使用Eden和一个Survivor空间,回收时将Eden和Survivor中活着的对象全部复制到另一块Survivor空间,最后清理掉Eden和用过的Survivor空间。 内存浪费:10% 内存担保:老年代内存
3.标记-整理算法(Mark-Compact)算法
思想:1.标记阶段:判断哪些对象需要被回收
2.整理阶段:将所有存活的对象向一端移动,最后清理掉端边界以外的内存。
优点:1.没有内存消耗
2.对于存活率较高的内存,效率较高
应用:老年代GC一般采用此算法。
4.分代收集算法
思想:将Java堆划分为新生代(Young generation)和老年代(Tenured generation)。
新生代中对象存活率较低,采用复制算法,老年代做分配担保;
老年代中对象存活率较高,采用"标记-清理"或"标记-整理算法"。