当经可达性算法筛选出失效的对象以后,并非当即清除,而是再给对象一次重生的机会java
判断是否覆盖finalize()算法
若是finalize()中出现耗时操做,虚拟机就直接中止执行,将该对象清除segmentfault
对象重生或死亡安全
注意:强烈不建议使用finalize()进行任何操做!
若是须要释放资源,请用try-finally或者其余方式都能作得更好.
由于finalize()不肯定性大,开销大,没法保证各个对象的调用顺序.
如下代码示例看到:一个对象的finalize被执行,但依然能够存活ide
/** * 演示两点: * 1.对象能够在被GC时自救 * 2.这种自救机会只有一次,由于一个对象的finalize()最多只能被系统自动调用一次,所以第二次自救失败 * @author sss * @since 17-9-17 下午12:02 * */ public class FinalizeEscapeGC { private static FinalizeEscapeGC SAVE_HOOK = null; private void isAlive() { System.out.println("yes,I am still alive :)"); } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("finalize methodd executed!"); FinalizeEscapeGC.SAVE_HOOK = this; } public static void main(String[] args) throws InterruptedException { SAVE_HOOK = new FinalizeEscapeGC(); // 对象第一次成功自救 SAVE_HOOK = null; System.gc(); // 由于finalize方法优先级很低,因此暂停0.5s以等待它 Thread.sleep(500); if (SAVE_HOOK != null) { SAVE_HOOK.isAlive(); } else { System.out.println("no,I am dead :("); } // 自救失败 SAVE_HOOK = null; System.gc(); Thread.sleep(500); if (SAVE_HOOK != null) { SAVE_HOOK.isAlive(); } else { System.out.println("no,I am dead :("); } } }
运行结果oop
finalize methodd executed! yes,I am still alive :) no,I am dead :(
使用复制算法
实现堆的内存回收,堆被分为新生代
和老年代
性能
因为方法区中存放生命周期较长的类信息、常量、静态变量.
所以方法区就像堆的老年代,每次GC只有少许垃圾被清除.this
方法区中主要清除两种垃圾spa
回收废弃常量和回收对象相似,只要常量池中的常量不被任何变量或对象引用,那么这些常量就会被清除.线程
断定无用类的条件则较为苛刻
即Java堆不存在该类的任何实例
只要一个类被虚拟机加载进方法区,那么在堆中就会有一个表明该类的对象:java.lang.Class.这个对象在类被加载进方法区的时候建立,在方法区中该类被删除时清除.
最基础
的收集算法,后续算法也都是基于此并改进其不足而得.
该算法会从每一个GC Roots出发,依次标记有引用关系的对象,最后将没有被标记的对象清除
把死亡对象所占据的内存标记为空闲内存,并记录在一个空闲列表(free list)之中
当须要新建对象时,内存管理模块便会从该空闲列表中寻找空闲内存,并划分给新建的对象。
清除这种回收方式的原理及其简单,可是有两个缺点
因为Java虚拟机的堆中对象必须是连续分布的,所以可能出现总空闲内存足够,可是没法分配的极端状况。
若是是一块连续的内存空间,那么咱们能够经过指针加法(pointer bumping)来作分配
而对于空闲列表,Java虚拟机则须要逐个访问列表中的项,来查找可以放入新建对象的空闲内存。
第二种是压缩(compact),即把存活的对象汇集到内存区域的起始位置,从而留下一段连续的内存空间。这种作法可以解决内存碎片化的问题,但代价是压缩算法的性能开销。
这种算法会带来大量的空间碎片,致使须要分配一个较大连续空间时容易触发FullGC,下降了空间利用率.
为了解决这个问题,又提出了“标记-整理算法”,该算法相似计算机的磁盘整理,首先会从GC Roots出发标记存活的对象,而后将存活对象整理到内存空间的一端,造成连续的已使用空间,最后把已使用空间以外的部分所有清理掉,这样就不会产生空间碎片的问题
把内存区域分为两等分,分别用两个指针from和to来维护,而且只是用from指针指向的内存区域来分配内存。
当发生垃圾回收时,便把存活的对象复制到to指针指向的内存区域中,而且交换from指针和to指针的内容。复制这种回收方式一样可以解决内存碎片化的问题,可是它的缺点也极其明显,即堆空间的使用效率极其低下。
将内存分红大小相等两份,只将数据存储在其中一块上
堆内存空间分为较大的Eden和两块较小的Survivor,每次只使用Eden和Survivor区的一块。这种情形下的“ Mark-Copy"减小了内存空间的浪费。“Mark-Copy”现做为主流的YGC算法进行新生代的垃圾回收。
在新生代中,因为大量对象都是"朝生夕死",也就是一次垃圾收集后只有少许对象存活
所以咱们能够将内存划分红三块
Eden、Survior一、Survior2
分配内存时,只使用Eden和一块Survior1.
Minor GC
,清除掉废弃的对象,经过这种方式,只须要浪费10%的内存空间便可实现带有压缩功能的垃圾收集方法,避免了内存碎片的问题.
准备为一个对象分配内存时,发现此时Eden+Survior中空闲的区域没法装下该对象
就会触发MinorGC
(新生代 GC 算法),对该区域的废弃对象进行回收.
但若是MinorGC事后只有少许对象被回收,仍然没法装下新对象
全部对象
都转移到老年代
中,而后再将新对象存入Eden区.这个过程就是"分配担保".在发生 minor gc 前,虚拟机会检测老年代最大可用连续空间是否大于新生代全部对象总空间
若成立,minor gc 可确保安全
若不成立,JVM会查看 HandlePromotionFailure
是否容许担保失败
那么会继续检测老年代最大可用的连续空间是否 > 历次晋升到老年代对象的平均大小
则将尝试进行一次 minor gc,尽管此次 minor gc 是有风险的
改成进行一次 full gc (老年代GC)
在回收前,标记过程仍与"清除"同样
但后续不是直接清理可回收对象,而是
这是一种老年代垃圾收集算法.
老年代中对象通常寿命较长,每次垃圾回收会有大量对象存活
所以若是选用"复制"算法,每次须要較多的复制操做,效率低
并且,在新生代中使用"复制"算法
当 Eden+Survior 都装不下某个对象时,可以使用老年代内存进行"分配担保"
而若是在老年代使用该算法,那么在老年代中若是出现 Eden+Survior 装不下某个对象时,没有其余区域给他做分配担保
所以,老年代中通常使用"压缩"算法
当前商业虚拟机都采用此算法.
根据对象存活周期的不一样将Java堆划分为老年代和新生代,根据各个年代的特色使用最佳的收集算法.
Java中根据生命周期的长短,将引用分为4类
咱们平时所使用的引用就是强引用
相似A a = new A();
即经过关键字new建立的对象所关联的引用就是强引用
只要强引用还存在,该对象永远不会被回收
一些还有用但并不是必需的对象
只有当堆即将发生OOM异常时,JVM才会回收软引用所指向的对象.
软引用经过SoftReference类实现
软引用的生命周期比强引用短一些
也是描述非必需对象,比软引用更弱
所关联的对象只能存活到下一次GC发生前.
只要垃圾收集器工做,不管内存是否足够,弱引用所关联的对象都会被回收.
弱引用经过WeakReference类实现.
也叫幽灵(幻影)引用,最弱的引用关系.
它和没有引用没有区别,没法经过虚引用取得对象实例.
设置虚引用惟一的做用就是在该对象被回收以前收到一条系统通知.
虚引用经过PhantomReference类来实现.
Java虚拟机中的垃圾回收器采用可达性分析来探索全部存活的对象。它从一系列GC Roots出发,边标记边探索全部被引用的对象。
为了防止在标记过程当中堆栈的状态发生改变,Java虚拟机采起安全点机制来实现Stop-the-world操做,暂停其余非垃圾回收线程。
回收死亡对象的内存共有三种方式,分别为:会形成内存碎片的清除、性能开销较大的压缩、以及堆使用效率较低的复制。
今天的实践环节,你能够体验一下无安全点检测的计数循环带来的长暂停。你能够分别测单独跑foo方法或者bar方法的时间,而后与合起来跑的时间比较一下。
// time java SafepointTestp // 还可使用以下几个选项 // -XX:+PrintGC // -XX:+PrintGCApplicationStoppedTime // -XX:+PrintSafepointStatistics // -XX:+UseCountedLoopSafepoints public class SafepointTest { static double sum = 0; public static void foo() { for (int i = 0; i < 0x77777777; i++) { sum += Math.sqrt(i); } } public static void bar() { for (int i = 0; i < 50_000_000; i++) { new Object().hashCode(); } } public static void main(String[] args) { new Thread(SafepointTest::foo).start(); new Thread(SafepointTest::bar).start(); } }
java的gc为何要分代?
深刻理解Java虚拟机(第2版)
深刻拆解Java虚拟机
本文由博客一文多发平台 OpenWrite 发布!