Appearance
- 1、JVM是什么,讲一下运行流程
- 2、什么是程序计数器
- 3、介绍一下堆
- 4、什么是虚拟机栈
- 5、讲一下方法区
- 6、听过直接内存吗
- 7、什么是类加载器,什么是双亲委派机制
- 8、说一下类加载的执行过程
- 9、对象什么时候可以被垃圾器回收
- 10、JVM 垃圾回收算法有哪些
- 11、说一下 JVM 的分代回收
- 12、说一下 JVM 有哪些垃圾回收器
- 13、详细聊一下 G1 垃圾回收器
- 14、强引用、弱引用、软引用、虚引用的区别
- 15、JVM调优参数可以在哪里设置参数值
- 16、用的JVM调优的参数都有哪些
- 17、说一下 JVM 调优的工具
- 18、JVM 内存泄漏的排查思路
- 19、CPU 飙高的排查方案及其思路
JVM 组成
JVM 是 什么
Java Virtul Machine:Java 程序的运行环境

JVM 组成

程序计数器
程序计数器:用于记录正在执行的字节码指令的地址。
什么是程序计数器?
- 线程私有的,每个线程一份,内部保存的字节码的行号。用于记录正在执行的字节码指令的地址。
Java 堆

你能给我详细的介绍Java堆吗?
线程共享的区域:主要用来保存对象实例,数组等,内存不够则抛出 OutOfMemoryError异常。
组成:年轻代+老年代
- 年轻代被划分为三部分,Eden区 和 两个大小严格相同的 Survivor 区
- 老年代主要保存生命周期长的对象,一般是一些老的对象
Jdk1.7和1.8的区别
- 1.7中有有一个永久代,存储的是类信息、静态变量、常量、编译后的代码
- 1.8移除了永久代,把数据存储到了本地内存的元空间中,防止内存溢出
讲一下老年代和年轻代 🚩
- 堆中区域(用于垃圾回收,垃圾回收主要针对的是堆这个区域)【基于对象生命周期划分】
- 年轻代
- Eden空间
- 大部分新生成的对象都会首先在Eden空间分配。当Eden空间填满时,会触发一次Minor GC(也称为Young GC),清理掉不再被引用的对象。
- 两个幸存者空间(Survivor Spaces):分别称为S0(From Space)和S1(To Space)
- 在Minor GC过程中,从Eden空间和一个Survivor空间(例如S0)幸存下来的对象,如果仍然被引用,会被移动到另一个Survivor空间(例如S1)。在每次Minor GC之后,这两个Survivor空间的角色会互换。
- Eden空间
- 老年代
- 老年代用于存放长时间存活的对象。一般来说,当对象在年轻代经历了多次GC还存活时,它们会被晋升到老年代。老年代的空间比年轻代大得多,当它被填满时,会触发Major GC(也称为Full GC),这个过程比Minor GC要慢得多。
- 年轻代
- 垃圾收集过程
- Minor GC:清理年轻代。因为年轻代中的对象大多是短暂的,所以Minor GC相对频繁,但速度快。
- Major GC / Full GC:清理整个堆,包括年轻代和老年代。这个过程比Minor GC慢,因为它需要检查堆中的所有对象。
GC策略和算法
不同的垃圾收集器有不同的实现方式和优化策略。例如,串行收集器(Serial GC)、并行收集器(Parallel GC)、并发标记清除(CMS GC)和G1收集器(G1 GC)等,它们在处理年轻代和老年代的垃圾时使用不同的算法和策略。
虚拟机栈
什么是虚拟机栈
- 每个线程运行时所需要的内存,成为虚拟机栈
- 每个栈由多个栈帧(frame)组成,对应着每次方法调用时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
垃圾回收是否涉及栈内存
- 垃圾回收主要指就是堆内存,当栈帧弹栈以后,内存就会释放
栈内存分配越大越好吗
- 未必,默认的栈内存通常为 1024 K, 栈帧过大会导致线程数变少
方法内的局部变量是否线程安全
- 如果方法内局部变量没有逃离方法的作用范围,他是线程安全的
- 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
什么情况下会导致栈内存溢出
- 1、栈帧过多导致内存溢出,典型问题:递归调用
- 2、栈帧过大导致栈内存溢出

堆栈的区别 是 什么
- 栈内存一般会用来存储局部变量和方法调用,但堆内存是用来存储va对象和数组的的。
- 堆会GC垃圾回收,而栈不会。
- 栈内存是线程私有的,而堆内存是线程共有的。
- 两者异常错误不同,但如果栈内存或者堆内存不足都会抛出异常
- 栈空间不足:java.lang.StackOverFlowError。
- 堆空间不足:java.lang.OutOfMemoryError。
方法区

