上次已经介绍了初识Java虚拟机和浅谈Java垃圾回收,今天来总结一下面试中常常遇到的有关Java虚拟机的面试题。程序员
简述Java垃圾回收机制
JVM内存分哪几个区,每一个区的做用是什么?
如何判断一个对象是否存活?
Java中垃圾收集的方法有哪些?
谈谈类加载器双亲委派模型机制
类加载器有哪些
请解释StackOverflowError和OutOfMemeryError的区别?
GC的回收流程是怎样的
在Java中,程序员是不须要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是 低优先级的,在正常状况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。
Java虚拟机主要分为如下几个区:面试
方法区
1. 有时候也成为永久代,在该区内不多发生垃圾回收,可是并不表明不发生GC,在这里进行的GC主要是对方法区里的常量池和对类型的卸载
2. 方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。
3. 该区域是被线程共享的。
4. 方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。算法
虚拟机栈
1. 虚拟机栈也就是咱们日常所称的栈内存,它为Java方法服务,每一个方法在执行的时候都会建立一个栈帧,用于存储局部变量表、操做数栈、动态连接和方法出口等信息。
2. 虚拟机栈是线程私有的,它的生命周期与线程相同。segmentfault
本地方法栈
本地方法栈和虚拟机栈相似,只不过本地方法栈为Native方法服务。线程
堆
Java堆是全部线程所共享的一块内存,在虚拟机启动时建立,几乎全部的对象实例都在这里建立,所以该区域常常发生垃圾回收操做。code
程序计数器
内存空间小,字节码解释器工做时经过改变这个计数值能够选取下一条须要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等功能都须要依赖这个计数器完成。对象
判断一个对象是否存活有两种方法:递归
引用计数法
所谓引用计数法就是给每个对象设置一个引用计数器,每当有一个地方引用这个对象时,就将计数器加一,引用失效时,计数器就减一。当一个对象的引用计数器为零时,说明此对象没有被引用,也就是“死对象”,将会被垃圾回收.
引用计数法有一个缺陷就是没法解决循环引用问题,也就是说当对象A引用对象B,对象B又引用者对象A,那么此时A,B对象的引用计数器都不为零,也就形成没法完成垃圾回收,因此主流的虚拟机都没有采用这种算法。生命周期
可达性算法
该算法的思想是:从一个被称为GC Roots的对象开始向下搜索,若是一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。内存
标记-清除
这是垃圾收集算法中最基础的,根据名字就能够知道,它的思想就是标记哪些要被回收的对象,而后统一回收。这种方法很简单,可是会有两个主要问题:1.效率不高,标记和清除的效率都很低;2.会产生大量不连续的内存碎片,致使之后程序在分配较大的对象时,因为没有充足的连续内存而提早触发一次GC动做。
复制算法
为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,而后每次只使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,而后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。可是这种方式,内存的代价过高,每次基本上都要浪费一半的内存。
因而将该算法进行了改进,内存区域再也不是按照1:1去划分,而是将内存划分为8:1:1三部分,较大那分内存交Eden区,其他是两块较小的内存区叫Survior区。每次都会优先使用Eden区,若Eden区满,就将对象复制到第二块内存区上,而后清除Eden区,若是此时存活的对象太多,以致于Survivor不够时,会将这些对象经过分配担保机制复制到老年代中。(Java堆又分为新生代和老年代)
标记-整理
该算法主要是为了解决标记-清除,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不一样之处就是在清除对象的时候现将可回收对象移动到一端,而后清除掉端边界之外的对象,这样就不会产生内存碎片了。
分代收集
如今的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,因为对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,因此可使用标记-整理 或者 标记-清除。
当一个类收到了类加载请求时,不会本身先去加载这个类,而是将其委派给父类,由父类去加载,若是此时父类不能加载,反馈给子类,由子类去完成类的加载。
- Bootstrap ClassLoader
- Extensions ClassLoader
- App ClassLoader
- 用户自定义类加载器
StackOverflowError栈溢出,通常因为递归过多,调用方法过多致使
OutOfMemeryError堆内存溢出,即OOM,因为堆内存中没有被GC回收的对象过多致使。
出现OOM的缘由:
GC回收流程以下:
对于整个的GC流程里面,那么最须要处理的就是新生代和老年代的内存清理操做,而元空间(永久代)都不在GC范围内
当如今有一个新的对象产生,那么对象必定须要内存空间。
一、首先会 判断Eden区是否有内存空间,若是此时有内存空间,则直接将新对象保存在Eden区。二、可是若是此时在Eden区内存不足,那么会自动执行一个Minor GC 操做,将Eden区的无用内存空间进行清理,Minor GC的清理范围只在Eden区,清理以后会继续判断Eden区的内存空间是否充足?若是内存空间充足,则将新对象直接在Eden区进行空间分配。
三、若是执行Minor GC 以后发现Eden区的内存空间依然不足,那么这个时候会执行Survivor区的判断,若是Survivor区有剩余空间,则将Eden区部分活跃对象保存在Survivor区,那么随后继续判断Eden区的内存空间是否充足,若是充足怎则将新对象直接在Eden区进行空间分配。
四、此时若是Survivor区没有内存空间,则继续判断老年区。若老年区有剩余内存则将部分存活对象保存在老年代。
五、若是这个时候老年代也满了,那么这个时候将产生Major GC(Full GC),那么这个时候将进行老年代的清理。
六、若是老年代执行Full GC以后,没法进行对象的保存,则会产生OOM异常,OutOfMemoryError异常