就写了一行代码,被问了这么多问题

面试官:如何在一个方法中创建一个局部byte类型数组?

小白:(是不是太基础了,暗笑)byte[] arrays = new byte[1024]。

面试官:这个局部arrays变量指向的数组对象什么时候会被GC回收?

小白:没有变量引用这个数组对象,或者arrays在虚拟机栈中的局部变量表的局部变量空间(Slot)被重用,发生垃圾回收时将会被回收掉。

面试官:数组对象没有被变量引用会被GC回收,为什么?

小白:JVM通过一系列被称为"GC Roots"的对象引用作为起始点,通过引用关系遍历对象,能被遍历到的(可到达的)对象就被判定为存活对象,没有被遍历到的(不可到达的)对象就被判定为死亡对象,找出所有存活对象来把其它对象判定为可回收对象,这就是可达性分析算法。当这个局部arrays变量所在的方法被执行时,会在当前线程的Java虚拟机栈中创建一个栈帧,这个栈帧的局部变量表中会存储arrays变量所指向的数组指针,当设置arrays=null,也就是arrays不再引用这个数组对象,arrays和这个数组对象之间的引用关系就断掉了,发生垃圾回收时,以Java虚拟机栈的栈帧中里的引用类型的变量为"GC Roots”,遍历引用关系,发现这个数组对象和"GC Roots”引用链之间没有关联了,也就是不可达,即被标识为可回收对象,等待被回收。

面试官:除了你刚刚说的Java虚拟机栈的栈帧里的引用类型局部变量可以作为"GC Roots”,还有哪些也可以作为"GC Roots”?

小白:当前所有正在被调用的方法里的引用类型的参数、局部变量和临时值;Java类的引用类型静态变量;所有当前被启动类加载器或系统类加载器加载的Java类,例如如rt.jar中的java.util.*;Java类的运行时常量池里的引用类型常量;String常量池里的引用;本地方法栈中JNI的引用;虚拟机里的一些静态数据结构里指向GC堆里的对象的引用,例如说HotSpot VM里的Universe里有很多这样的引用。

面试官:当一个对象被标识为可回收对象就一定会被回收掉吗?

小白:不一定。一个对象被标识为可回收对象后,还需要经过再次筛选,即查看这个对象有没有覆盖finalize()方法,或finalize()方法有没有被虚拟机执行过,如果没有覆盖finalize()方法或finalize()方法已经被虚拟机执行过,那么这个对象将会被回收掉,否则这个对象将会被放到一个叫F-Queue的队列中,这个队列中对象的finalize()方法将会被虚拟机创建的低优先级的Finalizer线程执行,在执行finalize()方法的过程中,只要这个对象和GC Roots引用链产生关联,即再次被GC Roots集合中的成员引用,那么它将被标记为不可回收对象,继续存活。

面试官:刚刚一直说到垃圾回收,那么Minor GC、Major GC和Full GC有什么区别?

小白:Minor GC指新生代GC,即发生在新生代(包括Eden区和Survivor区)的垃圾回收操作,当新生代无法为新生对象分配内存空间的时候,会触发Minor GC。因为新生代中大多数对象的生命周期都很短,所以发生Minor GC的频率很高,虽然它会触发stop-the-world,但是它的回收速度很快。Major GC清理Tenured区,用于回收老年代,出现Major GC通常会出现至少一次Minor GC。Full GC是针对整个新生代、老生代、元空间(metaspace,java8以上版本取代perm gen)的全局范围的GC。Full GC不等于Major GC,也不等于Minor GC+Major GC,发生Full GC需要看使用了什么垃圾收集器组合,才能解释是什么样的垃圾回收。

面试官:垃圾回收算法有哪些?

小白:标记-清除算法分为两部分,标记和清除。首先标记出所有需要被回收的对象,然后在标记完成后统一回收掉所有被标记的对象。这个算法简单,但是有两个缺点:一是标记和清除的效率不是很高;二是标记和清除后会产生很多的内存碎片,导致可用的内存空间不连续,当分配大对象的时候,没有足够的空间时不得不提前触发一次垃圾回收。

复制算法将可用的内存空间分为大小相等的两块,每次只是用其中的一块,当这一块被用完的时候,就将还存活的对象复制到另一块中,然后把原已使用过的那一块内存空间一次回收掉。这个算法常用于新生代的垃圾回收。复制算法解决了标记-清除算法的效率问题,以空间换时间,但是当存活对象非常多的时候,复制操作效率将会变低,而且每次只能使用一半的内存空间,利用率不高。

标记-整理算法分为三部分:一是标记出所有需要被回收的对象;二是把所有存活的对象都向一端移动;三是把所有存活对象边界以外的内存空间都回收掉。

标记-整理算法解决了复制算法多复制效率低、空间利用率低的问题,同时也解决了内存碎片的问题。

