对于GC知识点的总结


准备面试中,正好复习到了java的GC,将自己的体会和看到的好的总结搬运过来。
以下内容转自: https://www.jianshu.com/p/382c32538052

引用有哪些类型?

强引用:通过new创建出来的对象。只要强引用存在,垃圾回收器将不会回收。
软引用:通过SoftReference实现软引用,系统将要发生内存溢出之前才会对这些对象进行回收。
弱引用:通过WeakReference实现弱引用,无论当内存足够,GC运行时都会进行回收。
虚引用:通过PhantomReference实现,通过虚引用无法回去对象的实例,虚引用的作用就是当此对象被回收时,会收到一个系统通知。

如何判断一个对象是否应该被回收?

1、引用计数算法,每当有一个地方引用他,就加1,引用失效,就减1.数量为0,说明可以进行回收。
引用计数算法的缺点就是无法判断两个对象之间存在相互引用的情况。
jvm中没有使用这种判断方式
2、可达性分析算法,通过一系列GCRoot对象作为起始点,从这些点开始向下搜索,搜索的路径成为引用链,当一个对象到GC没有任何引用链,说明对象可以被回收。

GCRoot对象有哪些?

(1)虚拟机栈的栈帧中引用的对象。
(2)方法区中静态属性引用的对象。
(3)方法区中常亮引用的对象。
(4)本地方法栈中jni引用的对象。
枚举根节点时候需要GC停顿,保证分析结果的准确性。

使用GCRoot枚举根节点,由于在整个方法区进行枚举会耗费时间。如何解决?

执行准确式GC并不需要检查执行上下文中所有的引用的位置,在Hotspor中通过OopMap的数据结构来达到这个目的。在类加载完成的时候,虚拟机会将对象内什么偏移量什么数据计算出来,在JIT编译的时候,会在特定的位置记住栈和寄存器中什么地方存在引用,GC直接对这些引用进行扫描。

什么是安全点?

OopMap虽然可以进行快速准确的进行GC Root枚举,但是由于虚拟机的指令太多,如果为每个指令都生成对应的OopMap会浪费大量的空间,所以虚拟机会在特定的位置生成OopMap,这些特定的位置称作安全点。所以程序不是在任何时候都能够进行GC,只有到达安全点才能进行GC。

安全点如何选定?

依据是否能够让程序长时间的运行为特点进行选定,由于每条指令的运行时间都十分短,所以一般选用的点为方法的调用,循环跳转,异常跳转等。

发生GC时需要线程停顿,如何让线程在发生GC时候跑到最近的安全点停顿下来?

1、抢先式中断:发生GC时中断所有的线程,如果发现某条线程不在安全点,就恢复此线程让他跑到安全点上。(虚拟机一般不使用)
2、主动式中断:不对线程进行操作。设置一个标志位,让所有的线程去轮询标志位,发现标志位为真,就自动挂起。轮询标志的地方和安全点是重合的,再加上创建对象需要分配内存的地方。

什么是安全区域?

安全点解决了GC问题,但是当发生GC的时候线程处于sleep状态,此时线程无法响应中断请求。此时需要使用安全区域进行解决。安全区域就是代码片段中引用关系不会发生变化的地方。当线程执行到安全区域的时候,会对线程进行标记,发生GC时候,jvm不管这些线程,在GC的时候,如果这些线程要离开安全区域,此时,判断jvm是否已经完成GC,如果完成,则线程执行,如果没有完成,则线程停顿等待GC完成的信号。(当线程发生sleep时正处于安全区域)

可达性算法不可达的对象就一定会被回收吗?

不一定,当发现对象不可达的时候,将会对此对象进行第一次标记,对标记的对象就行筛选,筛选的条件是是否有必要执行finalize()方法。
当此对象已经调用过finalize()方法或者在对象中没有覆盖finalize()方法,则判定次对象没有必要执行finalize()方法。
没有必要执行finalize()方法的对象将会直接被回收。
有必要执行finalize()方法的对象放在一个队列中,之后有虚拟机创建一个低优先级的线程去出发队列中的对象的finalize()方法,注意此处为触发并非等待finalize()执行结束,防止finalize()方法中出现死循环导致回收系统崩溃。
当一个对象的finalize()方法执行结束后,方法并没有被回收,稍后会对队列中的对象进行二次标记,此时标记的依据是对象是否可达。如果还是不可达,才会将此对象放入即将回收的集合。所以finalize()方法中如果为对象添加引用链,可以拯救此对象。
注意:每个对象的finalize()方法只会被jvm调用一次,如果一个对象在第一次执行finalize()时候被拯救,在下次执行回收会直接对对象就行回收,将不会调用对象的finalize()方法。

方法区中没有垃圾回收?

