JVM学习笔记2--垃圾回收

1. 如何判断对象可以回收

1.1 引用计数法

给对象添加引用计数器,只要某个对象被某个变量所引用,就给他的引用计数+1.当引用计数为0时,就认为没有变量引用它了,即可以垃圾回收。

缺点:如果两个对象相互引用,引用计数均为1,则永远无法被回收。

1.2 可达分析

  1. 首先要确定根对象,即能确定不能被当成垃圾回收的对象。

  2. 对堆中的元素进行扫描,看其是否是根对象直接或间接引用的,如果是则不能被回收。

  3. 算法思想:

    通过一系列成为“GC Roots”的对象为起始点,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。如下图object5、6、7都是不可达的,都会被判定为可回收对象。

JVM学习笔记2--垃圾回收_第1张图片

1.3五种引用

 JVM学习笔记2--垃圾回收_第2张图片

 

1.强引用

指在程序代码中普遍存在的,例如Object obj = new Object(),只要强引用存在,被引用的对象就永远不会被回收。只有强引用全部断开,该对象才能被回收。

2.软引用

当发生垃圾回收之后,依然发现内存不足的话就会回收调被软引用的对象。因为即使该对象还有用,系统会认为软引用的对象非必需、不够重要。

//TODO 软引用实例
//TODO 将不太重要的对象放入SoftReference中,使其不占用内存空间,需要的时候再读取
//TODO 如果直接使用List强引用的话不会回收,会导致内存溢出。
List> list = new ArrayList<>();
ReferenceQueue queue = new ReferenceQueue<>();
//关联了引用队列,当软引用所关联的byte[]被回收时,软引用自己也会假如到queue中
SoftReference ref = new SoftReference<>(new byte[_4MB],queue);

黑马程序员JVM完整教程,全网超高评价,全程干货不拖沓_哔哩哔哩_bilibili 详细见视频讲解。

3.弱引用

只要发生垃圾回收且该对象没有被强引用,即使内存空间足够,也会回收掉该对象。

某个软引用对象和弱引用对象在他们所引用的对象被回收之后会被放入引用队列中,方便系统来遍历引用队列,如果需要会对这些软、弱引用对象本身进行回收。

4.虚引用

虚引用对象和直接内存有关。在虚引用对象引用的对象被垃圾回收时,虚引用对象本身也会被放入引用队列中,然后通过ReferenceHandler线程定时去引用队列中看有没有新入队的Cleaner,如果有的话就会调用Cleaner中的clean()方法,clean()方法会根据前面记录的直接内存地址调用Unsafe.freeMemory()方法来释放掉直接内存,从而避免直接内存溢出。

5.终结器引用

当终结器对象引用的对象要被垃圾回收时,终结器对象就会被放入引用队列(注意:该对象没有被立刻垃圾回收),再由一个优先级很低的线程finalizehandler来查看是否有终结期引用,有的话则会找到被引用的对象并调用它的finalize()方法,等到下次垃圾回收时就会回收掉这个对象所占用的内存。

2.垃圾回收算法

2.1标记清除法

首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

JVM学习笔记2--垃圾回收_第3张图片优点:速度快

缺点:1. 效率低:标记和清除两个过程的效率都不高;

2.空间问题:会产生大量不连续的内存碎片,分配较大对象时,无法找到足够的连续空间kennel会触发下一次垃圾回收

2.2标记整理法

第一部分标记同2.1一样,但后续过程是让所有寸或的对象都向前一端移动,然后直接清理掉端边界以外的内存。JVM学习笔记2--垃圾回收_第4张图片

 

优点:没有内存碎片

缺点:整理过程移动对象的过程涉及动态重定位等,速度较慢。

2.3复制

JVM学习笔记2--垃圾回收_第5张图片复制过程完成碎片的整理,并交换from和to的位置。

 

优点:不会产生碎片;

缺点:会占用双倍的内存。

2.4分代收集

JVM学习笔记2--垃圾回收_第6张图片

 

这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为“新生代”和“老年代”

  • 新生代中,每次垃圾回收时都有大批量的对象被回收,只有少量存活,选择复制算法,只需要付出少量存活对象的复制成本即可;

  • 老年代中,对象存活率高,没有额外空间对它进行分配担保,就必须使用“标记清除”或“标记整理”

  • 对象首先分配在伊甸园区域,伊甸园和from区域不足时触发MinorGC,伊甸园和from存活的对象使用复制算法copy到to中,存活的对象年龄+1并交换from和to。

  • Minor GC会引发stop the world,暂停其他用户的线程,让垃圾回收先工作直到结束。(时间很短)

  • 当对象寿命超过阈值时,就会晋升到老年代(最大15)

  • 当老年代空间不足,且触发Minor GC之后空间仍不足,就会触发full GC(也会引发stop the world 且时间会更长)

