1. 导读部分
写在开始 : 本文是学习 [黑马程序员] JVM 面试专题 的学后记录, 仅仅用作个人复习准备面试用。
附学习地址 : https://www.bilibili.com/video/BV1yT411H7YK?p=116&vd_source=08ac522c6603c56e243d5e129a309a60
2. JVM 组成
2.1 程序计数器
个人回答 : 程序计数器是运行时数据区的一部分,用来指向执行的指令的位置,唯一不会发生OOM的地方;
:::info
程序计数器(PC Register) : 线程私有,每个线程一份,内部保存字节码的行号。用于记录正在执行的字节码指令的地址。
:::
2.2 堆
个人回答 : 堆一般是运行时数据区空间最大的地方,存放的对象和数组;堆是线程共享的,也是垃圾回收的主要对象;堆分为新生代和老年代,新生代又分为eden区和s from 和 s to区,一般比例是8:1:1;
:::info
线程共享区域: 主要用来保存对象实例,数组等,当堆中没有内存空间可分配,无法再扩展时,OOM异常
组成: 年轻代 + 老年代
- 年轻代分为三部分,Eden区和两个大小严格相同的 Survivor 区
- 老年代主要保存生命周期长的对象,一般是一些老对象
JDK7和8的区别:
- 7 有一个永久代,存储类信息、静态变量、常量、编译后代码;
- 8 移除了永久代,数据存储到本地内存的元空间,防止内存溢出;
2.3. 方法区
个人回答 : 方法区也是线程共享的,7及之前是永久代,8及之后是元空间,
- 方法区 Method Area 是各个线程共享的内存区域
- 主要存储类的信息,运行时常量池
- 虚拟机启动时候创建,关闭虚拟机时候释放
- 如果方法区域中内存无法满足分配请求,OOM:Metaspace
:::info
常量池 : 可看作一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型和字面量等信息; 当类被加载,常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址;
:::
2.4. 直接内存
个人回答 : 没听过
:::info
直接内存 :
- 不属于JVM 的内存结构,不由 JVM 进行管理。是虚拟机的系统内存,
- 常见于 NIO 操作时,用于数据缓冲区,分配回收成本高,但读写性能高;不受JVM内存回收管理 :::
2.5 虚拟机栈
个人回答 ; 虚拟机栈也是运行时数据区一部分,每个线程私有,生命周期随着线程的产生和销毁而变化,当内存空间不够会抛出 stackoverflow 异常,也可能有情况抛出 OOM 异常,存储的是方法出口,局部变量等,栈内存按照先进后出的顺序执行;
:::info
Java Virtual machine Stacks ( java 虚拟机栈 )
- 每个线程运行时所需要的内存,称为虚拟机栈,先进后出;
- 每个栈由多个栈帧 (frame)组成,对应每次方法调用时所占用内存;
- 每个线程只有一个活动栈帧,对应当前正在执行的那个方法;
- 可存放 参数 | 局部变量 | 返回地址等
:::
2.6 垃圾回收是否涉及栈内存
个人回答 : 垃圾回收主要针对堆内存,栈是方法执行完毕弹栈释放,内存自动释放
2.7 栈内存分配越大越好吗
个人回答 : 不一定
:::info
未必,默认栈内存一般是 1024 K;
栈帧过大导致线程数变少,比如,机器总内存512M,能活动线程数则是512个,如果栈内存改为2048K,那么可以活动的栈帧就会减半;
:::
2.8 方法内的局部变量是否线程安全
个人回答 : 不一定
:::info
方法局部变量没有逃离方法作用范围,则安全; 如果局部变量引用了对象,并逃离方法作用范围,则需要考虑线程安全;
:::
2.9 栈内存溢出的情况
个人回答 ; 栈帧过大
// 1 栈帧过多导致栈内存溢出,典型问题 : 递归调用
// 2 栈帧过大导致栈内存溢出
public static void m1(){
m1(); // 抛出 java.lang.StackOverflowError异常
}
栈堆的区别是
个人回答见下表:
区别 | 栈 | 堆 |
---|---|---|
大小 | 一般是1024K | 随对象的大小而定,一般大于栈 |
私有 | 每个线程私有 | 共享区域 |
生命周期 | 随方法调用一样 | 对象创建存在,垃圾回收可能消失 |
异常类型 | 两种 SOF OOM | 只有 OOM |
存啥 | 存局部变量和返回地址等 | 对象和数组等 |
垃圾回收 | 无 | 有 |
物理地址 | 连续,性能快 | 不连续,性能慢 |
3. 类加载器
3.1 类加载器是什么?有哪些
个人回答 : 类加载器就是对类的信息进行加载,有引导类加载器,扩展类加载器,系统应用类加载器以及自定义类加载器等几种
:::info
类加载器 只会运行二进制文件,类加载器作用就是 将字节码文件加载到JVM中,从而让 Java 程序能够启动起来
BootStrap ClassLoader 加载JAVA_HOME/jre/lib 目录下的库
Ext /ext目录
App 加载classPath 下的类
CustomizeClassLoader : 自定义类继承 ClassLoader ,实现自定义类加载规则
:::
3.2 双亲委派模型
个人回答 : 类的加载优先找父类加载器进行,首先是引导类,其次是扩展类,然后是系统应用类加载器,最后是自定义的,为了保证安全,核心类库不被篡改;
:::info
加载某一个类,先委托上一级加载器进行加载,如果上级加载器也有上级,则继续向上委托,如果该类委托上级没有被加载,子加载器尝试加载该类;
:::
3.3 JVM为什么用双亲委派机制
:::info
为了安全,保证类库 API 不会被修改 可以避免某一个类被从重复加载,当父类已加载后则无需重复加载,保证唯一性;
:::
3.4 类装载执行过程
个人回答 : 准备 -> 连接 -> 初始化 太长记不清楚了,其中连接分为验证解析引用
:::info
类从加载开始,知道卸载为止的生命周期包括 加载 验证 准备 解析 初始化 使用 卸载
①加载 :
通过类全名,获取类的二进制数据流;解析为方法区的数据结构(Java类模型);在堆里创建类的实例,表示该类型。作为方法区这个类的各种数据的访问入口;
②连接 :
验证,是否符合规范,安全性检查,文件格式,元数据,字节码都是格式检查,符号引用验证;准备,为类变量分配内存并设置类变量初始值,静态变量(分配空间在准备阶段完成设默认值)和静态变量final引用类型,赋值在初始化阶段完成,静态变量是final的基本类型,以及字符串常量,值已确定,赋值在准备阶段完成;解析,类的符号引用转为直接引用,比如:方法中调其他方法,方法名可理解为符号引用,直接引用是使用指针直接指向方法;
③初始化 : 对类的静态变量,静态代码块进行初始化操作;优先初始化父类,自上而下顺序
④使用 : JVM
从入口方法开始执行用户的程序代码(带哦用静态类成员变量,new 创建对象实例)
⑤卸载
:::
小结 :
- 加载:查找和导入字节码文件
- 验证:保证类加载的准确性
- 准备:分配内存并设初始值
- 解析: 类中的符号引用转为直接引用
- 初始化:对静态变量|代码块初始化
- 使用 : JVM 从入口方法开始执行用户程序代码
- 卸载 : 执行完毕,JVM 开始销毁 Class 对象
4. 垃圾回收
强引用,软引用,弱引用,虚引用
个人回答 : 强不会被回收,软,当内存不够时会回收,弱,内存无论是否够都回收,虚
:::info
强引用: GC Roots能找到,就不会被回收
软 : 配合 SoftReference使用,当垃圾多次回收,内存依然不够时候回收
弱 : 配合 WeakReference 使用,只要进行垃圾回收,就回收
虚 :
必须配合引用队列使用,被引用对象回收时候,将虚引用入队,由Reference Handler 线程调用虚引用相关方法释放直接内存;
:::
:::info
ThreadLocal 内存泄漏问题:
ThreadLocal内存泄漏的原因主要是因为ThreadLocal中包含了ThreadLocalMap,然而ThreadLocalMap的对象是在Thread中的,如果Thread没有结束,则ThreadLocalMap一直不会释放,假如ThreadLocalMap中设置了很多值,而且没有手动设置remove(),则可能会造成内存泄露
:::
static class Entry extends Weakreference<ThreadLocal<?>>{
Object value;
Entry(ThreadLocal<?> k,Object v){
super(k);
value = v; // 强引用,不会被回收
}
}
判断对象可被垃圾器回收
个人回答 : 引用计数 可达性分析
:::info
如果一个或多个对象没有任何引用指向它,那么该对象就是垃圾,如果定位了垃圾,则有可能会被垃圾回收器回收; 定位垃圾的两种算法是 引用计数
和可达性分析算法
:::
:::info
引用计数:
一个对象被引用一次,在当前对象头上递增一次引用次数,如果对象引用次数0,则可回收;
当对象出现了循环引用,则失效;引发内存泄漏;
可达性分析:
JVM 的垃圾回收器都是采用可达性分析探索所有存活对象 扫描堆的对象,看能否沿着 GC Root 对象为起点的引用链找到该对象,找不到,就可回收;
- 虚拟机栈引用的对象(栈帧中的本地变量表);
- 方法区类静态属性引用的对象;
- 方法区常量引用的对象;
- 本地方法栈 JNI 引用的对象(Native方法);
:::
JVM垃圾回收算法
个人回答 : 标记清除,标记整理(针对老年代),复制算法(新生代常用)
:::info
标记清除 : 标记和清除 优点(标记和清除速度快)缺点(碎片化,内存不连贯)
标记整理 :同标记清除,解决了碎片化,但多了一步对象移动内存位置,效率有一定影响
复制算法 :
优点(垃圾对象多时效率高,无碎片)缺点(2块内存,同时刻,只能用一半,内存使用率低)一半年轻代用的多,将原有内存空间一分为二,每次使用其中一块,正在使用的对象复制到另一个内存空间,将该内存空间清空,交换两个内存的角色,完成垃圾回收;
:::
分代回收
个人回答 : 主要针对不同的内存区域采用不同的垃圾回收方式,垃圾回收器
:::info
- 堆区域划分
- 堆被分2份:新生代和老年代(1:2)
- 新生代内部分为三部分 Eden区 幸存者区 survivor(from和to) 8:1:1
- 对象分代回收策略
- 新建对象,去Eden
- 当 Eden内存不足,标记Eden和from的存活对象
- 复制算法到 to区,完毕后 Eden和from 内存释放
- 一段时间Eden内存又不足,标记Eden区to区存活对象,复制到 from区
- 幸存区对象熬过15次回收,晋升到老年代(大对象提前晋升)
MinorGC 新生代垃圾回收,暂停时间短 (STW)
Mixed GC 新生代和老年代部分区域垃圾回收 G1特有
FullGC 新生代老年代完整垃圾回收,暂停时间长,尽力避免
:::
G1 垃圾回收器
个人回答 :
- 应用于新生代和老年代,9以后默认使用 G1
- 划分多个区域,每个区域可充当 eden survivor old humongous(专为大对象准备)
- 采用复制算法
- 响应时间和吞吐量兼顾
- 分三个阶段 新生代回收(stw)、并发标记(重新标记 stw)、混合收集
- 如果并发失败(回收赶不上新建对象速度)触发Full GC
哪些垃圾回收器
串行垃圾收集器
Serial GC | Serial Old GC
并行垃圾收集器
Parallel Old GC | parNew GC
CMS(并发)垃圾收集器 作用在老年代
5. JVM 实战
JVM 调优参数哪里设置
war包部署在 tomcat 中设置 (修改tomcat_home/bin/catalina.sh文件) windows 是.bat
jar包部署在启动参数设置 java -Xms512m -Xmx1024m -jar xxxx.jar
通常在 linux系统下直接加参数启动 springboot 项目
java -Xms512m -Xmx1024m -jar xxxx.jar --spring.profiles.active=prod &
nohup : 用于系统后台不挂断运行命令
参数 & :命令在后台执行
:::
JVM 调优参数有哪些
堆空间大小 Xms堆初始化大小 Xmx堆最大大小
- 最大大小默认值物理内存25%,初始大小1/64;
- 堆太小,导致频繁的垃圾回收,stw,
- 堆内存打肯定好的,存在风险,假如 FullGC 扫描整个堆空间,stw时间长
- 最终: 尽量大,也需考虑当前计算机其他程序内存使用情况;
虚拟机栈的设置
- 每个线程默认1M,存放栈帧,调用参数,局部变量等,一般256K就够用 -Xss128K
- 通常减少线程的堆栈,可产生更多线程,实际受限于操作系统;
Eden和Survivor大小比例
年轻代晋升老年代阈值
**设置垃圾回收器 **
JVM 调优工具
内存泄漏排查思路
内存泄漏通常指堆内存,一些大对象不被回收的情况
- 通过 jmap 或设置 jvm 参数获取堆内存快照 dump
- 通过工具, VisualVM 分析 dump 文件,可加载离线的 dump 文件
- 通过查看堆信息情况,大概定位内存溢出的代码位置
- 找到对应代码,通过阅读上下文,进行修复即可
CPU 飙高排查方案和思路
- 找线程信息
ps H -eo pid,tid,%cpu | grep 40940
分析得到进程40940里 4.950占用cpu 较高
- 根据线程id 找有问题的线程 ,进一步定位到问题代码的源码行号
jstack 40940
40940是进程id,40955是线程id
十进制转十六进制
printf “%x\n” 4.955
:::info
- top命令查看cpu占用情况
- top命令查看后,可查哪个进程占用cpu较高
- ps命令查看进程的线程信息
- jstack命令查进程的哪些线程出现问题,最终定位问题;
:::
写在最后 : 一直有看看 JVM 的相关内容,但是整体架构不是太清晰, 这个视频从 JVM 组成, 类加载器, 垃圾回收, JVM调优实战四个方面通过面试题分类,基本包括基础的 JVM 知识内容,希望大家一起交流学习,共勉!