方法区有垃圾回收,但是回收的效率低。
方法区只要回收废弃的常量和无用的类。
如果没有任何地方对此常量进行引用,则此常量就会被回收。

方法区中哪些类是无用的类?

(1)java堆中不存在该类的任何实例。
(2)加载该类的ClassLoader已经被回收。
(3)该类的class对象没有任何地方被引用。
满足以上三个条件的类可以被回收,而不是和java堆中的对象一样必然会被回收。

对象的创建是如何创建的(new)?:

当虚拟机遇到new指令的时候,先去检查这个指令的参数是否能够在常量池中定位到一个符号的引用(类的全限定名),在判断这个符号引用代表的类是否已经被加载,如果没有被加载,将会进行类的加载操作。通过加载检查后,将对类分配内存。

对象创建时内存分配方式?

1、指针碰撞,对象需要多大的内存在类加载完成的时候就已经确定,此内存分配方式相当于,将已分配的内存放在放在一边,为分配的内存放在另一边,中间用指针进行隔离,当需要分配内存的时候只需要移动指针即可。
2、空闲列表,即维护一个列表,记录内存中还没有分配的内存位置。

分配内存的过程并非是线程安全的解决方案。

1、失败重试。
2、为每个线程分配本地线程缓冲区(TLAB),当TLAB使用完之后分配新的TLAB的时候,实行同步锁定。分配结束后,将分配的内存除对象头外,其余初始化为0值。如果使用TLAB,则初始化为0值提前到TLAB分配时执行。

GC对内存的分配。

内存一般分为新生代和老年代,新生代又分为一块较大的Eden和两块较小的Survivor区域(HotSpot虚拟机E:S=8:1),
新生的对象优先分配在新生代的E区,如果启用本地线程缓冲,优先在TLAB上进行分配,少数情况也会直接在老年代进行对象的分配,当在E分配对象发现内存不够使用的时候,会发生新生代的GC将E区对象转入S区,当发现S区无法存放时,通过分配担保将对象转入老年代。

对象进入老年代的判定。

大对象直接进入老年代(阀值可以通过参数进行设定),为了避免E区和S区之间发生大量的内存复制。
长期存活的对象进入老年代,虚拟机给每个对象定义了一个年龄计数器,在E区中的对象经过一次GC仍然存活并能够被S区容纳,设置此对象的年龄为1,在S区中的对象,每熬过一次GC,就将年龄加1,当年龄达到一定的程度(默认是15,可以通过参数进行设置)就会进入老年代,
动态年龄判断,不一定只有年龄达到阀值才会进入老年代,当相同年龄的对象的总大小大于S空间的一半,则大于等于这个年龄的对象将会进入老年代。

什么是空间分配担保?

当新生代发生GC的时候,先判断老年代中最大可用的连续空间大小是否大于新生代中的总对象的大小,如果是,则发生进行新生代的GC,如果不是,则判断是否允许担保失败,如果不允许,则进行老年代的GC,如果允许,则判断老年代中最大可用的连续空间大小是否大于新生代每次发生GC时进入老年代的对象的平均值,如果是,则进行新生代的GC,如果不是,则进行一次老年代的GC。

垃圾回收算法有那些?

1、标记-清除:标记所有需要被回收的对象,然后回收。次算法效率低,并且产生内存碎片,由于老年代中存活的对象多,在老年代中进行使用。
2、复制算法:将内存划分为等大小的两块,每次使用其中的一块,回收时,将存活的对象复制到没有使用的一块内存中,然后对使用的内存一次性就行清理。实现简单,运行高效,但是存在大量内存的浪费。由于新生代中存活的对象少,新生代中使用这种算法将E区存活的对象复制到S区。
3、标记整理算法:让所有存活的对象往一侧移动,然后清楚另一侧。在老年代中使用这种算法,避免产生内存碎片。

以下是我根据书本上的内容总结的垃圾收集器的内容:

垃圾收集器的种类与分类

新生代收集器:

1.Serial收集器(标记整理算法)

单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集的时候,必须暂停其他所有的工作线程,直到它收集结束。(Stop The World)
② 它是虚拟机运行在Client模式下的默认新生代收集器,简单而高效
③ Serial收集器可与CMS收集器配合工作。

2.ParNew收集器(标记整理算法)

① ParNew收集器就是Serial收集器的多线程版本。
② ParNew收集器可与CMS收集器配合工作,

3.Parallel Scavenge收集器(复制算法)

① 目的是达到一个可控制的吞吐量,也称“吞吐量优先收集器”。

吞吐量=CPU用于运行用户代码的时间/(运行用户代码时间+垃圾收集时间)。
比如虚拟机共运行100分钟,其中垃圾收集花费1分钟,那吞吐量就是99%。

