微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

深入理解JVM阅读笔记二

深入理解JVM阅读笔记(二)

1.为什么Java的不能采用引用计数法来判断对象是否可回收?

因为存在循环引用的问题,对象A和对象B互相引用,就算他们已经不存在其他引用了,但是依然无法被GC

2.什么判断方式比较合适?

可达性分析算法:从一个被作为“GC Roots”的对象作为起始点,从这些节点向下搜索,走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连,则证明该对象不可用。

注:如下对象可以作为GC Roots

3.有哪几种引用?

共有四种引用,引用强度由强至弱为:强引用,软引用,弱引用和虚引用。

  • **强引用:**指的是例如“Object obj = new Object()”,只要引用还存在,那么该对象永远不会被GC。
  • **软引用:**指的是一些有用但非必须的对象,只有在内存不足时才会回收该对象。通过SoftReference来实现软引用。
  • **弱引用:**同样指一些非必要的对象,在下一次GC发生时,无论内存是否足够,都会被回收。通过WeakReference来实现软引用。
  • **虚引用:**虚引用不会对对象的生存时间产生任何影响,同样也不能通过虚引用来获得对象实例。使用虚引用的唯一目的就是使该对象在被GC时可以收到通知,通过Phantomreference来实现虚引用。

4.方法区会如何进行回收?

方法区一般会回收两部分内容废弃常量无用的类

  • 废弃常量方法区中的常量池中的常量不再有任何对他的引用。

  • 无用的类:类需要满足如下三个条件才可以被称为“无用的类”。

    • 该类在堆上的所有实例均被回收
    • 该类的ClassLoader已被回收
    • 该类的java.lang.class对象不存在任何引用,即不存在任何通过反射访问该类中的方法

    注:对无用的类,GC回收器可以进行回收,但不是一定,需要设置。

5.GC算法分类

标记-清除算法:分为标记和清除,有两点不足:第一,清除和标记的效率都不高。第二,标记清除会产生大量不连续的内存碎片,导致在后续给大对象分配大内存无法找到连续的内存,需要再进行一次GC。

  1. 标记所有需要回收的对象
  2. 标记完成后统一回收所有的对象

标记有两次,第一次标记上与GC Roots没有相连接的引用链的,并筛选那些有必要执行finalize方法的对象,如果没有覆盖finalize方法或者finalize方法已经被调用过,那么就不会被GC;第二次会将第一次的结果放入“F-Queue”队列中,并对队列中的对象进行标记,如果这次对象没有让自己被其他对象引用而逃脱,那么就会被清除。

**复制算法:**将内存划分为大小相等的两块区域(商用JVM发现不需要相等),一次只使用其中一块,当一块的内存被使用完了,就将还存活的对象放到另外一块上,再把之前使用的内存块全部清理干净。优点是避免了内存碎片的产生,缺点是内存缩小过多,代价较大,另外在对象存活较多时,会复制多次,复制代价较大。

**标记-整理算法:**同样由两步组成,标记和整理

  1. 标记过程类似于标记-清理算法。
  2. 整理指的是将所有的存活的对象向内存一段移动,并且清理掉端边界以外的内存。

**分代收集算法:**将堆内存分为新生代和老年代,对每个区域选用最合适的GC算法思想。

注:上述的几种算法都是算法思想,在现代GC收集器中均有运用在不同的内存区域中。如复制算法主要用在新生代中,因为新生代中在GC时经常有大量对象死去,少量存活,标记整理算法主要运用在老年代中,因为老年代中的存活率较高。

6.GC算法有哪些问题,HotSpot通过什么方法解决的?

问题:

  • 逐个检查所有GC Roots的引用链会消耗很多的时间。
  • 可达性分析必须在一个能够确保一致性的快照中进行,这点导致GC时必须停掉所有的线程。

