• 暂时停更一段时间!
  • 近期网站将陆续进行前端页面改造!
  • 招募网站编辑,联系站长!

JVM面试知识点解析(三)GC 相关

极客题库 Geekerstar 来源:博客园 6个月前 (05-23) 215次浏览 已收录 0个评论 扫描二维码
文章目录[隐藏]

1)如何判断一个对象是否已经死去?

答:

引用计数:每个对象有一个引用计数属性,新增一个引用时计数加 1,引用释放时计数减 1,计数为 0 时可以回收。此方法简单,无法解决对象相互循环引用的问题。
可达性分析(Reachability Analysis):从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的。不可达对象。

2)垃圾回收算法有哪些?

答:

引用计数:
原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为 0 的对象。此算法最致命的是无法处理循环引用的问题。

标记-清除:
此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。

复制算法:
此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。

标记-整理:
此算法结合了 “标记-清除” 和 “复制” 两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了 “标记-清除” 的碎片问题,同时也避免了 “复制” 算法的空间问题。

分代收集算法:
分代收集算法并没有提出新的思想,只是根据对象存活周期的不同将内存划为几块。一般 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用适当的收集算法。
在新生袋中每次垃圾手机时都会由大批对象死去,只有少量存活,那就用复制算法,只需要付出少量存活对象的复制成本就可以。老年代中对象存活率高、没有额外担保,所以必须使用“标记-清理”或者“标记整理算法。

3)GC 什么时候开始?

答:GC 经常发生的区域是堆区,堆区还可以细分为新生代、老年代,新生代还分为一个 Eden 区和两个 Survivor 区。

  • 对象优先在 Eden 中分配,当 Eden 中没有足够空间时,虚拟机将发生一次 Minor GC,因为 Java 大多数对象都是朝生夕灭,所以 Minor GC 非常频繁,而且速度也很快;
  • Full GC,发生在老年代的 GC,当老年代没有足够的空间时即发生 Full GC,发生 Full GC 一般都会有一次 Minor GC。大对象直接进入老年代,如很长的字符串数组,虚拟机提供一个-XX:PretenureSizeThreadhold 参数,令大于这个参数值的对象直接在老年代中分配,避免在 Eden 区和两个 Survivor 区发生大量的内存拷贝;
  • 发生 Minor GC 时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则进行一次 Full GC,如果小于,则查看 HandlePromotionFailure 设置是否允许担保失败,如果允许,那只会进行一次 Minor GC,如果不允许,则改为进行一次 Full GC。

4)引用的分类?

答:

  • 强引用:通过 new 出来的引用,只要强引用还存在,则不会回收。
  • 软引用:通过 SoftReference 类来实现,用来描述一些有用但非必须的对象。在系统将要发生内存溢出异常之前,会把这些对象回收了,如果这次回收还是内存不够的话,才抛出内存溢出异常。
  • 弱引用:非必须对象,通过 WeakReference 类来实现,被弱引用引用的对象,只要已发生 GC 就会把它干掉。
  • 虚引用:通过 PhantomReference 类来实现,无法通过徐引用获得对象的实例,唯一作用就是在这个对象被 GC 时会收到一个系统通知。

5)垃圾收集器?


解析:如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现

答:

1. Serial 收集器

串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;垃圾收集的过程中会 Stop The World(服务暂停)

2.ParNew 收集器

ParNew 收集器 ParNew 收集器其实就是 Serial 收集器的多线程版本。新生代并行,老年代串行;新生代复制算法、老年代标记-压缩

参数控制:
-XX:+UseParNewGC ParNew 收集器
-XX:ParallelGCThreads 限制线程数量

3.Parallel Scavenge 收集器

Parallel Scavenge 收集器类似 ParNew 收集器,Parallel 收集器更关注系统的吞吐量。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制 GC 的时间不大于多少毫秒或者比例;新生代复制算法、老年代标记-压缩

参数控制: -XX:+UseParallelGC 使用 Parallel 收集器+ 老年代串行

4.Parallel Old 收集器

Parallel Old 是 Parallel Scavenge 收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在 JDK 1.6 中才开始提供

参数控制: -XX:+UseParallelOldGC 使用 Parallel 收集器+ 老年代并行

5.CMS 收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的 Java 应用都集中在互联网站或 B/S 系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。

从名字(包含“Mark Sweep”)上就可以看出 CMS 收集器是基于“标记-清除”算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为 4 个步骤,包括:

  • 初始标记(CMS initial mark)
  • 并发标记(CMS concurrent mark)
  • 重新标记(CMS remark)
  • 并发清除(CMS concurrent sweep)

其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,并发标记阶段就是进行 GC Roots Tracing 的过程,而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。

由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS 收集器的内存回收过程是与用户线程一起并发地执行。老年代收集器(新生代使用 ParNew)

优点: 并发收集、低停顿
缺点: 产生大量空间碎片、并发阶段会降低吞吐量

