第一章--JVM面试题

1. 导读部分

写在开始 : 本文是学习 [黑马程序员] JVM 面试专题 的学后记录, 仅仅用作个人复习准备面试用。
附学习地址 : https://www.bilibili.com/video/BV1yT411H7YK?p=116&vd_source=08ac522c6603c56e243d5e129a309a60
image.png
image.png
image.png
image.png

2. JVM 组成

2.1 程序计数器

个人回答 : 程序计数器是运行时数据区的一部分,用来指向执行的指令的位置,唯一不会发生OOM的地方;

:::info
程序计数器(PC Register) : 线程私有,每个线程一份,内部保存字节码的行号。用于记录正在执行的字节码指令的地址。
:::

image.png

2.2 堆

个人回答 : 堆一般是运行时数据区空间最大的地方,存放的对象和数组;堆是线程共享的,也是垃圾回收的主要对象;堆分为新生代和老年代,新生代又分为eden区和s from 和 s to区,一般比例是8:1:1;

:::info
线程共享区域: 主要用来保存对象实例,数组等,当堆中没有内存空间可分配,无法再扩展时,OOM异常
组成: 年轻代 + 老年代

  • 年轻代分为三部分,Eden区和两个大小严格相同的 Survivor 区
  • 老年代主要保存生命周期长的对象,一般是一些老对象

JDK7和8的区别:

  • 7 有一个永久代,存储类信息、静态变量、常量、编译后代码;
  • 8 移除了永久代,数据存储到本地内存的元空间,防止内存溢出;

image.png
image.png

2.3. 方法区

个人回答 : 方法区也是线程共享的,7及之前是永久代,8及之后是元空间,

  • 方法区 Method Area 是各个线程共享的内存区域
  • 主要存储类的信息,运行时常量池
  • 虚拟机启动时候创建,关闭虚拟机时候释放
  • 如果方法区域中内存无法满足分配请求,OOM:Metaspace

image.png
:::info

常量池 : 可看作一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型和字面量等信息; 当类被加载,常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址;

:::
image.png
image.png

2.4. 直接内存

个人回答 : 没听过

:::info
直接内存

  • 不属于JVM 的内存结构,不由 JVM 进行管理。是虚拟机的系统内存,
  • 常见于 NIO 操作时,用于数据缓冲区,分配回收成本高,但读写性能高;不受JVM内存回收管理 :::

image.png
image.png

2.5 虚拟机栈

个人回答 ; 虚拟机栈也是运行时数据区一部分,每个线程私有,生命周期随着线程的产生和销毁而变化,当内存空间不够会抛出 stackoverflow 异常,也可能有情况抛出 OOM 异常,存储的是方法出口,局部变量等,栈内存按照先进后出的顺序执行;
:::info

Java Virtual machine Stacks ( java 虚拟机栈 )

  • 每个线程运行时所需要的内存,称为虚拟机栈,先进后出;
  • 每个栈由多个栈帧 (frame)组成,对应每次方法调用时所占用内存;
  • 每个线程只有一个活动栈帧,对应当前正在执行的那个方法;
  • 可存放 参数 | 局部变量 | 返回地址等

:::

2.6 垃圾回收是否涉及栈内存

个人回答 : 垃圾回收主要针对堆内存,栈是方法执行完毕弹栈释放,内存自动释放

2.7 栈内存分配越大越好吗

个人回答 : 不一定
:::info

未必,默认栈内存一般是 1024 K;
栈帧过大导致线程数变少,比如,机器总内存512M,能活动线程数则是512个,如果栈内存改为2048K,那么可以活动的栈帧就会减半;

:::

2.8 方法内的局部变量是否线程安全

个人回答 : 不一定
:::info

方法局部变量没有逃离方法作用范围,则安全; 如果局部变量引用了对象,并逃离方法作用范围,则需要考虑线程安全;

:::
image.png

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 不会被修改 可以避免某一个类被从重复加载,当父类已加载后则无需重复加载,保证唯一性;

:::
image.png

3.4 类装载执行过程