解决方法

  • 枚举根节点:用一组称为OopMap的数据结构来存储那些地方存储着引用,GC在扫描时可以直接获得这些信息,称为准确式GC,大大减少了时间。

  • 选择安全点:只有在安全点处生成OopMap,避免每条指令都生成OopMap来占用大量的额外空间。选择安全点是以“是否具有让程序长时间执行的特征”为标准。如方法调用、循环跳转、异常跳转在这功能的指令才会产生SafePoint。

    注:为了让所有线程都跑到安全点再停顿下来,有两种思想。

    • 抢先式中断:在GC发生时,人为的把把所有的线程全部中断,如果发现有的线程不在安全点上,将其单独放开,让它跑到安全点再停止(几乎不再使用)。
    • 主动式中断:设置一个是否中断的标志位,轮询标志的地方和安全点是重合的,当线程轮询发现标志位为真,则将自己挂起。
  • **选择安全区:**针对状态为Blocked的线程,安全点不再适用,选择一段代码作为安全区域,在该区域内引用关系不会发生变化。当线程进入安全区,会对自己进行标识,但当该线程要离开安全区时,必须检查GC是否结束,若GC完成了,线程继续进行,未完成,必须等到收到可以离开的信号为止。

7.HoptSpot虚拟机包含哪些收集器

image-20210617101015240

注:没有最好的收集器,只有最合适的收集器,虚拟机中是运用了多种收集器,并在合适的地方使用合适的收集器,如果两个收集器存在连线,那么意味着可以搭配使用。

8.介绍一下Serial收集器

Serial收集器是一个单线程收集器,指其只会用一个cpu或者收集线程来完成工作,另外在运作的时候,需要停下所有其他的线程,只留它一条线程工作。如下是其和Serial Old配合的工作流程。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HLik10qb-1624182899325)(C:\Users\Kyle-Sung-Gu\AppData\Roaming\Typora\typora-user-images\image-20210617102313547.png)]

**工作范围:**Client模式下认的新生代收集器

优点:

  • 相比于其他单线程收集器,简单而高效(在单个cpu环境下,Serial收集器省去了线程交互的开销)
  • 在虚拟机管理的内存不是很大的情况下,停顿时间是可以接受的

9.介绍下ParNew收集器

可以理解为多线程实现的Serial收集器,其余参数和Serial收集器一模一样,仍然存在Stop the World问题,如下是其和Serial Old配合的工作流程。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OV1YqaFS-1624182899329)(C:\Users\Kyle-Sung-Gu\AppData\Roaming\Typora\typora-user-images\image-20210617103235407.png)]

**工作范围:**Server模式下认的新生代收集器,重要原因是除了Serial收集器外,只有它可以和CMS收集器合作。

**优点:**在单cpu情况下不一定比Serial收集器好,但是在多cpu的环境下会更加充分的利用cpu资源。

注:ParNew是一个并行收集器,在GC中并行指的是多条垃圾收集线程共同工作,用户线程处于等待状态并发指的是垃圾收集线程和用户线程同时执行,不一定是并行,可能会交替工作。

10.介绍下Parallel Scanvage收集器

是一种吞吐量优先收集器,使用复制算法的新生代收集器,是一种并行收集器

吞吐量:指的是cpu运行用户代码的时间与cpu消耗时间的比值,即 运 行 用 户 代 码 时 间 运 行 用 户 代 码 时 间 + 垃 圾 收 集 时 间 \frac{运行用户代码时间}{运行用户代码时间+垃圾收集时间} 运行用户代码时间+垃圾收集时间运行用户代码时间​,不同于停顿时间短是为了提高与用户交互程序的响应速度,高吞吐量可以充分利用cpu性能,适用于在后台运算而不需要太多交互的任务。

通过两个参数来控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。

另外一个不同于ParNew收集器的点是Parallel Scanvage有一种自适应调节策略,把内存管理交给JVM,由JVM根据用户设置的内存数据,并以设置的最大垃圾收集停顿时间和吞吐量大小的参数为优化目标,自动进行内存管理

11.介绍下Serial Old收集器

Serial收集器的老年代版本,单线程,应用标记-整理算法