运行时常量池
常量池

运行时常量池

总结(方法区)

直接内存
先看一下普通 IO 和 NIO 的数据拷贝图示区别
普通 IO
使用常规 IO 进行数据拷贝的时候,需要使用到 Java 缓冲区 和 操作系统的 系统缓存区的两个缓存区的数据交互(Java 不能直接访问系统缓存区,两个缓存区之间有一个数据复制耗时)

NIO
提供了一个直接内存(Java 能直接访问)

总结

在1.8 中,运行时常量池是放置在哪个区域?
在Java 8中,字符串常量池已经被移动到了堆内存中,以提高性能和减少内存溢出的风险。
而运行时常量池除了字符串常量外的部分,仍然存储在方法区中,但方法区的实现已从永久代变更为元空间。

类加载器
类加载器

双亲委派机制

类装载的过程
总体的一个过程:加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载
加载

验证

准备

解析:把类中的符号引用转换为直接引用

初始化:对类的静态变量,静态代码块执行初始化操作。
初始化的时候关于子类和父类以及静态变量的加载顺序可能要注意一下

使用: JVM 开始从入口方法开始执行用户的程序代码。

卸载:当用户程序代码执行完毕后,JVM便开始销毁创建的 Class 对象。
总结

垃圾回收
垃圾回收机制总结
Java Virtual Machine

对象什么时候可以被垃圾器回收 🚩
判断对象是垃圾,则进行回收
回收确认垃圾的方式有两种:
- 引用计数法
- 可达性分析算法

引用计数法
引用计数法:一个对象被引用了一次,在当前的对象头上递增一次引用次数,如果这个对象的引用次数为0,代表这个对象可回收
- 问题:当对象间出现了循环引用,则引用技术法会失效(两个的引用次数都不是0,不使用的时候引用次数互相为 1)
可达性分析算法

