Skip to content

Latest commit

 

History

History
94 lines (65 loc) · 6.78 KB

05.垃圾回收.md

File metadata and controls

94 lines (65 loc) · 6.78 KB

垃圾回收

引用计数法:

​ 为每个对象添加一个引用计数器,来统计指向该对象的引用个数,一旦某个对象引用器为0时,则说明这个对象已经死了,需要回收

​ 问题:无法解决 循环引用 问题

​ 如果a和b互相引用,当a和b实际已经死了的情况下,他们的引用计数都不为0,会导致无法回收,最终导致内存泄露

可达性分析:

​ GC Roots(起始存活对象集合 live set)-> 从这个集合开始探索所有能够被该集合引用的对象,并将其加入该集合中(标记Mark);最终未被探索到的对象便是死亡的,可回收

​ 多线程环境下存在误报(将引用设置为null)和漏报(将引用设置为未被访问过的对象-新对象)

安全点:

​ 解决多线程环境下漏报问题

​ 传统的解决方法:Stop-the-world 即停止其他非垃圾回收线程的工作,直到完成垃圾回收,造成了垃圾回收的暂停时间(GC pause)

​ 安全点(safepoint)机制,Java虚拟机收到Stop-the-world请求,便会等待所有的线程到达安全点,才允许请求Stop-the-world的线程进行独占的工作;

​ 安全点的定义:虚拟机定义了一下情况为安全点,并对应一写安全点的检测机制(当有安全点请求时,执行一条字节码便进行一次安全点的检测)

回收:
  • 清除(sweep):将死亡对象的内存标记为空闲内存,并记录到空闲列表(free list)中
    • 缺点:
      • 造成内存碎片:JVM的堆中对象必须是连续分布的,所有可能出现总空闲内存很多,但是无法分配的极端情况
      • 分配效率低:需要逐个访问空闲列表中的项,来查找能够分配新建对象的空闲内存(如果是一块连续的内存,直接进行指针加法(pointer bumping)来做分配)
  • 压缩(compact):将存活的对象聚集到内存区域的起始位置
    • 优点:减少内存碎片化问题
    • 缺点:压缩算法的性能开销
  • 复制(copy):将内存分成两等分,分别用from和to两个指针来维护两块内存,只有from区域的内存可以用来分配,当发生垃圾回收时,便将存活的对象复制到to指针指向的内存中,并交换from和to的指针内容
    • 缺点:内存使用率低

现代垃圾回收器会综合上述几种回收方式,取优点规避缺点。

分代回收:

大部分Java对象只存活一小段时间,而存活下来的小部分对象则会存活很长一段时间。

  • 新生代
    • 猜测大部分对象只存在一小段时间,便可以频繁的采用耗时短的垃圾回收算法,让大部分垃圾能够在新生代被回收掉
  • 老年代
    • 猜测大部分垃圾已经在新生代被回收了;当触发老年代回收时,表明签名的猜测错误或者堆内存已经耗尽,这时JVM会进行一次全堆扫描,耗时将不计成本(现代垃圾回收器都是并发收集,来避免全堆扫描)
Minor GC:(新生代)
  • 堆划分

    • Java虚拟机堆划分:新生代 + 老年代
    • 新生代划分:Eden区 + 两个大小相同的Survivor区
    • 默认情况下,JVM采用动态分配策略(-XX:+UsePSAdaptiveSurvivorSizePolicy),根据对象的生成速率,以及Survivor区的使用情况动态调整Eden区和Survivor区的比例
  • 内存分配:

    • 当我们new一个对象时,会在Eden区划出一块作为存储对象的内存,由于堆空间是内存共享的,所有需要进行同步,否则就会出现两个对象公用一块内存的事故;
      • 解决:每个线程预先申请内存区域(连续的,加锁申请),当用完时,继续预申请内存(这项技术:TLAB,Thread Local Allcoation Buffer,对应虚拟机参数 -XX:+UseTLAB,默认开启)
    • 当Eden空间耗尽,这时,JVM会触发一次 Minor GC,回收垃圾,存活下来的对象会被送到Survivor区
    • 两块Survivor区
      • 分别标识为 from 和 to
      • 当发生Minor GC时,Eden区和from指向的Survivor区中的存活对象会被复制到to指向的Survivor区中,然后交换from和to的指针,以保证下次Minor GC时,to指向的Survivor区还是空的
      • JVM会记录Survivor区中的对象一共被来回复制了几次,如果一个对象被复制的次数为15(-XX:+MaxTenuringThreshold),则该对象将被晋升到老年代,其次如果单Survivor区被占用50%(-XX:TargetSurvivorRatio),那么较高复制次数的对象也会被晋升到老年代
  • 卡表(新生代垃圾回收时,老年代有可能引用了新生代,也就标记存活对象时,我们需要扫描老年代的对象,这样会导致扫描整个老年代)

    • 大致(推测)标志出可能存在的老年代新生代引用的内存区域
垃圾回收器:
  • 新生代垃圾回收器:(标记-复制算法)
    • Serial:单线程 -XX:UseSerialGC
    • Parallel Scavenge:多线程,吞吐量大于Parallel New,但不能与CMS一起使用
    • Parallel New:多线程 -XX:UseParNewGC
  • 老年代垃圾回收器:
    • Serial Old:标记-压缩算法,单线程
    • Paralle Old:标记-压缩算法,多线程 UseParallelGC
    • CMS:标记-清除算法,并发的,可在应用程序运行过程中进行垃圾回收;并发收集失败时,虚拟机会使用其他两个压缩型垃圾回收器进行一次垃圾回收
    • G1(Garbage First):
      • 标记-压缩算法
      • 横跨新生代和老年代的垃圾回收器
      • 打乱前面的堆结构,直接将堆分成极其多个区域(通常是几百个),这些区域是固定大小的(默认情况下大约为2MB),每个区域都可以充当Eden区、Survivor区或者老年代中的一个;G1能够对每个细分的区域进行垃圾回收,且优先回收死亡对象较多的区域
      • 在程序运行中并行进行垃圾回收
      • G1还有一个较关键的参数是-XX:MaxGCPauseMillis = n,这个参数是用来限制最大的GC暂停时间,目的是尽量不影响请求处理的响应时间。G1将根据先前收集的信息以及检测到的垃圾量,估计它可以立即收集的最大区域数量,从而尽量保证GC时间不会超出这个限制。因此G1相对来说更加“智能”,使用起来更加简单。
      • 由于G1的出现,CMS在Java9被废弃了
    • ZGC:
      • Java11采用,暂停时间不超过10ms
其他:

JNI(Java Native Interface)中传入的引用类型参数,或者JNI函数创建的Java对象,JNI采用局部引用与全局引用来告知垃圾回收器,不要回收这些C代码中可能引用到的Java对象,垃圾回收算法将标记这两种引用指向的对象为不可回收。