【jvm】垃圾回收机制

一。如何判断垃圾可回收

  1.引用计数法

  引用计数法就是当一个对象被引用时,就对它添加一个引用标记,当一个对象不被引用时就减少一个引用标记,每当需要垃圾回收时,就对引用为0的对象进行回收。

  这种方法会产生很多永远不会被回收的垃圾,诸如一个列表中引用了另一个列表,而另一个列表中也引用了它,则会构成循环引用。

  2。可达性分析算法

  由于循环引用的方式,引用计数法显然不适合用来检索垃圾,这就需要用可达性分析。

  可达性分析会将GC root作为根,往下遍历它所有引用的对象,标记结束后,没有被标记的对象都可以被回收

  GC root:

    system Class 系统类 java.lang.class object hashmap 等

    Native Class 本地方法类  

    Thread  线程正在运行的类 像栈帧里用到的局部变量中引用的对象都可以作为gc root

    Busy Monitor  sync锁正在使用的作为锁的对象

  可以使用eclipse提供的Memory Analyzer (Mat)查看gcroot

  3.四种引用

  除了直接的引用也就是强引用之外,还有4种引用,这些引用有的会在内存不足的时候,有可能会被回收。

    1.软引用

    当只有软引用引用该对象的时候,会在第一次垃圾回收之后内存不足的情况下,再次触发垃圾回收,回收这些软引用对象。

    除了回收软引用的对象之外,还可以配和引用队列回收软引用本身。

    2.弱引用

    仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象。

    弱引用同样可以配合引用队列来配合回收。

    与软引用不同的是,弱引用在第一次垃圾回收时就会被回收,而不会到第二次垃圾回收。

    3.虚引用

    虚引用必须配合引用队列使用,主要配合ByteBuffer使用。被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存。

    4.终结器引用

    无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象 暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被引用对象。

二。垃圾回收算法

  1.标记清除

  标记阶段使用可达性算法标记不被回收的对象,清除阶段会将没被标记的对象回收。

  缺点:在清除之后会留下很多内存碎片。

  2.标记整理

  标记整理是标记清除的优化,修正了大量内存碎片这一缺点,但同时在整理阶段会停顿很长时间。

  3.标记复制

  标记复制把内存分为两块,发生垃圾回收时,会将一边的内存复制到另一边,然后回收剩下的内存。

三。分代回收

  在分代回收中,堆内存被分为一下几个代

  新生代:

    伊甸园区

    幸存区s0

    幸存区s1

  老年代

  对象首先被分在伊甸园区,当新生代内存不足时,出发minorGC,将伊甸园区的存活对象copy到s0,然后交换s0和s1。

  minor gc 会引发 stop the world,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行。

  当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit)。

  当老年代空间不足,会先尝试触发 minor gc,如果之后空间仍不足,那么触发 full gc,STW的时 间更长。

  当对象不能被放在伊甸园区时,触发minorgc。

  如果minorgc中将存货对象放入幸存区不成功,则会直接升级为老年代

  当一个对象大到即使触发gc新生代也容纳不下时,就会直接放到老年代中,而不发生gc

  子线程的oom不会影响子线程的运行,并且清除线程垃圾

  相关参数:

  堆初始大小 -Xms

  堆最大大小 -Xmx 或 -XX:MaxHeapSize=size

  新生代大小 -Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size )

  幸存区比例(动态) -XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy

  幸存区比例 -XX:SurvivorRatio=ratio

  晋升阈值 -XX:MaxTenuringThreshold=threshold

  晋升详情 -XX:+PrintTenuringDistribution

  GC详情 -XX:+PrintGCDetails -verbose:gc

  FullGC 前 MinorGC -XX:+ScavengeBeforeFullGC

