知识犹如人体的血液一样宝贵。——高士其
JVM面试相关知识汇总:
上图中,内存模型、类加载机制、GC垃圾回收是比较重点的内容。
1. 简单描述JVM的内存模型
回答这个问题,要答出两个要点:一个是各部分的功能,另一个是哪些线程共享,哪些线程独占。
- 栈,也叫方法栈,是线程私有的,线程在执行每个方法时都会同时创建一个栈帧,用来存储局部变量表、操作栈、动态链接、方法出口等信息。调用方法时执行入栈,方法返回时执行出栈。
- 本地方法栈与栈类似,也是用来保存线程执行方法时的信息,不同的是,执行 Java 方法使用栈,而执行 native 方法使用本地方法栈。
- 程序计数器保存着当前线程所执行的字节码位置,每个线程工作时都有一个独立的计数器。程序计数器为执行 Java 方法服务,执行 native 方法时,程序计数器为空。
以上三个部分:栈、本地方法栈、程序计数器,都是线程独占的。
- 堆是JVM管理的内存中最大的一块,堆被所有线程共享,目的是为了存放对象实例,几乎所有的对象实例都在这里分配。当堆内存没有可用的空间时,会抛出OOM异常。根据对象存活的周期不同,JVM把堆内存进行分代管理,由垃圾回收器来进行对象的回收管理。
- 方法区也是各个线程共享的内存区域,又叫非堆区。用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,JDK1.7中的永久代和JDK1.8中的Metaspace都是方法区的一种实现。
2.什么情况会触发FullGC?
- 老年代空间不足——如果创建的对象很大,Eden区域放不下这个对象,会放入到老年代中。如果老年代空间也不足,就会触发Full GC。为了避免这种情况,最好就是不要创建太大的对象。
- 永久代空间不足——这主要是针对JDK7以及以前的版本。当系统中需要加载和调用的类很多,而同时持久代当中没有足够的空间去存放类的信息和方法信息的时候,就会触发出一次Full GC。而JDK8以后由于取消了永久代,就不存在”永久代空间不足”这种情况了。(这也是JDK8后面用元空间替代永久代的原因之一,为了降低Full GC的频率,减少GC的负担,提升其效率)
- CMS GC时出现promotion failed, concurrent mode failure。对于采用CMS 进行老年代GC的程序而言,如果GC日志中出现了这两个字段。如果出现了,可能会触发Full GC。
- Minor GC晋升到老年代的平均大小大于老年代的剩余空间
- 调用System.gc()——这个是我们在程序里面手动调用的,触发Full GC。需要注意这个方法只是提醒虚拟机,程序员希望你在这里回收一下对象。但是具体怎么做还是要看虚拟机自己,程序员没有控制权
- 使用RMI来进行RPC或管理的JDK应用,每小时执行1次Full GC
这些点很多,面试的时候只要能够提到3点,基本可以点到为止了,可以答到老年代空间不足,程序手动调用System.gc(),然后如果用的JDK版本比较老,在JDK8之前的版本,会有永久代空间不足的情况。当然其他的能说出来更好。
需要注意:
1.promotion failed是在进行Minor GC的时候Survivor放不下了,对象只能放入老年代,而此时恰好老年代也放不下,这时候就会造成promotion failed。
2.concurrent mode failure是在执行CMS GC的过程中同时有对象要放入老年代中,而此时老年代空间不足,就会造成这个failure。
而对于Minor GC晋升的这第四点,是比较复杂的触发情况。HotSpot为了避免由于新生代对象晋升到老年代而导致老年代空间不足的现象,在进行Minor GC的时候做了一个判断:如果之前统计所得到的Minor GC晋升到老年代的平均大小大于老年代的剩余空间,就直接触发Full GC。例如,程序第一次触发GC后有6M的对象晋升到老年代,当下一次Minor GC发生的时候,首先先检查老年代的剩余空间是否大于6M,如果小于6M,则执行Full GC。