JVM常见

84 阅读5分钟

1. JVM 内存模型

截屏2023-06-10 12.03.29.png 1. 线程共享区域: 方法区、堆 (伴随虚拟机的开启而创建,关闭而销毁)
2. 线程私有区域: 虚拟机栈、本地方法栈、程序计数器 (依赖用户的线程而创建、销毁而销毁)

  • 方法区: 用来存储类、运行常量池、静态的变量和编译后的代码等数据
  • 堆: 创建的数组和对象都放入到java堆中,当然也是垃圾收集器(GC)重要回收的地方。VM 主要采用分代收集算法,主要产生在新生代和老年代
  • 虚拟机栈: 当我们执行这个方法时会创建一个栈帧用来存储局部变量表、操作数帧、动态链接、方法出口信息;每一个方法从调用直到执行完成的过程,就对应着一个栈帧入栈到出栈的过程。
  • 本地方法栈: 本地方法栈是用来区别虚拟机调用外部的执行方法,而本地方法栈则为Native修饰,C语言栈
  • 程序计数器: 用户每次访问都会独立开启一个线程,程序计数器会记录每次当前执行代码的行号指示器

2. JVM 运行时内存分配

主要在堆上进行运行时内存分配。
堆分为:新生代(Enden、SurvivorFrom、SurvivorTo)、老年代、元空间
1. 新生代: 用来存放 new 的对象,由于频繁创建对象,所以会触发 MinorGc 垃圾回收
2. 老年代: 用来存放应用程序中生命周期长的内存对象

  • 老年的对象比较稳定,所以 MajorGC 不会频繁执行,在进行 MajorGC 前一般都先进行类一次 MinorGC。使得有新生代的对象晋升到老年代。老年代空间不够才会进行 MajorGC。
  • MajorGC 采用类标记清除法:首先扫描一次所有的老年代,标记出存活的对象,然后回收没有标记的对象。

3. 元空间: 元空间不在虚拟机中,而使用本地内存。

3. 如何确定当前对象是垃圾

1. 引用计数法: 引用计数为0,表面不太可能再用到,表明可回收。相互引用的时候,内存不能回收。
2. 可达性分析: 为解决计数法的循环引用问题,Java使用了可达性分析。通过一系列的 GC roots 对象作为起点搜索。如果在 GC roots 和一个对象之间没有可达路径,则称该对象是不可达的。(不可达不意味着可回收,要经过两次标记过程后,若仍然是可回收,则回收)。

4. GC 算法有哪些

1. 标记清除法: 标注、清除。标注阶段标记出所有需要回收的对象,清除阶段回收被标记对象所占用的空间。内存碎片化严重,效率低。
2. 复制算法: 按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后,将尚存活的对象复制到另外一块上,把已使用的内存清除。简单,但可用内存压缩到了原本一半,当存活对象多的时候,效率低。
3. 标记整理法: 标记后不是清理对象,而是将存活对象移向内存的一端,然后清除边界外的对象。
4. 分代收集算法: 新生代复制,老年代标记清除或标记整理。

5. JVM 中类的加载机制

截屏2023-06-10 13.17.18.png

  • 加载、验证、准备、解析、初始化 (家宴准接触)

1. 加载: 在内存中生成一个代表这个类的 java.lang.class 对象,作为方法区这个类的各种数据的入口
2. 验证: 确保这个类符合要求
3. 准备: 加载一些静态变量和常量
4. 解析: 把编译 class 文件进行解析
5. 初始化: 初始化默认对象属性信息

6. JVM 参数调优

  • JVM 调优涉及到两个重要概念:吞吐量和响应时间。JVM 调优主要是针对他们进行调整优化,达到一个理想的目标,根据业务确定目标是吞吐量优先还是响应时间有限。
  • 吞吐量: 用户代码执行时间/(用户代码执行时间 + GC执行时间)
  • 响应时间:整个接口的响应时间(用户代码执行时间 + GC执行时间)

7. 双亲委派机制是什么?为什么要使用?

  • 双亲委派模型: 如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个记载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时,子加载器才会尝试自己去加载。
  • 为什么需要双亲委派模型: 如果没有双亲委派,那么用户可以自己定义一个 java.lang.Object 的同名类,并把他放到 ClassPath 中,那么类之间的比较结果及类的唯一性将无法保证。因此,使用双亲委派模型,防止内存中出现多份同样的字节码。

8. 未完待续

来一份常见 JVM 面试题+“答案”! - 知乎 (zhihu.com)