② 两个参数用户精确控制吞吐量:

最大垃圾收集停顿时间(MaxGCPauseMillis):并不是越小越快,减少时间是以牺牲吞吐量和新生代空间来换取的。
吞吐量大小(GCTimeRatio):参数值是一个大于0小于100的整数,也就是垃圾收集时间占总时间的比率,相当于是吞吐量的倒数。如果把此参数设置为19,那允许的最大GC时间就占总时间的5%(即1/(1+19)),默认值为99,就是允许最大1%(即1/(1+99))的垃圾收集时间。

③ GC自适应的调节策略(UseAdaptiveSizePolicy)

参数UseAdaptiveSizePolicy是一个开关参数,打开之后就不需要手工指定新生代的大小、Eden与Survivor区的比例、晋升老年代对象的年龄等细节参数了,虚拟机会根据当前系统的运行情况手机性能监控信息,动态调整这些参数以提供最适合 的停顿时间或者最大吞吐量,这种调节方式成为GC自适应的调节策略

老年代收集器:

1.CMS(标记清除算法)

① 追求系统停顿时间最短,并发收集、低停顿。
② 4个步骤:

初始标记:需要Stop The World,作用仅仅是标记一下GC ROOTS能直接关联到的对象,速度很快。
并发标记:进行GC ROOTS Tracing的过程。
重新标记:需要Stop The World,作用是修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,停顿时间一般比标记阶段稍长,比并发标短。
并发清除:清除过程与用户线程一起工作。
③ 3个缺点:
**对CPU资源非常敏感。CPU数量越少,占用资源越大;CPU数量越多,占用资源越小。CMS默认启动的回收线程数是(CPU数量+3)/4,当CPU不足4个时,CMS对用户程序的影响就可能变得很大。比如2个CPU,那要分出一半的运算能力去执行收集线程。
**无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。
**因为是基于“标记清除”算法实现的收集器,意味着在收集结束后会有大量的空间碎片产生,可能无法给大对象分配空间,从而不得不提取Full GC。为解决这个问题CMS收集器提供了UseCMSCompactAtFullCollection开关(默认开启),用于在CMS收集器顶不住要进行Full GC的时候开启内存碎片的整合过程,但内存整理过程是无法并发的,所以停顿时间会变长。另一个参数CMSFullGCsBeforeCompaction,这个参数是用于设置执行多少次不压缩的Full GC后,跟着来一次带压缩的。

2.Serial Old(标记整理算法)

① Serial Old是Serial收集器的老年代版本,同样是一个单线程收集器,给Client模式下的虚拟机使用。
② Server模式下的两大用途:

在JDK1.5及之前的版本中与Parallel Scavenge收集器搭配使用。
作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。

③ 什么是Client模式?什么是Server模式?

JVM有两种运行模式Server与Client。两种模式的区别在于,Client模式启动速度较快,Server模式启动较慢;但是启动进入稳定期长期运行之后Server模式的程序运行速度比Client要快很多。这是因为Server模式启动的JVM采用的是重量级的虚拟机,对程序采用了更多的优化;而Client模式启动的JVM采用的是轻量级的虚拟机。所以Server启动慢,但稳定后速度比Client远远要快。

3.Parallel Old(标记整理算法)

① Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和标记整理算法。
② 在注重吞吐量及CPU资源敏感的场合,可以优先考虑Parallel Scavenge加Parallel Old收集器组合。

全代收集器(新生代+老年代)

1.G1收集器(标记整理算法+复制算法)

特点:

并行与并发:充分利用多CPU、多核环境缩短Stop The World的时间。仍可通过并发方式让Java程序继续运行。
分代收集
空间整合:基于“标记整理算法”实现的收集器,从局部上(两个Region之间)来看是“复制算法”实现的。不会产生内存空间碎片。比CMS好。
可预测的停顿:这是G1相对于CMS的另一大优势。建立可预测的停顿时间模型。

Java堆的内存布局:

将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。

可预测的停顿时间模型:

G1跟踪各个Region里的垃圾堆积的价值大小(回收空间+回收时间),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region

Remember Set:

在G1收集器中,Region之间的对象引用导致在判断对象是否存活时需要全堆扫描。而Remember Set是来避免全堆扫描的。G1中每个Region都有一个对应的Remember Set,虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之间,如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Remember Set之中。
当进行内存回收时,在GC根节点的枚举范围中加入Remember Set即可保证不对全堆扫描也不会有遗漏。

运作步骤:

a.初始标记
b.并发标记
c.最终标记
d.筛选回收

我看到一篇文章,里面详细的记录了G1的内容,比书里还详细,贴出地址:
https://blog.csdn.net/lijingyao8206/article/details/80513383

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值