个人回答 : 准备 -> 连接 -> 初始化 太长记不清楚了,其中连接分为验证解析引用
:::info

类从加载开始,知道卸载为止的生命周期包括 加载 验证 准备 解析 初始化 使用 卸载
①加载 :
通过类全名,获取类的二进制数据流;解析为方法区的数据结构(Java类模型);在堆里创建类的实例,表示该类型。作为方法区这个类的各种数据的访问入口;
②连接 :
验证是否符合规范,安全性检查,文件格式,元数据,字节码都是格式检查,符号引用验证;准备,为类变量分配内存并设置类变量初始值,静态变量(分配空间在准备阶段完成设默认值)和静态变量final引用类型,赋值在初始化阶段完成,静态变量是final的基本类型,以及字符串常量,值已确定,赋值在准备阶段完成;解析类的符号引用转为直接引用,比如:方法中调其他方法,方法名可理解为符号引用,直接引用是使用指针直接指向方法;
③初始化 : 对类的静态变量,静态代码块进行初始化操作;优先初始化父类,自上而下顺序
④使用 : JVM
从入口方法开始执行用户的程序代码(带哦用静态类成员变量,new 创建对象实例)
⑤卸载

:::

小结 :

  1. 加载:查找和导入字节码文件
  2. 验证:保证类加载的准确性
  3. 准备:分配内存并设初始值
  4. 解析: 类中的符号引用转为直接引用
  5. 初始化:对静态变量|代码块初始化
  6. 使用 : JVM 从入口方法开始执行用户程序代码
  7. 卸载 : 执行完毕,JVM 开始销毁 Class 对象

image.png

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块内存,同时刻,只能用一半,内存使用率低)一半年轻代用的多,将原有内存空间一分为二,每次使用其中一块,正在使用的对象复制到另一个内存空间,将该内存空间清空,交换两个内存的角色,完成垃圾回收;

:::

分代回收

个人回答 : 主要针对不同的内存区域采用不同的垃圾回收方式,垃圾回收器
image.png
image.png
image.png
:::info

  1. 堆区域划分
    1. 堆被分2份:新生代和老年代(1:2)
    2. 新生代内部分为三部分 Eden区 幸存者区 survivor(from和to) 8:1:1
  2. 对象分代回收策略
    1. 新建对象,去Eden
    2. 当 Eden内存不足,标记Eden和from的存活对象
    3. 复制算法到 to区,完毕后 Eden和from 内存释放
    4. 一段时间Eden内存又不足,标记Eden区to区存活对象,复制到 from区
    5. 幸存区对象熬过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(并发)垃圾收集器 作用在老年代
image.png

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 调优工具

image.png
image.png

内存泄漏排查思路

image.png
image.png
image.png
内存泄漏通常指堆内存,一些大对象不被回收的情况

  1. 通过 jmap 或设置 jvm 参数获取堆内存快照 dump
  2. 通过工具, VisualVM 分析 dump 文件,可加载离线的 dump 文件
  3. 通过查看堆信息情况,大概定位内存溢出的代码位置
  4. 找到对应代码,通过阅读上下文,进行修复即可

CPU 飙高排查方案和思路

image.png

  1. 找线程信息

ps H -eo pid,tid,%cpu | grep 40940
分析得到进程40940里 4.950占用cpu 较高

  1. 根据线程id 找有问题的线程 ,进一步定位到问题代码的源码行号

jstack 40940
40940是进程id,40955是线程id
十进制转十六进制
printf “%x\n” 4.955

:::info

  1. top命令查看cpu占用情况
  2. top命令查看后,可查哪个进程占用cpu较高
  3. ps命令查看进程的线程信息
  4. jstack命令查进程的哪些线程出现问题,最终定位问题;
    :::

写在最后 : 一直有看看 JVM 的相关内容,但是整体架构不是太清晰, 这个视频从 JVM 组成, 类加载器, 垃圾回收, JVM调优实战四个方面通过面试题分类,基本包括基础的 JVM 知识内容,希望大家一起交流学习,共勉!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值