分代收集算法根据对象生存周期的不同将内存空间划分为不同的块,然后对不同的块使用不同的回收算法。一般把Java堆分为新生代和老年代,新生代中对象的存活周期短,只有少量存活的对象,所以可以使用复制算法,而老年代中对象存活时间长,而且对象比较多,所以可以采用标记-清除和标记-整理算法。

面试官:JVM运行时数据区中的方法区可以进行垃圾回收吗?

小白:方法区和堆一样,都是线程共享的内存区域,被用于存储已被虚拟机加载的类信息、即时编译后的代码、静态变量和常量等数据。根据Java虚拟机规范的规定,方法区无法满足内存分配需求时,也会抛出OutOfMemoryError异常,虽然规范规定虚拟机可以不实现垃圾收集,因为和堆的垃圾回收效率相比,方法区的回收效率实在太低,但是此部分内存区域也是可以被回收的。方法区的垃圾回收主要有两种,分别是对废弃常量的回收和对无用类的回收。当一个常量对象不再任何地方被引用的时候,则被标记为废弃常量,这个常量可以被回收。方法区中的类需要同时满足以下三个条件才能被标记为无用的类:Java堆中不存在该类的任何实例对象、加载该类的类加载器已经被回收、该类对应的java.lang.Class对象不在任何地方被引用,且无法在任何地方通过反射访问该类的方法,当满足上述三个条件的类才可以被回收,但是并不是一定会被回收,需要参数进行控制,例如HotSpot虚拟机提供了-Xnoclassgc参数进行控制是否回收。

面试官:如果让你配置JVM新生代和老年代的大小,你如何掌控?

小白:新生代配置原则:

  • 追求响应时间优先

这种需求下,新生代尽可能设置大一些,并通过实际情况调整新生代大小,直至接近系统的最小响应时间。因为新生代比较大,发生垃圾回收的频率会比较低,响应时间快速。

  • 追求吞吐量优先

吞吐量优先的应用,在新生代中的大部分对象都会被回收,所以,新生代尽可能设置大。此时不追求响应时间,垃圾回收可以并行进行。

  • 避免设置过小

新生代设置过小,YGC会很频繁,同时,很可能导致对象直接进入老年代中,老年代空间不足发生FullGC。

老年代配置原则:

  • 追求响应时间优先

这种情况下,可以使用CMS收集器,以获取最短回收停顿时间,但是其内存分配需要注意,如果设置小了会造成回收频繁并且碎片变多;如果设置大了,回收的时间会很长。所以,最优的方案是根据GClog分析垃圾回收信息,调整内存大小。

  • 追求吞吐量优先

吞吐量优先通常需要分配一个大新生代、小老年代,将短期存活的对象在新生代回收掉。

本文分享自微信公众号 - JavaQ(Java-Q)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-11-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏TopCoder

深入浅出Java反射

反射,它就像是一种魔法,引入运行时自省能力,赋予了 Java 语言令人意外的活力,通过运行时操作元数据或对象,Java 可以灵活地操作运行时才能确定的信息。

9420
来自专栏ImportSource

类图画法?这样记

很多新手甚至老手有时候都无法徒手画类图,其中原因可能就是关系线太难记。下面是我总结的类图UML中的概念和Java中的映射,以及对应的关系线。

8410
来自专栏TopCoder

深入浅出动态代理

代理模式是为了提供额外或者不同的操作,而插入代替”实际对象”的对象,即代理类,针对代理类的调用操作,都会涉及到与”实际对象”的通信,代理类起到中间人的作用。Ja...

6420
来自专栏TopCoder

从Redis异步到反应式架构

说到Redis的通信,我们都知道Redis基于RESP(Redis Serialization Protocol)协议来通信,并且通信方式是停等模型,也就说一次...

6720
来自专栏用户5521492的专栏

单例模式,没你想的简单

来 源:http://www.tekbroaden.com/singleton-java.html

8620
来自专栏TopCoder

java lambda 容易掉进的2个坑

这是为什么呢?因为默认情况下,也就是Collectors.toMap(k -> k, v -> v)未指定BinaryOperator<U> mergeFunc...

9620
来自专栏码力全开

[译]JS 模块化历史简介

对于 JavaScript 来说,模块化是一个相对现代的概念,这篇文章会带你在 JavaScript 的世界里快速浏览模块化的历史进程~

9420
来自专栏TopCoder

java lambda 原理分析

以上代码就会产生一个Application$1.class文件和一个lambda$main$0的方法。既然lambda实现不是内部类,那么在lambda中thi...

8230
来自专栏TopCoder

Java常见几种动态代理的对比

•JDK动态代理:运行期动态的创建代理类,只支持接口;•ASM:一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。不过ASM在创建cl...

9530
来自专栏GitHubDaily

5 门正在奋力崛起的编程语言

在软件项目与具体实现层面,我们需要考虑众多具体因素。但无论从哪种角度出发,技术栈的选择永远是决定项目成功与否的核心因素之一。根据您的实际应用需求、站点或者产品设...

8020

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励