哪些对象可以作为 GC Root ?
- 虚拟机栈中(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中 JNI 引用的对象

总结

垃圾回收算法有哪些 🚩
- 标记清除算法
- 复制算法
- 标识整理算法
标记清除算法
(使用较少)

标记整理算法

复制算法

总结:
- 标记清除算法(使用较少,会产生内存碎片)
- 标识整理算法(老年代用这种方式回收多一些)
- 复制算法(新生代用这种方式回收多一些)

说一下 JVM 的分代回收
工作机制
- 1、新创建的对象,都会先分配到 eden 区
- 2、当伊甸园内存不足,标记伊甸园与 from(现阶段没有) 的存活对象
- 3、将存活对象采用复制算法复制到to中,复制完毕后,伊甸园和 from内存都得到释放
- 4、经过一段时间后伊甸园的内存又出现不足,标记eden区域to区存活的对象,将存活的对象复制到from区
- 5、当幸存区对象熬过几次回收(最多15次),晋升到老年代(幸存区内存不足或大对象会导致提前晋升)

MinorGC、Mixed GC、FullGC 的区别是什么?

总结

讲一下常用的垃圾回收器
在 jvm 中,实现了多种垃圾收集器,包括:
- 串行垃圾收集器
- 并行垃圾收集器
- CMS(并发)垃圾收集器
- G1垃圾收集器
串行垃圾收集器

并行垃圾收集器

CMS(并发)垃圾收集器

总结

讲一下 G1 垃圾收集器

Young Collection(年轻代垃圾回收)
- 1、初始时,所有区域都处于空闲状态
- 2、创建了一些对象,挑出一些空闲区域作为伊甸园区存储这些对象
- 3、当伊甸园需要垃圾回收时,挑出一个空闲区域作为幸存区,用复制算法复制存活对象,需要暂停用户线程
- 5、随着时间流逝,伊甸园的内存又有不足
- 6、将伊甸园以及之前幸存区中的存活对象,采用复制算法,复制到新的幸存区,其中较老对象晋升至老年代
Young Collection+Concurrent Mark(年轻代垃圾回收+并发标记)
当老年代占用内存超过阈值(默认是45%)后,触发并发标记,这时无需暂停用户线程
并发标记之后,会有重新标记阶段解决漏标问题,此时需要暂停用户线程。
这些都完成后就知道了老年代有哪些存活对象,随后进入混合收集阶段。此时不会对所有老年代区域进行回收,而是根据暂停时间目标优先回收价值高(存活对象少)的区域(这也是Gabage First名称的由来)。
Mixed Collection(混合垃圾回收)
复制完成,内存得到释放。进入下一轮的新生代回收、并发标记、混合收集
其中大对象如果一个固定区域不够的话,会分配一个连续的空间给他
总结

强引用、软引用、弱引用和虚引用的区别?
- 强引用
- 软引用

弱引用

虚引用

总结

JVM 实践
JVM 调优参数可以在哪里设置参数值
一般是分为两种情况,一种是 tomcat 的 war 包,还有一种是 SpringBoot 打包的 Jar 包
War 包

Jar 包

总结:
JVM 调优的参数可以在那里设置参数值
- war 包部署在 tomcat 中设置
- 修改 TOMCAT_HOME/bin/catalina.sh 文件
- jar 包部署在启动参数设置
- java -Xms512m -Xmx1024m -jar xxxx.jar
JVM 调优参数有哪些
官网参考: https://www.oracle.com/java/technologies/javase/vmoptions-jsp.html
一般不会用到很多
对于 JVM调优,主要就是调整年轻代、老年代、元空间的内存空间大小及使用的垃圾回收器类型。
- 设置堆空间大小
- -Xms 设置堆的初始化大小
- -Xmx:设置堆的最大大小
- 虚拟机栈的设置
- 年轻代中Eden区和两个Survivor区的大小比例
- 年轻代晋升老年代阈值
- 设置垃圾回收收集器
设置堆空间大小

虚拟机栈的设置

年轻代中 Eden 区和 两个 Survivor 区的大小比例
- 默认 Eden区:From:To = 8:1:1

设置垃圾回收收集器

总结
用的 JVM 调优的参数都有哪些?
- 设置堆空间大小
- 虚拟机栈的设置
- 年轻代中 Eden 区和两个 Survivor 区的大小比例
- 年轻代晋升老年代阈值
- 设置垃圾回收收集器
说一下 JVM 调优的工具

具体待实践使用一下

- jmap

- jstat

- 调优工具:jconsole

- 调优工具 VisualVM

查看运行中的 dump
Dump文件是进程的内存镜像。可以把程序的执行状态通过调试器保存到dump文件中
https://zhuanlan.zhihu.com/p/372721732
可以使用 JProfiler 工具来查看
或者

总结:略
Java 内存泄漏的排查思路
Java 的内存溢出有三个方向:栈、元空间和堆,栈一般是递归调用造成,堆的情况是平时最多的时候,一般考察也是问堆内存溢出怎么排查。

排查的话,提供一种方法:生成 dump 文件,然后分析这个 dump 文件

生成 dump 文件:
- 一般是设置 JVM 参数,有时候程序是直接闪退挂了,不太可能让你运行的时候使用 jmap 命令(使用 vm 参数获取 dump 文件)

分析 dump 文件
- 1、打开 VisualVM
- 2、选择:文件 → 装入 → (选择文件) → 打开
- 3、概要 → 堆转储
- 4、通过查看堆信息的情况,可以大概定位内存谥出是哪行代码出了问题

总结:

CPU 飙高排查思路

分析一下这个过程
- 1、使用
top命令查看CPU使用情况,确认问题的存在。 - 2、在
top命令输出中,找到CPU使用率最高的Java进程,记下它的PID。比如,Java进程的PID为12345。 - 3、使用
jstack命令导出Java进程的线程堆栈信息到文件。 - 4、找出CPU使用率最高的线程
查看进程中的线程信息
首先,使用ps、top、或jps(Java Virtual Machine Process Status Tool)命令找到Java进程的PID(Process ID)
使用top命令以线程模式运行,指定Java进程的PID:
top -H -p 12345这将显示进程12345中所有线程的CPU使用情况(切换到top命令的线程视图,查看高CPU使用的线程。)
将线程ID转换为16进制
Linux系统中top命令显示的是线程的PID(实际上是轻量级进程ID,LWP ID),需要将其转换为16进制,以匹配jstack输出中的线程ID。
假设线程ID是6789,可以使用printf命令进行转换:
printf "%x\n" 6789输出将是线程ID的16进制表示。
获取线程堆栈
使用jstack命令导出Java进程的线程堆栈信息:
jstack 12345 > threadDump.txt这将把进程12345的所有线程堆栈信息输出到threadDump.txt文件中。
在threadDump.txt文件中,查找转换为16进制的线程ID对应的堆栈信息。这通常以nid=0x开头,后跟线程的16进制ID。
定位到高CPU使用线程的堆栈信息后,分析它正在执行的代码。查找该堆栈中的Java方法和类,特别关注自己的应用程序代码。
