JVM常见面试题整理
内存模型以及分区,需要详细到每个区放什么。
JVM 分为堆区和栈区,还有方法区,初始化的对象放在堆里面,引用放在栈里面,
class 类信息常量池(static 常量和 static 变量)等放在方法区
- 方法区:主要是存储类信息,常量池(static 常量和 static 变量),编译后的代码(字
节码)等数据 - 堆:初始化的对象,成员变量 (那种非 static 的变量),所有的对象实例和数组都要
在堆上分配 - 栈:栈的结构是栈帧组成的,调用一个方法就压入一帧,帧上面存储局部变量表,操
作数栈,方法出口等信息,局部变量表存放的是 8 大基础类型加上一个应用类型,所
以还是一个指向地址的指针 - 本地方法栈:主要为 Native 方法服务
- 程序计数器:记录当前线程执行的行号
GC 收集器有哪些?CMS 收集器与 G1 收集器的特点。
- 并行收集器:串行收集器使用一个单独的线程进行收集,GC 时服务有停顿时间
- 串行收集器:次要回收中使用多线程来执行
- CMS 收集器是基于“标记—清除”算法实现的,经过多次标记才会被清除
- G1 从整体来看是基于“标记—整理”算法实现的收集器,从局部(两个 Region 之间)
上来看是基于“复制”算法实现的
Minor GC 与 Full GC 分别在什么时候发生?
新生代内存不够用时候发生 MGC 也叫 YGC,JVM 内存不够的时候发生 FGC
堆里面的分区:Eden,survival (from+ to),老年代,各自的特点。
堆里面分为新生代和老生代(java8 取消了永久代,采用了 Metaspace),新生代包
含 Eden+Survivor 区,survivor 区里面分为 from 和 to 区,内存回收时,如果用的是复
制算法,从 from 复制到 to,当经过一次或者多次 GC 之后,存活下来的对象会被移动
到老年区,当 JVM 内存不够用的时候,会触发 Full GC,清理 JVM 老年区
当新生区满了之后会触发 YGC,先把存活的对象放到其中一个 Survice
区,然后进行垃圾清理。因为如果仅仅清理需要删除的对象,这样会导致内存碎
片,因此一般会把 Eden 进行完全的清理,然后整理内存。那么下次 GC 的时候,
就会使用下一个 Survive,这样循环使用。如果有特别大的对象,新生代放不下,
就会使用老年代的担保,直接放到老年代里面。因为 JVM 认为,一般大对象的存
活时间一般比较久远。
简述 java 垃圾回收机制?
在 java 中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在
JVM 中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚
拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将
它们添加到要回收的集合中,进行回收。
java 中垃圾收集的方法有哪些?
- 标记-清除:
这是垃圾收集算法中最基础的,根据名字就可以知道,它的思想就是标记哪些要被
回收的对象,然后统一回收。这种方法很简单,但是会有两个主要问题:1.效率不
高,标记和清除的效率都很低;2.会产生大量不连续的内存碎片,导致以后程序在
分配较大的对象时,由于没有充足的连续内存而提前触发一次 GC 动作。 - 复制算法:
为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,然后每次只
使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,然
后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一般的内存。
于是将该算法进行了改进,内存区域不再是按照 1:1 去划分,而是将内存划分为
8:1:1 三部分,较大那份内存交 Eden 区,其余是两块较小的内存区叫 Survior 区。
每次都会优先使用 Eden 区,若 Eden 区满,就将对象复制到第二块内存区上,然
后清除 Eden 区,如果此时存活的对象太多,以至于 Survivor 不够时,会将这些对
象通过分配担保机制复制到老年代中。(java 堆又分为新生代和老年代) - 标记-整理
该算法主要是为了解决标记-清除,产生大量内存碎片的问题;当对象存活率较高
时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候现将可回
收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了。 - 分代收集
现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生
代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那
么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担
保,所以可以使用标记-整理 或者 标记-清除。
类加载器双亲委派模型机制?什么是类加载器,类加载器有哪些?
当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类
去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。
实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。
主要有一下四种类加载器:
- 启动类加载器(Bootstrap ClassLoader)用来加载 java 核心类库,无法被 java 程序直接
引用。 - 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的
实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。 - 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)
来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过
ClassLoader.getSystemClassLoader()来获取它。 - 用户自定义类加载器,通过继承 java.lang.ClassLoader 类的方式实现。
简述 java 内存分配与回收策率以及 Minor GC 和Major GC
- 对象优先在堆的 Eden 区分配。
- 大对象直接进入老年代.
- 长期存活的对象将直接进入老年代.
当 Eden 区没有足够的空间进行分配时,虚拟机会执行一次 Minor GC.Minor Gc 通
常发生在新生代的 Eden 区,在这个区的对象生存期短,往往发生 Gc 的频率较高,
回收速度比较快;Full Gc/Major GC 发生在老年代,一般情况下,触发老年代 GC
的时候不会触发 Minor GC,但是通过配置,可以在 Full GC 之前进行一次 Minor
GC 这样可以加快老年代的回收速度。