参数控制:
-XX:+UseConcMarkSweepGC 使用 CMS 收集器
-XX:+ UseCMSCompactAtFullCollection Full GC 后,进行一次碎片整理;整理过程是独占的,会引起停顿时间变长
-XX:+CMSFullGCsBeforeCompaction 设置进行几次 Full GC 后,进行一次碎片整理
-XX:ParallelCMSThreads 设定 CMS 的线程数量(一般情况约等于可用 CPU 数量)

6.G1 收集器

G1 是目前技术发展的最前沿成果之一,HotSpot 开发团队赋予它的使命是未来可以替换掉 JDK1.5 中发布的 CMS 收集器。与 CMS 收集器相比 G1 收集器有以下特点:

  • 空间整合,G1 收集器采用标记整理算法,不会产生内存空间碎片。分配大对象时不会因为无法找到连续空间而提前触发下一次 GC。
  • 可预测停顿,这是 G1 的另一大优势,降低停顿时间是 G1 和 CMS 的共同关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 N 毫秒的时间片段内,消耗在垃圾收集上的时间不得超过 N 毫秒,这几乎已经是实时 Java(RTSJ)的垃圾收集器的特征了。

上面提到的垃圾收集器,收集的范围都是整个新生代或者老年代,而 G1 不再是这样。使用 G1 收集器时,Java 堆的内存布局与其他收集器有很大差别,它将整个 Java 堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region 的集合。

G1 的新生代收集跟 ParNew 类似,当新生代占用达到一定比例的时候,开始出发收集。和 CMS 类似,G1 收集器收集老年代对象会有短暂停顿。

收集步骤:

1、标记阶段,首先初始标记(Initial-Mark),这个阶段是停顿的(Stop the World Event),并且会触发一次普通 Mintor GC。对应 GC log:GC pause (young) (inital-mark)

2、Root Region Scanning,程序运行过程中会回收 survivor 区(存活到老年代),这一过程必须在 young GC 之前完成。

3、Concurrent Marking,在整个堆中进行并发标记(和应用程序并发执行),此过程可能被 young GC 中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那个这个区域会被立即回收(图中打 X)。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。

4、Remark, 再标记,会有短暂停顿(STW)。再标记阶段是用来收集 并发标记阶段 产生新的垃圾(并发阶段和应用程序一同运行);G1 中采用了比 CMS 更快的初始快照算法:snapshot-at-the-beginning (SATB)。

5、Copy/Clean up,多线程清除失活对象,会有 STW。G1 将回收区域的存活对象拷贝到新区域,清除 Remember Sets,并发清空回收区域并把它返回到空闲区域链表中。

6、复制/清除过程后。回收区域的活性对象已经被集中回收到深蓝色和深绿色区域。

其他 JVM 相关面试题整理


1)64 位 JVM 中,int 的长度是多数?

答:Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位或者 4 个字节。意思就是说,在 32 位 和 64 位 的 Java 虚拟机中,int 类型的长度是相同的。

2)怎样通过 Java 程序来判断 JVM 是 32 位 还是 64 位?

答:Sun 有一个 Java System 属性来确定 JVM 的位数:32 或 64:

sun.arch.data.model=32 // 32 bit JVM
sun.arch.data.model=64 // 64 bit JVM
我可以使用以下语句来确定 JVM 是 32 位还是 64 位:

System.getProperty(“sun.arch.data.model”)

3)32 位 JVM 和 64 位 JVM 的最大堆内存分别是多数?

答:理论上说上 32 位的 JVM 堆内存可以到达 2^32,即 4GB,但实际上会比这个小很多。不同操作系统之间不同,如 Windows 系统大约 1.5 GB,Solaris 大约 3GB。64 位 JVM 允许指定最大的堆内存,理论上可以达到 2^64,这是一个非常大的数字,实际上你可以指定堆内存大小到 100GB。甚至有的 JVM,如 Azul,堆内存到 1000G 都是可能的。

4)你能保证 GC 执行吗?

答:不能,虽然你可以调用 System.gc() 或者 Runtime.gc(),但是没有办法保证 GC 的执行。

5)怎么获取 Java 程序使用的内存?堆使用的百分比?

答:可以通过 java.lang.Runtime 类中与内存相关方法来获取剩余的内存,总内存及最大堆内存。通过这些方法你也可以获取到堆使用的百分比及堆内存的剩余空间。Runtime.freeMemory() 方法返回剩余空间的字节数,Runtime.totalMemory() 方法总内存的字节数,Runtime.maxMemory() 返回最大内存的字节数。

6)Java 中堆和栈有什么区别?

答:JVM 中堆和栈属于不同的内存区域,使用目的也不同。栈常用于保存方法帧和局部变量,而对象总是在堆上分配。栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。


丨极客文库, 版权所有丨如未注明 , 均为原创丨
本网站采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行授权
转载请注明原文链接:JVM 面试知识点解析(三)GC 相关
喜欢 (0)
[247507792@qq.com]
分享 (0)

邀请您免费 注册账号 登录 即可参与讨论!