JVM--垃圾收集算法

1、为什么了解GC(垃圾收集)和内存分配

  需要排查各种内存溢出、内存泄露问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,需要对这些”自动化”的技术实施必要的监控和调节。

2、内存回收区域 

  程序计数器,虚拟机栈、本地方法栈这3个区域是线程私有的,方法结束或者线程结束时,内存跟着回收。栈中的栈帧随着随着方法的而进入与退出执行着入栈和出栈的操作,栈帧分配多少内存在类结构确定下来就是已知的。

  JAVA堆和方法区,内存分配是动态的,只有在程序处于运行期间才知道会创建那些对象。

方法区回收

  永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。

  无用的类判断条件:

    1、类的所有实例都已被回收,Java堆中不存在任何实例。

    2、加载该类的ClassLoader已经被回收。

    3、该类的Class对象没有在任何地方被引用,无法在任何地方通过反射引用该类。

3、对象是否死亡 

3.1、引用计数算法

  给对象中添加一个引用计数器,每当有一个地方引用它,计数器值就加1;引用失效时,计数器值就减1。计数器的为0的对象就不可能再被使用。

  缺点:存在对象之间相互循环引用的问题,虚拟机并不采用引用计数算法来判断对象是否存活。

3.2、可达性分析算法

  可达性分析算法,判断对象是否存活。

  基本思想:”GC Roots”对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为”引用链”,当一个的对象到GC Roots没有任何引用链相连时,证明此对象是不可用的。

  JAVA中可视为GC Roots的对象有以下几种:

    虚拟机栈(栈帧中的本地变量表)中引用的对象。

    方法区中类静态属性引用的对象。

    方法区中常量引用的对象。

    本地方法栈JNI引用的对象。

4、四大引用类型

4.1、强引用

  new对象时的引用就是强引用,只要引用还在,垃圾收集器就不会回收掉被引用的对象。

4.2、软引用

  描述一些有用但非必须的对象。

  发生内存溢出之前,会把这些对象列进回收范围之中进行第二次回收。

4.3、弱引用

  描述非必须对象。

  强度比软引用要弱一些,被弱引用关联的对象只能生存到下一次垃圾回收之前。

4.4、虚引用

  最弱的一种引用关系。

  目的:在这个对象被收集器回收时收到一个系统通知。

5、垃圾收集算法

5.1、标记-清除算法

  算法分为两个阶段“标记”、“清除”。

  标记:标记出所有需要回收的对象。清除:在标记完成后同意回收所有被标记的对象。

缺陷:

  1、效率问题

    标记和清除两个过程效率并不高。

  2、空间问题

    标记清除之后会产生大量不连续的内存碎片。空间碎片太多可能会导致,在分配较大对象时,无法找到足够的连续内存而提前触发垃圾收集动作。

5.2、标记-整理算法(老年代算法)

  标记整理算法,标记与标记清除算法一样,整理则是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

5.3、复制算法

  复制算法为了解决效率问题,将可用内存划分为大小相等的两块,每次只使用其中一块。当其中一块的内存用完了,就将还存活着的对象复制到另一块上面,然后已经使用过的内存空间一次清理掉。

  这种算法的代价是将内存缩小为原来的一半。

  采用复制算法回收新生代,将内存分为一块较大的Eden空间和两块较小的Survivor空间,Eden和Survivor的大小比例是8:1,90%的空间作为新生代的容量,10%会浪费。当Survivor空间不够用时,需要依赖其他内存(老年代)进行分配担保。

  分配担保机制作用:如果另一块Survivor空间没有足够空间存放上一次新生代收集下来的存活对象时,这些对象将直接通过分配担保机制进入老年代。

5.4、分代收集算法

  当前虚拟机垃圾收集都采用“分代收集”。

  一般Java堆分为新生代和老年代。

  新生代中,每次垃圾收集时都有大批对象死去,只有少量存活,采用复制算法。付出少量存活对象的复制成本就可完成收集。

  老年代中,对象存活率高、没有额外空间对它进行分配担保,必须使用“标记-清理”或者“标记-整理”算法来回收。

6、HotSpot的算法实现 

  上面描述了对象存活判定算法垃圾收集算法

6.1、枚举根节点

  GC Roots节点找引用链的操作。可作为GC Roots的节点主要在全局性的引用(例如常量或类静态属性)与执行上下文(栈帧中的本地变量表)。

  可达性分析需耗时的体现:

    1、方法区比较大,逐个检查引用比较耗时。

    2、GC停顿,因为可达性分析工作必须在一个能确保一致性的快照中进行。

  “一致性”:在整个分析期间整个执行系统看起来就像被冻结在某一个时间点上,不可以出现分析过程中对象引用关系还在不断的变化的情况,该点不满足的话分析结果准确性就无法得到保证。

  可达性分析的准确性是导致GC进行是必须停顿所有Java执行线程(Stop  the  World)的重要原因。

  在类加载完成后,HotSpot把对象的引用记录在OopMap的数据结构中,在GC开始,执行系统停顿下来,不需要一个不漏地检查完所有执行上下文和全局的引用位置,只需在特定位置查找即可。

6.2、安全点(Safepoint)

问题一:

  在OopMap的协助下,HotSpot可以很快的完成枚举。但是OopMap的内容变化的指令非常多,若为每一条指令都生成对应的OopMap,将会需要大量的额外空间,GC空间的成本将会很高。

解决方案:

  HotSpot只是在“特定位置”记录这些信息,这些位置成为安全点。程序执行时并非在所有地方都能停顿下来开始GC,只有在达到安全点时才能暂停。

问题二: 

  如何在GC发生时让所有线程(这里不包括执行JNI调用的线程)都“跑”到最近的安全点上再停顿下来。 

解决方案:

   方案一:(几乎不用)

    抢先式中断:把所有线程全部中断,如果发现有线程中断的地方不在安全点上,就恢复线程,让它“跑”到安全点上。

  方案二: 

    主动式中断:当GC需要中断线程的时候,不直接对线程操作,仅仅简单设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时就自己挂起。轮询标志的地方和安全点是重合的。

6.3、安全区域(Safe Region)

  Safepoint机制保证了程序执行时,在不太长的时间内就会遇到可进入GC的Safepoint。

  当程序“不执行”时,不使用SafePoint机制。

  程序不执行:没有分配CPU时间,典型的例子是线程处于Sleep状态或者Blocked状态,此时线程无法响应JVM的中断请求,“走”安全的地方去中断挂起,JVM不太可能等待线程重新被CPU分配时间,这种情况下,需要用安全区域来解决。

  安全区域是指一段代码片段中,引用关系不会发生变化。在这个区域中的任意地方开始GC都是安全的。

 

posted @   无虑的小猪  阅读(104)  评论(0编辑  收藏  举报
(评论功能已被禁用)
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示