- JVM包含哪几部分?
- ClassLoader 类加载器
- 负责加载字节码文件即 class 文件
- Runtime Data Area 运行时数据区,内存分区
- 存放数据,分为五部分
- Stack 虚拟机栈
- Heap 堆
- Method Area 方法区
- PC Register 程序计数器
- Native Method Stack 本地方法栈
- 存放数据,分为五部分
- Execution Engine 执行引擎
- 将 JVM 指令集翻译为操作系统指令集。
- Native Interface 本地库接口
- 负责调用本地接口
- ClassLoader 类加载器
- JVM是如何运行的?
- JVM的装入环境和配置
- 装载JVM
- 初始化JVM,获得本地调用接口
- 运行Java程序
- Java程序是怎么运行的?
- Java 源代码文件经过 Java 编译器编译成字节码文件后,通过类加载器加载到内存中,然后到 Java 虚拟机中解释执行,最后通过操作系统操作 CPU 执行获取结果。
- 说一说Java的内存分布情况
线程共享的数据区:方法区、堆- 程序计数器
- 当前线程所执行的字节码的行号指示器
- Java虚拟机栈
- 描述的是Java方法执行的线程内存模型
- 每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧
- 本地方法栈
- 是为虚拟机使用到的本地(Native)方法服务。
- Java堆
- 几乎所有的对象实例以及数组都应当在堆上分配
- 方法区
- 用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据
- 运行时常量池
- 是方法区的一部分
- 直接内存
- 程序计数器
- 类存放在哪里?
- 方法区
- 局部变量存放在哪里?
- 虚拟机栈
- 介绍一下Java代码的编译过程
- 准备过程
- 初始化插入式注解处理器。
- 解析与填充符号表
- 将源代码的字符流转变为标记集合,构造出抽象语法树。
- 填充符号表,产生符号地址和符号信息。
- 插入式注解处理器的注解处理过程
- initPorcessAnnotations()
- 分析与字节码生成过程
- 标注检查
- 数据流及控制流分析
- 解语法糖
- 字节码生成
- 准备过程
- 介绍一下类加载的过程
- 加载
- 通过全限定名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
- 验证
- 文件格式验证
- 验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。
- 元数据验证
- 对字节码描述的信息进行语义分析验证
- 字节码验证
- 确定程序语义是合法的、符合逻辑的。
- 符号引用验证
- 对类自身以外的各类信息进行匹配性校验
- 文件格式验证
- 准备
- 正式为类中定义的变量分配内存并设置类变量初始值的阶段
- 解析
- Java虚拟机将常量池内的符号引用替换为直接引用的过程
- 初始化
- 执行类构造器
<clinit>()
(Javac)编译器的自动生成物方法的过程
- 执行类构造器
- 加载
- 介绍一下对象的实例化过程
具有父类的子类的实例化顺序如下
父类 | 子类 |
---|---|
1 静态变量 | 3 静态变量 |
2 静态代码块 | 4 静态代码块 |
5 变量 | 8 变量 |
6 代码块 | 9 代码块 |
7 构造器 | 10 构造器 |
- 元空间是在栈内还是栈外
- 栈外,元空间占用的是本地内存
- 谈谈JVM的类加载器,以及双亲委派模型
- 把类的加载阶段(通过全限定名获取二进制字节流)放到JVM外部实现的动作代码
- 双亲委派模型
- 工作过程
- 如果一个类加载器收到了类加载的请求首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成;只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
- 好处
- Java中的类随着它的类加载器一起具备了一种带有优先级的层次关系
- 工作过程
- 双亲委派机制会被破坏吗?
- 双亲委派模型主要出现过3次较大规模“被破坏”的情况。
- 介绍一下Java的垃圾回收机制
- 哪些内存需要回收
- 处于运行期间程序创建的对象
- 怎么定义垃圾
- 引用计数算法
- 在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。
- 缺点
- 有很多例外情况要考虑要配合大量额外处理才能保证正确地工作,比如对象之间相互循环引用的问题。
- 在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。
- 可达性分析算法
- 某个对象到GC Roots(起始节点集)间没有任何引用链相连明此对象是不可能再被使用的。
- 固定作为GC Roots的对象:
- 在虚拟机栈(栈帧中的本地变量表)中引用的对象
- 在方法区中类静态属性引用的对象
- 在方法区中常量引用的对象
- Native方法引用的对象
- Java虚拟机内部的引用
- 被同步锁持有的对象。
- 映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
- 固定作为GC Roots的对象:
- 某个对象到GC Roots(起始节点集)间没有任何引用链相连明此对象是不可能再被使用的。
- 回收方法区
- 方法区的垃圾收集主要回收两部分内容:废弃的常量和不再使用的类型
- 引用计数算法
- 怎么回收垃圾
- 分代收集理论
- 收集器将Java堆划分出不同的区域,将回收对象依据其年龄分配到不同的区域之中存储
- 标记-清除算法
- 首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,
- 缺点:
- 执行效率不稳定
- 内存空间的碎片化
- 标记-复制算法
- 将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉
- 缺点:
- 浪费内存空间
- 对象存活率高时要进行较多复制操作,效率低下
- 标记-整理算法
- 首先标记出所有需要回收的对象,让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存
- 分代收集理论
- 哪些内存需要回收
- Full GC会导致什么
- Full GC会“Stop The World”全程暂停用户的应用程序。
- JVM中一次完整的GC流程是怎样的?(面试真题)
- 新创建的对象一般会被分配在新生代中,Eden 区全部挤满Minor GC 就触发
- 根据老年代剩余空间与新生代中对象的大小
- 老年代剩余多—>直接Minor GC
- 老年代剩余少 —> 检查老年代剩余空间与历次Minor GC生育对象的大小
- 老年代剩余大于历次Minor GC剩余对象 —>进行Minor GC
- 老年代剩余小于历次Minor GC剩余对象 —>进行Full GC
- Minor GC之后有三种情况
- Minor GC之后的对象足够放到Survivor区,GC结束
- 不够放到Survivor区,接着放入老年代,老年代放得下,GC结束
- 老年代放不下 Full GC
- JVM什么时候触发GC,如何减少FullGC的次数?
- 当 Eden 区的空间耗尽时
- 减少策略
- 增加方法区、老年代空间
- 减少新生代
- 使用标记-整理算法
- 排查无用对象
- 对象如何晋升到老年代
- JVM给每个对象定义了一个年龄计数器,对象每熬过一次Minor GC,年龄+1,年龄达到一定程度(默认15)晋升到老年代
- 为什么老年代不能使用标记-复制
- 老年代的对象都是难以消亡的,复制会操作会很多,效率很低
- 新生代为什么要分Survivor和Eden,它们的比例是多少
- 实际上98%的对象熬不过第一轮收集,所以标记-复制算法中不需要按1:1的比例划分内存
- Apple式回收把新生代分为较大的Eden区和两块较小的Survivor,每次分配只在Eden和一块Survivor,另一块用来复制
- 默认比例是8:1
- 为什么设置两个Survivor
- 解决内存碎片化
- 保证永远有一个Survivor区是空的,另一个非空Survivor是无碎片的
- 再细分容易导致Survivor区被填满
- 为什么新生代和老年代要采用不同的回收算法
- 兼顾垃圾回收的时间开销和内存空间的有效利用
请介绍G1垃圾收集器
- 主要面向服务端,自JDK9,开创了收集器面向局部收集的设计思路和基于Region的内存布局形式
- 把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region可以根据需要扮演Eden、Survivor、老年代
- Region的特殊区域Humongous用来存储大对象(超过了一个Region一半)
- 好处
- 每次收集到的内存空间都是Region的整数倍,避免了全区域的垃圾收集
- 根据用户设定的收集停顿时间,优先处理回收价值收益最大的Region(Garbage First的由来)
请介绍CMS垃圾收集器
- 一种以获取最短回收停顿时间为目标的收集器,基于标记-清除算法
- 运作过程
- 初始标记 (需要STOP THE WORLD)
- 并发标记 (需要STOP THE WORLD)
- 重新标记
- 并发清除
- 缺点:
- 会因为占用了一部分线程导致应用程序变慢,降低总吞吐量
- 有可能出现Con-current Mode Failure导致Full Gc
- 收集结束时会产生大量碎片空间
- 内存泄漏和内存溢出有什么区别
- 内存泄漏 memory leak
- 指程序运行过程中分配的内存用完后没有被GC回收,始终在占用内存,既不能使用又被分配
- 内存溢出 out of memory
- 申请的内存大于能够提升的内存导致无法申请到足够的内存
- 内存泄漏 memory leak
- 什么时候内存泄漏?怎么解决?
内存泄漏的本质:长生命周期的对象持有短生命周期对象的引用- 尽早释放无用对象的引用
- 避免在循环中创建对象
- 使用字符串时尽量使用StringBuildrer
- 少使用静态变量
- 什么是内存溢出,怎么解决?
- 内存溢出的原因
- 内存加载数据量过大,如一次从数据库中取出过多数据
- 存在死循环或循环产生太多重复对象
- 启动参数内存值设定太小
- 集合类中对象的引用使用后为清空,是的JVM不能回收
- 解决
- 修改JVM参数直接增加内存
- 检查异常
- 使用内存查看工具动态查看内存使用情况
- 排查代码中的循环
- 内存溢出的原因
- 哪些区域会OOM,怎么触发OOM
除了程序计数器外的虚拟机内存都有可能发生- Java堆溢出
- 对象总容量触及到最大堆容量时
- 虚拟机栈和本地方法溢出
- 无法申请到足够的内存时
- 方法区和运行时常量池溢出
- 运行时生成大量动态类的场景
- 本地内存直接溢出
- Java堆溢出