zoukankan      html  css  js  c++  java
  • JVM

    引用计数法:在Java领域,至少主流的Java虚拟机里面都没有选用引用计数算法来管理内存,主要原因是,这个看似简单的算法有很多例外情况要考虑,必须要配合大量额外处理才能保证正确地工作,譬如单纯的引用计数就很难解决对象之间相互循环引用的问题。
    可达性分析
      ○ 通过一系列称为“GCRoots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。
      ○ Object5、6、7都将被回收
      

      ○ 固定可作为GC Roots的对象
        § 虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
        § 静态属性引用的对象、常量引用的对象
        § 被同步锁(synchronized)持有的对象
        § JVM内部的引用(基本数据类型对应的class对象、常驻异常对象、系统类加载器)
        § JNI引用的对象
        § ...
    引用的分类(引用强度依次减弱)
      ○ 强引用:普通引用,存在最多,例如new
      ○ 软引用(SoftReference):在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
      ○ 弱引用(WeakReference):当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
      ○ 虚引用(PhantomReference):一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。
    判定是否回收
      ○ 在进行可达性分析后,如果一个对象没有与GC Roots相连接的引用链,它并不会被立即回收,还要经过一次筛选,假如对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,那么虚拟机将这两种情况都视为“没有必要执行”。
    方法区的垃圾回收条件苛刻,不常进行

    分代收集
      ○ 弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。
      ○ 强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。
    收集的分类
      ○ 部分收集(Partial GC):指目标不是完整收集整个Java堆的垃圾收集
        § 新生代收集(Minor GC/Young GC):指目标只是新生代的垃圾收集。
        § 老年代收集(Major GC/Old GC):指目标只是老年代的垃圾收集。目前只有CMS收集器会有单独收集老年代的行为。另外请注意“MajorGC”这个说法现在有点混淆,在不同资料上常有不同所指,读者需按上下文区分到底是指老年代的收集还是整堆收集。
        § 混合收集(Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集。目前只有G1收集器会有这种行为。
      ○ 整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集。
    区域划分
      ○ 新生代、老年代:在新生代中,每次垃圾收集时都发现有大批对象死去,而每次回收后存活的少量对象,将会逐步晋升到老年代中存放。
      ○ 跨代引用假说(Intergenerational Reference Hypothesis):跨代引用相对于同代引用来说仅占极少数。所以,在对新生代单独做垃圾收集时,不应该为了少量的跨代引用而遍历整个老年代(搜索引用链)。
      ○ 全局的数据结构——记忆集(Remembered Set):把老年代划分成若干小块,标识出老年代的哪一块内存会存在跨代引用。此后当发生Minor GC时,只有包含了跨代引用的小块内存里的对象才会被加入到GC Roots进行扫描。
    标记-清除算法
      ○ 算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。
      ○ 缺点
        § 执行效率不稳定:若存在大量要回收的对象,则大量的标记和清除操作会降低效率。
        § 内存空间碎片化:在执行若干次标记清除之后,磁盘空间将变得碎片化,当难以分配空间给较大对象时,则必须再执行标记清除。
          

    标记-复制算法
      ○ 半区复制:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
      ○ 当要回收的半区中大部分对象是可以存活的,这将带来很大的复制开销;但当大部分对象是需要回收时,只需执行少量的复制操作和一次清除操作即可。所以,这种算法很适合新生代区域的回收。
      ○ 缺点是空间被浪费了一半(必须闲置一半的空间用于复制)。但实际应用中,设计者并没有按照1:1来划分区域,因为98%的新生代对象死在第一轮收集。
    标记-整理算法
      ○ 让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
      ○ 弊端:对于老年代需要大量移动对象;移动对象时需要全程暂停应用程序。
    • 选择
      ○ 标记-清除算法在回收时更为直接,但由于空间碎片化,需要依赖于更复杂的内存分配和访问机制,而内存访问是非常频繁的,会直接影响程序的吞吐量。
      ○ 是否移动对象都存在弊端,移动则内存回收时会更复杂,不移动则内存分配时会更复杂。
      ○ HotSpot虚拟机里面关注吞吐量的Parallel Scavenge收集器是基于标记-整理算法的,而关注延迟的CMS收集器则是基于标记-清除算法的。
    平时多数时间都采用标记-清除算法,暂时容忍内存碎片的存在,直到内存空间的碎片化程度已经大到影响对象分配时,再采用标记-整理算法收集一次,以获得规整的内存空间。

    参考资料:《深入理解Java虚拟机 第三版》周志明

  • 相关阅读:
    WorldWind源码剖析系列:下载请求类DownloadRequest
    WorldWind源码剖析系列:网络下载类WebDownload
    WorldWind源码剖析系列:地形瓦片类TerrainTile和地形瓦片服务类TerrainTileService
    WorldWind源码剖析系列:下载队列类DownloadQueue
    C#游戏开发中快速的游戏循环
    C#游戏开发中精确的时间调配
    C#中XmlSerializer实现序列化浅析
    Vue 组件通信(组件间通信)
    Vue 组件通信(子组件向父组件传递数据)
    Vue props 单向数据流
  • 原文地址:https://www.cnblogs.com/CofJus/p/13426267.html
Copyright © 2011-2022 走看看