深入理解java虚拟机第三章总结1

    第二章中说了程序计数器,虚拟机栈,本地方法栈三个区域随着线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出有条不紊的执行着出栈和入栈的操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的(大体上认为是编译期可知的),因此这几个区域的内存分配和回收都具备确定性,不需要过多的考虑回收的问题,因此垃圾回收主要在堆和方法区进行。

一:判断对象是否存活的方法

    1:引用计数器算法:给对象中添加一个引用计数器,每当有一个地方引用它计数器加1;引用失效计数器减1.任何时候计数器为0的对象就是不可能再被使用的。使用简单效率很高,但是不用,可能两个方法无外部引用,但存在方法内部互相调用导致引用计数不为0不被回收。因此不用词方法。

    2:可达性分析法:通过一系列的称为"GC Roots"的 对象作为起始点,从这些节点开始向下搜索当一个对象到GC Roots没有任何引用链相连时,证明此对象是不可用的,可被回收。可作为GC Roots的对象包括:①虚拟机栈(栈帧中的本地变量表)中引用的对象;②方法区中类的静态属性引用的对象;③方法区中常亮引用的对象;③本地方法栈中JNI(一般说的Native方法)引用的对象。

    引用被分为4种:强引用,软引用,弱引用,虚引用,依次减弱。   强引用:指在程序代码之中普遍存在的,类似于"Object obj=new Object()"这类的引用,只要强引用还存在,垃圾收集器永远不会回收被引用的对象;   软引用:用来描述一些还有用但非必要的对象,在系统将要发生内存溢出异常之前将会把这些对象列入回收范围之中镜像第二次回收;    弱引用:用来描述非必需对象,被弱引用关联的对象只能生存到下一次垃圾回收发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象;    虚引用:一个对象是否有虚引用的存在,完全不用对其生存时间构成影响,也无法通过虚引用来获取一个对象实例,为一个对象设置虚引用关联的唯一目的就是能在这个对象呗收集器回收时收到一个系统通知。

    对象的生存还是死亡:在可达性分析算法中不可达的对象不一定是“非死不可”的,他们只是暂时处在于缓刑阶段,要真正宣告一个对象死亡,至少需要两次标记过程:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。 如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会放置在一个叫做F-Queue的队列中,并稍后有虚拟机自动建立一个低优先级的Finalizer线程去执行它。执行也只是说会触发这个方法并不一定承诺等待到它运行结束,因为一个对象在finalize()方法中执行缓慢,或者发生了死循环可能会导致F-Queue队列中其他对象永久处于等待状态导致整个内存回收系统奔溃。如果对象要在finalize()中成功拯救自己 ----只要重新与引用链上的任何一个对象建立关联即可,比如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那么第二次标记时它会被移除"即将回收"的集合;如果还逃脱那就基本真的被回收了。  finalize()方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不用它。

     回收方法区:永久代的垃圾收集主要回收两个部分的内容:废弃常量和无用的类。废弃常量郁回收堆中的对象相似就不描述了。判断一个类是否是“无用的类”:1)该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例;2)加载该类的ClassLoader已经被回收;3)该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。满足上述三个条件的无用类可以进行回收。

 

二 垃圾收集算法

    1:标记-清除算法:分成两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所以被标记的对象。不足之处在于:标记和清除两个效率都低,空间问题,标记清楚后会产生大量不连续的内存碎片,空间碎片太多容易导致以后再程序员运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前出发另一次垃圾收集动作。

    2:复制算法:将内存按照容量划分为大小相等的两块,每次值使用其中一块,当这块的内存用完了,就将还活着的对象复制到另外一块上去,然后把已使用过的内存空间一次清除掉。不需要考虑内存碎片等复杂情况只需要移动堆顶指针,按照顺序分配内存即可,简单高效。现在的商用虚拟机都采用这个方法来回收新生代,因为新生代中对象98%都是“朝生夕死”不需要1:1比例划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor空间。当回收时将Eden和Survivor中还存活着的对象一次性地复制到另一块Survivor空间上,最后清理掉Eden和刚才使用过的Survivor空间。Eden和Survivor大小比例是8:1,也就是每次新生代中可使用的内存空间为整个新生代容量的90%,只要10%浪费,当Survivor空间不够用时需要依赖其他内存(这里只老年代)进行分配担保。

    3:标记-整理算法:复制算法适合新生代,老年带不适合。标记-整理算法标记过程和之前一样,但后续步骤不是直接对可回收对象进行清理,而是让所以存活的对象都往一端移动,然后直接清理掉端边界以外的内存

    4:分代收集算法:分代收集根据不同代进行回收新生代用复制算法;老年代用标记-清理或者标记-整理算法。

三 HotSpot的算法实现  

    1 枚举根节点:从可达性分析为例作为GC Roots的节点主要在全局性的引用(常量以及静态属性)与执行上下文(栈帧中的本地变量表)逐一检查很耗费时间。另外对时间的敏感还体现在GC停顿上,.因为这箱分析工作必须在一个能确保一致性的快照中进行—这里“一致性”意思是指在整个分析期间整个执行系统看起来就像被冻结在某个时间点上,这个点事导致GC进行时必须停顿所有Java执行线程。HotSpot采用了OopMap的数据结构来得知哪些地方存放着对象引用。在类加载完成的时候HotSpot就把对象内什么偏移量上是什么类型的数据 计算出来,在JIT编译过程中,也会在特定的位置记录下栈和寄存器中哪些位置是引用,这样GC在扫描时就可以直接得知这些信息了。

  2  安全点:在OopMap协助下,HotSpot可以快速且准确地完成GC Roots枚举,也会存在问题 为每一条指令都生成对应的OopMap需要大量的额外空间,这样GC空间成本会很高。实际上也没有为每条指令都生成OopMap,只是在特定位置记录了这些信息,称为安全点SafePoint,即程序执行时只有在到达安全点时才能暂停。如何在GC发生时让所有线程都跑到最近的安全点再停顿下来,一种是抢断式中断,一种是主动式中断,抢断式中断指的是,GC发生时把所有线程全部中断,发现没有到安全点的线程恢复起来跑到安全点。主动式中断指的是设置一个标志,让所有线程去轮询这个标志,轮询标志和安全点是重合的,判断在轮询标志中的线程就自己中断挂起。当前HotSpot使用的是主动式中断。

   3 安全区:  safepoint机制保证了程序执行时,在不太长的时间内就会遇到可进入GC的Safepoint。但是,程序“不执行”的时候呢?所谓的程序不执行就是没有分配CPU时间,典型的例子就是线程处于Sleep状态或者Blocked状态,这时候线程无法响应JVM的中断请求,“走”到安全的地方去中断挂起,JVM也显然不太可能等待线程重新被分配CPU时间。对于这种情况,就需要安全区域(Safe Region)来解决。

      安全区域是指在一段代码片段之中,引用关系不会发生变化。在这个区域中的任意地方开始GC都是安全的。我们也可以把Safe Region看做是被扩展了的Safepoint。在线程执行到Safe Region中的代码时,首先标识自己已经进入了Safe Region,那样,当在这段时间里JVM要发起GC时,就不用管标识自己为Safe Region状态的线程了。在线程要离开Safe Region时,它要检查系统是否已经完成了根节点枚举(或者是整个GC过程),如果完成了,那线程就继续执行,否则它就必须等待直到收到可以安全离开Safe Region的信号为止。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值