**工作范围:**主要在Client模式下使用,在Server模式下,一方面是在JDK1.5之前和Parallel Scanvage合作使用,另一方面是在发生Concurrent Mode Failure的情况下,作为CMS收集器的备选方案。

12.介绍下Parallel Old收集器

**产生原因:**为了解决Parallel Scanvage只能和Serial Old收集器共同使用,但Serial Old收集器在多cpu的Server端无法充分利用多cpu的处理能力,无法获得整体上吞吐量的最优。

如下是Parallel Scanvage和Parallel Old收集器运行示意图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-twn5TLKb-1624182899332)(C:\Users\Kyle-Sung-Gu\AppData\Roaming\Typora\typora-user-images\image-20210617134703735.png)]

13.介绍下CMS收集器

目标:线程回收停顿时间最短,适用于重视服务器响应速度来给用户带来良好体验的服务。

基于标记-清除算法,有以下几个步骤:

  1. 初始标记单线程,并暂停其他所有线程标记和GC Roots直接关联的对象,速度很快。
  2. 并发标记用户线程同时运作,检查所有和GC Roots存在引用链的对象。
  3. 重新标记多线程,暂停其他所有线程,修正并发标记期因为用户程序继续运作导致引用链发生变化的对象的记录
  4. 并发清除,用户线程同时运作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rQMGrd8q-1624182899337)(C:\Users\Kyle-Sung-Gu\AppData\Roaming\Typora\typora-user-images\image-20210617153221161.png)]

优点:

考虑到耗时最长的并发标记和并发清除是同时和用户程序运作的,因此可以认为CMS是一个并发收集器,从而实现低停顿。

缺点:

  • 对资源十分敏感,随着cpu数量的增大,对cpu占用的资源逐步降低,但是在cpu数量较少时,占用过多cpu资源。通过增量式并发收集器的思想来解决这一问题,即让并发标记和并发清除通过伪并发(和用户程序交替执行来实现),导致垃圾收集时间变长,但独占cpu的时间变短。
  • 无法处理浮动垃圾,浮动垃圾指的是CMS在并发清除阶段,用户线程仍然在产生垃圾,而这些垃圾必须等待下一次的清理。并且由于用户程序需要运行,所以需要预留足够的内存空间给用户线程,如果预留的内存不够,那么就会触发Concurrent Mode Failure,并导致JVM启用Serial Old来启动再一次Full GC。
  • 基于标记-清除算法的收集器会产生内存碎片,导致无法为大对象分配空间,导致再次发生Full GC。

Full GC 和 Minor GC

Full GC又称为Old GC,指发生在老年代的GC,并且速度比Minor GC慢10倍以上

Minor GC指的是发生在新生代的GC,因为在新生代中的对象大多是朝生夕死,Minor GC非常频繁,回收的也非常快。

14.介绍下G1(Garbage-First)收集器

优点

  • **并行与并发:**可以使用多条线程进行垃圾收集,缩短Stop-the-world时间,并且在垃圾收集时,可以让用户线程进行运行达到并发。
  • **分代收集:**G1收集器可以管理整个GC端,但是对于存活过多次GC的老对象采用更合适的方法
  • **空间整合:**采用标记-整理算法,避免了内存碎片的产生。
  • **可预测的停顿:**近乎实时Java垃圾收集器,在指定长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不超过N秒。

如何实现可预测的停顿?

G1避免了在整个堆区域进行垃圾回收,通过回收所获得的空间大小和回收所需时间的经验值来判断清除各个Region中垃圾的价值,并在后台建立一个优先列表,根据允许的收集时间,优先回收价值最大的区域

工作流程

G1提供了两种GC模式,Young GC和Mixed GC。

  • Young GC:选定所有新生代里的Region,通过控制年轻代的内存大小,来控制Young GC的时间开销
  • Mixed GC: 选定所有年轻代的Region,外加通过下列方式选择出的收益高的老年代Region,在用户指定的开销范围内尽可能选择收益高的老年代Region。