四。垃圾回收器。

  1.串行

  XX:+UseSerialGC = Serial + SerialOld

  serial 是针对新生代的回收器,特点是单线程回收

  serialOld是针对老年代的回收期。

  2.吞吐量优先

  -XX:+UseParallelGC ~ -XX:+UseParallelOldGC

  -XX:+UseAdaptiveSizePolicy

  -XX:GCTimeRatio=ratio

  -XX:MaxGCPauseMillis=ms

  -XX:ParallelGCThreads=n

  其中parallelGC是针对新生代的,ParallelOldGC是针对老年代的。

  在进行标记的时候会产生stw,所有的cpu都会运行垃圾回收,其他时间不会,这样吞吐量就会提升

  3.响应时间优先

  -XX:+UseConcMarkSweepGC ~ -XX:+UseParNewGC ~ SerialOld

  -XX:ParallelGCThreads=n ~-XX:ConcGCThreads=threads

  -XX:CMSInitiatingOccupancyFraction=percent

  -XX:+CMSScavengeBeforeRemark

  parNew,是多线程版的serial,而cms是针对老年代的gc,它和ParallelGC不同的地方是它更关注响应时间,也就是减少单位时间内stw的时间

  cms的步骤:

  1.初始标记

  初始标记时会产生stw,所有线程阻塞等待完成。

  初始标记标记的时GCroot直接引用的对象。

  2.并发标记

  并发标记会将gc和其他运行线程一起运行,它会根据初始标记依次往下遍历出所有被引用的对象。

  3.重新标记

  重新标记会产生stw,将所有没被标记的对象检查其是否被引用,这一步防止在并发标记的过程中没被标记的对象被重新使用,防止误删。

  4.并发清除

  清除垃圾的同时运行其他线程。

  值得注意的时,当老年代使用占比超过一定的比例就要使用gc,否则在并发标记和清除的时候会产生新的堆内存,如果这些内存超过了可用线程就会退化成serialoldGC,并且出发一次fullgc回收整个堆的内存。

 

 

   4.G1

  -XX:+UseG1GC

  -XX:G1HeapRegionSize=size

  -XX:MaxGCPauseMillis=time

  首先g1中引用一个新的概念为region。一个region为512k,每个region都有可能时伊甸园区,幸存区和老年区,或者大对象区。

 

   G1的回收有三个阶段

  1.young Collection

  这个阶段会触发stw,也就时minorgc,会将伊甸园区的存活对象标记复制到另一个region变成存货区。

  并且当存活区满时,会将超过年龄的对象升级到老年区,不超过年龄的转移到其他幸存区。

  2.Young Collection + CM

  在 Young GC 时会进行 GC Root 的初始标记 老年代占用堆空间比例达到阈值时,进行并发标记(不会 STW),由下面的 JVM 参数决定:

  -XX:InitiatingHeapOccupancyPercent=percent (默认45%)

  3.Mixed Collection

  会对 E、S、O 进行全面垃圾回收

    1.初始标记(由上面的younggc标记完成)

    2.并发标记

    3.最终标记

    最终标记阶段是为了修正在并发标记期间因用户程序继续运作而导致标记产生变动 的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但是可并行执行。

    4.筛选回收

    最后在筛选回收阶段首先对各个Region的回收价值和成本进行排序, 根据用户所期望的GC停顿时间来制定回收计划,从Sun公司透露出来的信息来看,这个阶段 其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控 制的,而且停顿用户线程将大幅提高收集效率。 

    简单来说,垃圾越多的分区越先被回收,而全是垃圾的分区在标记阶段就会被回收。

  4.跨代回收

  当回收年轻代时,这些对象不免会引用老年代的对象,所以可达性分析需要遍历所有其他分区,如果数据量大则会需要很长的stw,所以包括cms都会使用一种叫记忆集(remember set)的东西来记录对象引用的地址。

  记忆集是一种概念,在g1 中是通过cardTable卡表来实现记忆集,

  在引用变更时通过 post-write barrier + dirty card queue,写屏障,把该对象加入到脏卡中,最后,将记忆集的信息同步给GCroot,这样只需要遍历伊甸园区的分区即可

  5.remark

  之前在重标记中,有一部叫remark,此步骤分为三色

  白色:尚未被GC访问过的对象,如果全部标记已完成依旧为白色的,称为不可达对象,既垃圾对象。

  黑色:本对象已经被GC访问过,且本对象的子引用对象也已经被访问过了(本对象的孩子节点也都被访问过)。

  灰色:本对象已访问过,但是本对象的子引用对象还没有被访问过,全部访问完会变成黑色,属于中间态(本对象的孩子节点还没有访问)

标记过程:

  初始时,所有对象都在 【白色集合】中;
  将GC Roots 直接引用到的对象 挪到 【灰色集合】中;
  从灰色集合中获取对象:
    将本对象 引用到的 其他对象 全部挪到 【灰色集合】中;
    将本对象 挪到 【黑色集合】里面。
  重复步骤3,直至【灰色集合】为空时结束。
  结束后,仍在【白色集合】的对象即为GC Roots 不可达,可以进行回收

 

posted on 2022-05-10 17:35  一只萌萌哒的提莫  阅读(145)  评论(0编辑  收藏  举报