3.垃圾回收器

3.1串行

底层是一个单线程的垃圾回收器,其他线程都暂停让他来完成垃圾回收;

适合堆内存较小的个人电脑。

//TODO 开启串行垃圾回收语句:
-XX:+UseSerialGC = Serial + SerialOld

//Serial工作在新生代,采用复制算法
//SerialOld工作在老年代,采用标记-整理算法

JVM学习笔记2--垃圾回收_第7张图片

 

堆内存不够了,触发垃圾回收时,让各个线程在安全点停下(因为对象的地址可能会发生改变),只有一个垃圾回收线程在运行,其他线程处于阻塞状态。

3.2吞吐量优先

多线程,适合堆内存较大的场景,需要多核CPU来支持

单位时间内stw的时间最短

eg:单次垃圾回收需要0.2秒,一小时内发生两次,即0.2x2=0.4秒

//TODO 开启串行垃圾回收语句:
-XX:+UseParallelGC~-XX:+UseParallelOldGC
-XX:ParallelGCThreads=n     //指定参与的线程数
-XX:+UseAdaptiveSizePoicy   //采取自适应的策略调整新生代的大小
-XX:GCTimeRatio=ratio       //调整吞吐量,垃圾回收的时间和总时间的占比 1/(1+ratio) 一般19
-XX:MaxGCPauseMillis=ms     //最大暂停时间200ms 

//ratio和pauseMillis负相关 要取折中

//Parallel工作在新生代,采用复制算法
//ParallelOld工作在老年代,采用标记-整理算法

JVM学习笔记2--垃圾回收_第8张图片

 

3.3响应时间优先

多线程,适合堆内存较大的场景,需要多核CPU来支持

垃圾回收时,让单次的stw时间尽可能短

eg:单词垃圾回收需要0.1秒,一小时内发生五次,即0.1x5=0.5秒

//TODO 开启串行垃圾回收语句:
-XX:+UseConcMarkSweepGC~-XX:+UseParNewGC~SerialOld
//concurrent并发的  cms工作在老年代,有时会并发失败,就退化到SerialOld来补救
-XX:ParallelGCThreads=n~-XX:ConcThreads=threads     //指定参与的线程数
-XX:+CMSInitialOccupancyFraction=percent      //执行CMS垃圾回收的内存占比
-XX:+CMSScavengeBeforeRemark       //

JVM学习笔记2--垃圾回收_第9张图片

 图

垃圾回收过程:

  1. 首先多个CPU并行执行,然后老年代发生内存不足,这些线程都到达安全点,CMS垃圾回收器会执行一个初始标记(标记的是与GCroots直接相连的对象,且需要STW)
  2. 初始标记结束后,用户线程恢复运行,与此同时垃圾回收线程继续进行**并发标记,**把剩余的垃圾找出来,和用户线程并发执行;(不需要STW)
  3. 随后还有一个重新标记的过程,且此过程需要STW,因为前序工作可能会产生新的对象或改变对象的引用会对垃圾回收有干扰。
  4. 重新标记完了,用户线程可以继续运行,垃圾回收线程做一个并发的垃圾回收清理。

Tips:

  • 整个工作阶段,初始标记和重新标记需要STW,响应时间非常短
  • 并发池的线程数ConcThreads受到ParallelGCThreads的影响,一般为它的1/4
  • 对CPU的占用并不高,但用户工作线程也在运行,只能占原来的3/4,对整个应用程序的吞吐量有影响。
  • 在垃圾回收线程进行并发清理时,用户线程可能产生新的垃圾称为浮动垃圾,这些垃圾只能等到下一次垃圾清理时才能被回收,所以不能等到堆内存不足了再做垃圾回收,并且还要给这些浮动垃圾预留一些空间。指定CMS时的内存占比-XX:+CMSInitialOccupancyFraction=percent,默认65%。
  • 在重新标记的阶段有一个特殊的场景,有可能新生代的对象会引用老年代的对象,重新标记的话必须扫描整个堆,对性能影响较大,使用CMSScavengeBeforeRemark在重新标记之前对新生代做一次垃圾回收。
  • CMS在内存碎片较多的时候,有可能造成并发失败,垃圾回收器就会退化为SerialOld做一次单线程串行的垃圾回收,整理完碎片减少了再恢复工作。也是CMS最大的问题所在。

参考:

1.《深入理解Java虚拟机》

2.B站黑马程序员视频黑马程序员JVM完整教程 

你可能感兴趣的:(JVM学习,java)