Mixed GC不是Full GC,只能回收部分老年代的Region,如果Mixed GC实在无法跟上内存分配的速度,就会启用Serial Old GC来收集整个Java堆。

  1. 初始标记
  2. 并发标记,类似于CMS
  3. 最终标记,类似于CMS,把这段时间内引用关系的变化合并到Remembered Set中
  4. 筛选回收,非并发,对各个Region的价值进行排序,根据用户所需的停顿时间来制定回收计划。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yZKVdimy-1624182899340)(C:\Users\Kyle-Sung-Gu\AppData\Roaming\Typora\typora-user-images\image-20210618095512099.png)]

15.G1如何实现化整为零

区别于传统的内存分代,G1的各代存储地址是不连续的,每一代都使用了N个不连续的大小相同的Region,每个Region内的地址是连续的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yc00vFBL-1624182899342)(C:\Users\Kyle-Sung-Gu\AppData\Roaming\Typora\typora-user-images\image-20210618095946763.png)]

存在的问题:各个区域内的对象并不是彼此孤立的,不同区域的对象彼此之间可能存在引用,那么在判断对象存活的时候仍需要扫描整个堆内存。

**解决方法:**为每一个Region区域维护一个Remebered Set,当JVM发现程序对Reference类型数据进行读写操作时,会产生一个Write Barrier暂停中断写操作,检查将要被引用的对象和references数据是否在不同的区域里,如果是,将引用信息记录到被引用对象所在的区域的Remembered Set中。在GC中,将Remeberbered Set加入根节点的枚举范围。

如果一个对象的大小超过了一个分区大小的一半,那就会认定它为巨型对象,G1收集器会将它放在一个称为Humogous的区域内,如果一个H区装不下,那就会找连续的多个H区,如果找不到,启动Full GC。

16.对象内存分配策略

对象的内存分配遵循如下几条规则:

  • 对象优先在新生代Eden区中分配,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配,当Eden没有足够的区域的时候就会发起一起Minor GC。

**TLAB:**全称是Thread Local Allocation Buffer,即线程本地分配缓存区,这是一个线程专用的内存分配区域,在线程初始化时,指定一块给定大小的内存只给当前线程使用,在需要分配内存时,优先在该区域上分配内存。

  • 大对象(指的是需要大量连续内存空间的Java对象如很长的字符串和数组)直接进入老年代进行内存分配,目的是避免在Eden区和两个Survivor区发生大量的内存复制

  • 长期存活,年龄增加到15岁的对象,将会被晋升入老年代中

对象的年龄:对象在Eden区出生后并经过第一次Minor GC仍然存活,并且能被Survivor容纳,那么将会被放入Survivor区中,并且该对象的年龄设为1,在Survivor区中每熬过一次Minor GC,那么年龄就会加1,另有动态对象年龄判定

  • 动态对象年龄判定,并非永远要求对象达到年龄阈值才能放入老年代,如果Survivor空间中相同年龄的对象的闸弄空间大小总和大于Survivor空间的一半,那么年龄大于或等于该年龄的对象就可以直接进入老年代。

  • 空间分配担保,考虑到新生代使用复制算法,为了提高内存利用率,只使用其中一个Survivor空间来作为轮换备份,因此当出现大量对象在Minor GC后依然存活的情况,就需要老年代做担保,将Survivor无法容纳的对象放入老年代中,考虑到无法确定老年代中是否有存储对象的足够空间,所以就采取每一次回收的晋升到老年代对象容量大小的平均值作为经验值来决定老年代是否需要Full GC来保证空间,如果失败了,那么就需要发起一次Full GC

  • 空间分配担保,考虑到新生代使用复制算法,为了提高内存利用率,只使用其中一个Survivor空间来作为轮换备份,因此当出现大量对象在Minor GC后依然存活的情况,就需要老年代做担保,将Survivor无法容纳的对象放入老年代中,考虑到无法确定老年代中是否有存储对象的足够空间,所以就采取每一次回收的晋升到老年代对象容量大小的平均值作为经验值来决定老年代是否需要Full GC来保证空间,如果失败了,那么就需要发起一次Full GC

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