1. JVM内存结构
2. 谈谈对jvm的理解
Java虚拟机,它是虚构出来的一种计算机,是一种规范,通过在计算机中仿真模拟真实计算机功能实现的,其内部体系结构主要由类装载器,运行时数据区,执行引擎和本地方法接口组成。
- 类装载器:将编译后的class文件加载到JVM内存中。加载class后为模板Class。
- 数据区:就是常说的JVM内存
- 执行引擎:在执行字节码,或者执行本地方法
- 本地方法接口:这个方法调用的是底层操作系统和第三方C语言函数库方法
3. 类加载器的种类
- 启动类加载器(根加载器):用来加载java核心类库,无法被java程序直接引用。
- 扩展类加载器:它用来加载 Java 的扩展库。
- 应用程序类加载器:它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。
- 用户自定义类加载器:通过继承 java.lang.ClassLoader类的方式实现。
4. 什么是类的加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
5. 说一下类装载的执行过程?
- 加载:根据查找路径找到相应的 class 文件然后导入;
- 验证:检查加载的 class 文件的正确性;
- 准备:为类变量分配内存,并将其初始化为默认值;
- 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;
- 初始化:对静态变量和静态代码块执行初始化工作。
6. 双亲委派机制
如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,最终传送到根加载器。只有当父加载无法完成加载请求时,子加载器才会尝试去加载类。即每个请求只加载从根加载器向下找到的第一个文件,保证了java源代码不被污染。
7. 说一下 JVM 运行时数据区
- 方法区:方法区只是一个规范,java8用元空间实现。线程共享,存在少量GC,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据
- PC寄存器(程序计数器):存放下一条指令的地址,指向下一条要执行的命令。线程私有,内存很小几乎不存在GC
- Java栈:用于存储局部变量表、操作数栈、动态链接、方法出口等信息;线程私有,不存在GC问题
- 本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;
- 堆:Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存
8. 栈
- 栈管运行,堆管存储。栈中的数据都是以栈帧的格式存在,在JVM栈里方法叫栈帧,在JVM外叫方法。每个方法执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程
- SOF: 如果线程请求的栈深度大于虚拟机栈允许的最大深度,将抛出StackOverflowError异常。比如递归调用没有递归终止条件。
9. 堆(java7之前逻辑上三部分)
- 新生代(PSYoungGen):
伊甸区(Eden Space): new对象存在该区,GC(轻GC)在该区产生;
幸存者0区(也叫S0 或 from区):每次GC也会进行复制清空互换的过程
幸存者1区(也叫S1 或 to区):与S0区机制相同 - 老年代(养老区ParOldGen):当对象超过内存阈值后,开启Full GC(重GC),多次FGC后无法再腾出空间,将抛出OOM堆内存溢出错误,每次Full GC经常伴随一次轻GC
- 元空间Metaspace(Java7叫永久代或持久代):
用于存放JDK自身携带的Class,Interface的元数据(结构信息),也就是运行环境必须的类信息。被装载进此区域的数据不会被GC,关闭JVM才会释放此区域所占用的内存
对应方法区,虽然逻辑上JVM规范将方法区描述为堆的一个逻辑部分,但物理上不包含在堆里。元空间是方法区的一个实现,即方法区是接口,元空间是实现。
永久代和元空间最大区别:永久代使用JVM的堆内存,而Java8之后的元空间存在于本机物理内存,字符串常量池也在元空间。
10. GC(轻GC)
普通GC:复制-> 清空-> 互换
- 复制:第一次GC将Eden区存活的对象复制到S0区后清空Eden区,再次GC后会扫描Eden和S0区进行GC,回收存活的对象并复制到S1区(如果有对象的年龄到达老年区的标准则复制到老年区),同时把这些对象年龄+1,以后每次都需要扫描Eden区和S0区,进行MinorGC过程
- 清空:清空Eden和S0区的对象,也即复制之后有交换,谁空谁是S1区
- 交换:To区和From区互换,部分对象存活到15时(默认15)后, 存入老年代
11. 说一下 JVM 有哪些垃圾回收算法?
- 复制算法(Copying):复制时内存分为两块,一块移动到to区,另一块被清除,主要用于年轻代的轻GC中,优点是效率高,不会产生内存碎片, 缺点是耗费空间
- 标记清除:先扫描并标记要回收的对象,接着再扫描一次,然后统一回收这些标记的对象,优点是不需要额外空间,缺点是两次扫描耗时严重,会产生内存碎片
- 标记整理,标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存,优点是标记清除的优点+无内存碎片 缺点:耗时最长
- 分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理或标记清除算法。
12. 说一下 JVM 有哪些垃圾回收器?
- Serial收集器(单线程、 复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;
- ParNew收集器 (Serial+多线程): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;只在新生代并行
- Parallel Scavenge收集器 (多线程复制算法、高效): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;新生代和老年代都并行
- Serial Old收集器 (单线程标记整理算法): 老年代单线程收集器,Serial收集器的老年代版本;
- Parallel Old收集器 (多线程标记整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
- CMS(Concurrent Mark Sweep)收集器(多线程标记清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。
- G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。
13.怎样判断对象是否可以被回收
- 引用计数器:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;
- 可达性分析:从 GC Roots 开始向下搜索。当一个对象到 GC Roots 没有任何引用链相连时,说明该对象不可达,则此对象是可以被回收的。
14. 哪些对象可以作为GC Roots的对象:
- 虚拟机栈中引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈JNI(Native方法)引用的对象
15. Java 中都有哪些引用类型?
- 强引用:发生 GC 的时候不会被回收。强引用是造成Java内存泄露的主要原因之一
- 软引用:当系统内存充足时,软引用不会回收;当系统内存不足时,软引用会被回收
- 弱引用:当JVM进行GC时,无论内存是否充足,被弱引用关联的对象都会被回收
- 虚引用:如果一个对象仅持有虚引用,那么它和没有任何引用一样。虚引用的用途是在 GC时返回一个通知
16.OOM
- StackOverflowError:
栈溢出异常,通常发生在递归调用且未添加终止条件时 - OOM:java heap space
当new大对象或者不断new新对象,导致new出来的内存超过了heap的大小,会导致OOM: java heap space异常 - OOM:GC overhead limit exceeded:
超过98%的时间用来做GC并且回收了不到2%的堆内存,连续多次GC仍然出现这种情况时,才会抛出该异常。 - OOM:direct buffer memory:
不断分配本地内存,堆内存很少使用,那么 JVM 就不需要执行 GC,DirectByteBuffer 对象就不会被回收,这时虽然堆内存充足,但本地内存可能已经不够用了,就会出现 OOM,本地直接内存溢出。 - OOM:unable to create new native thread:
应用创建了太多的线程,超过了系统承载极限 - OOM:metaspace:
元空间使用本地内存,但是本地内存也有打满的时候,所以也会有异常。
17. 其他
java基础常见面试题
java集合常见面试题目
JUC并发编程面试题汇总
MySQL数据库常见面试题目
Spring常见面试题总结