29

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

 4 years ago
source link: https://mp.weixin.qq.com/s/t-7YoNcFbmtoFuM6o-BNHw
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

面试官 :如何在一个方法中创建一个局部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分析垃圾回收信息,调整内存大小。

  • 追求吞吐量优先

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

推荐阅读:

面试官: JVM对锁进行了优化,都优化了啥?

synchronized连环问

如有收获,请点击底部右下角" 在看 ",谢谢!

MfUNZvm.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK