JVM常见面试知识点

一、JDK、JRE、JVM的关系

在这里插入图片描述

JDK:JRE + 执行工具
JRE:JVM + 核心类库
JVM:执行的机器

二、Class文件格式

Magic Number:魔数,4位,8个16进制位 (cafe babe)Java class文件共有,确定这个文件是否为一个能被虚拟机接收的 Class 文件
Minor Version:子版本,两个字节
Major Version:主版本,两个字节
constant_pool_count:常量池中的数量,两个字节65535
constant_pool:常量池
access_flags:权限标识
this_class:当前类
super_class:父类
interfaces_count:实现的接口数
interfaces:具体接口
fields_count:属性数量
fields:具体的属性
methods_count:方法数量
methods:具体方法
attributes_count:附加属性属性
attributes:具体附加属性

三、对象(数组)布局

3.1、普通对象

  1. Mark Word(对象头中):8字节,包括对象哈希码、锁状态标志、GC分代年龄、偏向锁线程ID等信息,需要注意的是,不同锁状态下,Mark Word对象头中保存的字段不同。
  2. Class Point类型指针(对象头中):指向对象的类元信息的指针,参数-XX:+UseCompressedClassPointers开启时类型指针占4字节,否则类型指针占8字节。默认情况下开启类型指针压缩,占用4字节。
  3. 实例数据:对象中的成员变量。参数-XX:+UseCompressedOops开启时,引用类型占用4字节,不开启时占用8字节,默认开启。
  4. Padding对齐填充:将对象所占大小填充至8的倍数字节。

3.2、数组对象

  1. Mark Word(对象头中):8字节,包括对象哈希码、锁状态标志、GC分代年龄、偏向锁线程ID等信息,需要注意的是,不同锁状态下,Mark Word对象头中保存的字段不同。
  2. Class Point类型指针(对象头中):指向数组中存放的对象的类元信息的指针,参数-XX:+UseCompressedClassPointers开启时类型指针占4字节,否则类型指针占8字节。默认情况下开启类型指针压缩,占用4字节。
  3. 数组数据
  4. Padding对齐填充:将对象所占大小填充至8的倍数字节。

三、类的加载

3.1、类的加载过程

  • Loading:加载,将class文件加载到内存,一个class文件加载到内存有两个过程,一个是将class文件的二进制信息加载到内存,一个是生成一个Class对象(Class对象放在方法区,Class对象在存在指向类二进制信息的指针)。
  • Linking:链接
    • verification: 验证,验证class文件是否符合JVM规范
    • preparation: 准备,静态变量(static)赋默认值,静态final变量(final static)直接初始化、
    • resolution:解析,将符号引用转化为直接引用,符号引用可以理解为只是存储了对象的一个名称,直接引用是对象在常量池中真实地址。
  • Initializing:初始化,静态变量赋初始值,执行静态代码块

3.2、类加载器

  • BootStrap:加载lib/rt.jar charset.jar等核心类,C++/C实现,java中没有对应的类
  • Extension:加载扩展包/jre/lib/ext/*.jar或由-Djava.ext.dirs指定内容
  • App:加载classpath指定内容
  • Customer ClassLoader:自定义classLoader

各种类加载加载的路径
BootStrap ClassLoader Path:
E:\SystemPath\jdk1.8\jre\lib\resources.jar
E:\SystemPath\jdk1.8\jre\lib\rt.jar
E:\SystemPath\jdk1.8\jre\lib\sunrsasign.jar
E:\SystemPath\jdk1.8\jre\lib\jsse.jar
E:\SystemPath\jdk1.8\jre\lib\jce.jar
E:\SystemPath\jdk1.8\jre\lib\charsets.jar
E:\SystemPath\jdk1.8\jre\lib\jfr.jar
E:\SystemPath\jdk1.8\jre\classes
=======================================
Extension ClassLoader Path:
E:\SystemPath\jdk1.8\jre\lib\ext
C:\WINDOWS\Sun\Java\lib\ext
=======================================
Application ClassLoader Path:
E:\SystemPath\jdk1.8\jre\lib\charsets.jar
E:\SystemPath\jdk1.8\jre\lib\deploy.jar
E:\SystemPath\jdk1.8\jre\lib\ext\access-bridge-64.jar
E:\SystemPath\jdk1.8\jre\lib\ext\cldrdata.jar
E:\SystemPath\jdk1.8\jre\lib\ext\dnsns.jar
E:\SystemPath\jdk1.8\jre\lib\ext\jaccess.jar
E:\SystemPath\jdk1.8\jre\lib\ext\jfxrt.jar
E:\SystemPath\jdk1.8\jre\lib\ext\localedata.jar
E:\SystemPath\jdk1.8\jre\lib\ext\nashorn.jar
E:\SystemPath\jdk1.8\jre\lib\ext\sunec.jar
E:\SystemPath\jdk1.8\jre\lib\ext\sunjce_provider.jar
E:\SystemPath\jdk1.8\jre\lib\ext\sunmscapi.jar
E:\SystemPath\jdk1.8\jre\lib\ext\sunpkcs11.jar
E:\SystemPath\jdk1.8\jre\lib\ext\zipfs.jar
E:\SystemPath\jdk1.8\jre\lib\javaws.jar
E:\SystemPath\jdk1.8\jre\lib\jce.jar
E:\SystemPath\jdk1.8\jre\lib\jfr.jar
E:\SystemPath\jdk1.8\jre\lib\jfxswt.jar
E:\SystemPath\jdk1.8\jre\lib\jsse.jar
E:\SystemPath\jdk1.8\jre\lib\management-agent.jar
E:\SystemPath\jdk1.8\jre\lib\plugin.jar
E:\SystemPath\jdk1.8\jre\lib\resources.jar
E:\SystemPath\jdk1.8\jre\lib\rt.jar
E:\project_01\target\classes
D:\DevelopmentTools\IntelliJ IDEA 2019.3.3\lib\idea_rt.jar

3.3、自定义类加载器

  1. 继承ClassLoader类
  2. 重写findClass()方法(设计模式 - 模板方法)
  3. 将class文件存进byte[]数组
  4. 将byte[]数组通过defindClass方法将byte[]数组转成Class对象

注意:打破双亲委派机制需要重写loadClass()(Tomcat中类的热部署)

3.4、类加载过程的双亲委派原则

在这里插入图片描述

使用双亲委派机制的原因:
主要原因:安全,防止类似java.lang.String这样的自定义类覆盖原先的类。
次要原因:防止重复加载
加上VM参数-verbose:class,可以查看加载了哪些类

3.5、类初始化的时机

  1. 对类进行反射调用时,该类必须初始化
  2. 初始化子类时,父类必须先初始化
  3. 虚拟机启动时,被执行的主类必须初始化
  4. 访问非final修饰的静态变量时,类必须先初始化

3.6、类卸载的时机

  1. 该类的所有实例都已经被回收,堆中不存在该类的实例对象
  2. 加载该类的ClassLoader已经被回收
  3. 该类对应的Class对象没有被引用

四、垃圾回收

4.1、Java中什么是垃圾?

  1. 没有任何引用指向的一个对象。
  2. 多个对象循环引用,但是没有其他引用指向这堆循环引用的对象。

4.2、如何定位Java中的垃圾?

  1. 引用计数,可定位没有任何引用指向的对象。
  2. 根可达算法(root searching),以根对象为起点,把对象连接起来,这些对象都不是垃圾,剩下的没有被连接的对象都是垃圾。

GC Root包括:

  1. 线程栈变量(JVM Stack 虚拟机栈)
  2. JNI指针 (native method stack 本地方法栈)
  3. 静态变量(static references in method area 方法区)
  4. 常量池 (runtime constant pool 常量池)

简单来说,根变量包括虚拟机栈和本地方法栈中的引用变量、方法区中的静态变量、运行时常量池中的引用变量。

4.3、常见的垃圾回收算法

  1. 标记清除(Mark-Sweep) - 位置不连续,产生碎片,适用于存活对象比较多的情况,两遍扫描,一遍寻找存活对象,一遍清除无用对象
  2. 拷贝算法(Coping) - 没有碎片,浪费空间,适用于存活对象比较少的情况,扫描一次,移动复制对象,需要调整对象引用。
  3. 标记压缩(Mark-Compact) - 没有碎片,效率偏低(在进行压缩,即复制的时候需要进行线程同步),扫描两次,需要移动对象。

4.4、垃圾回收器

4.4.1 年轻代垃圾回收器

1、 Serial

采用复制算法对年轻代进行垃圾回收,单线程串行回收,回收期间需要Stop The World(STW)

2、Parallel Scavenge

采用复制算法对年轻代进行垃圾回收,多个垃圾回收线程并行回收,回收期间需要Stop The World(STW)。与ParNew最大的不同在于,它关注的是垃圾回收的吞吐量,也就是总时间与垃圾回收时间的比例。

相关参数:

  1. -XX:SurvivorRatio 设置Survivor的比例
  2. -XX:PreTenureSizeThreshold 设置大对象的大小
  3. -XX:+ParallelGCThreads 并行收集器的线程数,同样适用于CMS,一般为和CPU核数相同
  4. -XX:+UseAdaptiveSizePolicy 自动选择各区大小比例
3、ParNew

为了配合老年代垃圾回收器CMS,在Parallel Scavenge基础上改造而来的年轻代垃圾回收器。

4.4.2 老年代垃圾回收器

1、Serial Old

采用标记整理对老年代进行垃圾回收,单线程串行回收,回收期间需要Stop The World(STW)

2、Parallel Old

采用标记整理对老年代进行垃圾回收,多个垃圾回收线程并行回收,回收期间需要Stop The World(STW)

3、CMS

(1)初始标记:单线程执行,需要STW,标记GC Root,速度很快。
(2)并发标记:单线程执行,不需要STW,标记与GC Root关联的对象,垃圾回收线程与工作线程并发执行。
(3)重新标记:多线程执行,需要STW,对并发标记期间新产生的垃圾进行重新标记。
(4)并发清除:单线程执行,不需要STW,并发清除之前标记的垃圾。

CMS存在的三个问题:

  1. 产生浮动垃圾,在并发标记和并发清理的阶段可能产生新的垃圾,称为浮动垃圾
  2. 使用的是标记清除算法,会产生碎片(开启压缩整理参数)
  3. 并发失败(Concurrent mode failure),在并发标记或者并发清理阶段,产生了新的对象,需要分配空间,但分配不下的时候,会进入STW,使用Serial Old来进行回收。可通过参数来降低触发full gc的阈值,以保证有足够空间给新的对象。

三色标记

  1. 白色:未被标记的对象
  2. 灰色:自身被标记,成员变量未被标记
  3. 黑色:自身和成员变量都被标记

漏标问题:灰色到白色的引用被删除了,黑色的指向了白色(非垃圾的白色对象被错误回收)

  1. 增量更新:关注引用的增加,当新增了黑色到白色的引用时,把黑色重新标记为灰色,重新扫描。(CMS)
  2. 快照Snapshot:STAB,关注引用删除,当删除了从灰色到白色的引用时,把该引用收集起来,重新扫描这些引用。(G1)

相关参数

  1. -XX:+UseConcMarkSweepGC 使用ParNew + CMS垃圾回收器
  2. -XX:ParallelCMSThreads CMS线程数量
  3. -XX:CMSInitiatingOccupancyFraction 使用多少比例的老年代后开始CMS收集,默认是68%
  4. -XX:+UseCMSCompactAtFullCollection 在Full GC时进行压缩。
  5. -XX:CMSClassUnloadingEnabled 回收方法区Class对象
  6. -XX:CMSFullGCsBeforeCompaction 多少次Full GC之后进行压缩
  7. -XX:CMSInitiatingPermOccupancyFraction 在什么比例时进行Perm回收
  8. GCTimeRatio 设置GC时间占用程序运行时间的百分比
  9. -XX:MaxGCPauseMillis 停顿时间,是一个建议时间,GC会尝试各种手段达到这个时间,比如减小年轻代

注意:CMS追求的是响应时间

4.4.3 通用垃圾回收器

1、G1

逻辑分代,物理不分代。堆中存在四种内存块,Old、Humongous、Survivor、Eden。
存在Young GCFull GC的概念。

为什么G1使用SATB解决漏标问题
灰色到白色的引用删除时,如果没有黑色指向白色,引用会被push到堆栈。
下次扫描时,拿到这个引用,由于有RSet存在,不需要扫描整个堆区查找指向白色的引用,效率比较高,快照Snapshot配合RSet使用,浑然天成。

相关参数:

  1. -XX:+UseG1GC 使用G1垃圾回收器
  2. -XX:MaxGCPauseMillis 停顿时间,是一个建议时间,GC会尝试各种手段达到这个时间,比如减小年轻代
  3. -XX:GCPauseIntervalMills
  4. -XX:G1HeapRegionSize 分区大小,建议逐渐增大该值,1 2 4 8 16 32…,随着Size的增大,垃圾存活时间更长,GC间隔更长,但每次GC的耗时也更长
  5. -XX:G1NewSizePercent
  6. -XX:G1MaxNewSizePercent
2、ZGC

待完善

3、Shenandoah

待完善

4.4.4 其他知识点

  1. jdk8默认的垃圾回收器:Parallel Scavenge + Parallel Old
  2. 追求吞吐量:PS + PO
  3. 追求响应时间:ParNew + CMS / G1
  4. 内存泄露:垃圾对象一直没有被GC回收,积累到一定量后会造成内存溢出

4.5、常见垃圾回收器组合参数设定

  1. -XX:+UseSerialGC:使用Serial + Serial Old,适用于小型程序。
  1. -XX:+UseConcMarkSweepGC:使用ParNew + CMS + Serial Old,当已经没有空间可以分配对象时,会触发STW,由Serial Old进行垃圾清理。
  1. -XX:+UseParallelGC:使用`Parallel Scavenge + Parallel Old,JDK1.8 默认配置。
  1. -XX:+UseG1GC:使用通用垃圾回收器G1

查看默认垃圾回收器的命令:java -XX:+PrintCommandLineFlags -version

4.6、命令行参数

  1. 标准参数:- 开头
  2. 非标准参数:-X 开头,特定版本HotSpot支持特定命令
  3. 不稳定参数:-XX 开头,下一版本中可能被弃用
  1. -XX:MaxTenuringThreshold 升代年龄,默认15
  2. -XX:+PrintCommandLineFlags 打印默认参数
  3. -Xmn10M 设置年轻代区大小为10M
  4. -Xms40M 设置初始堆大小为40M
  5. -Xmx40M 设置最大堆大小为40M
  6. -Xss40M 设置栈大小为40M,默认为1M
  7. -XX:+PrintGC 打印GC信息
  8. -XX:+PrintGCDetails 打印GC详细信息
  9. -XX:+PrintGCTimeStamps 打印GC时间戳
  10. -XX:+PrintGCCauses 打印GC原因
  11. -XX:+PrintFlagsInitial 打印默认参数
  12. -XX:+PrintFlagsFinal 打印实际参数
  13. -XX:+PrintFlagsFInal -version | grep xxx 打印与xxx相关的参数
  14. -XX:+UseTLAB 使用TLAB(堆上的栈内存)
  15. -XX:+PrintTLAB 打印TLAB使用情况
  16. -XX:+TLABSize 设置TLAB大小
  17. -XX:+DisableExplictGC 使System.gc()失效
  18. -XX:+PrintHeapAtGC
  19. -XX:+PrintGCApplicationConcurrentTime 打印应用程序时间
  20. -XX:+PrintGCApplicationStoppedTime 打印暂停时长
  21. -XX:+PrintReferenceGC 打印回收了多少种不同类型的引用
  22. -verbose:class 类加载的详细过程
  23. -XX:+PrintVMOptions 打印虚拟机参数

五、JVM调优策略

5.1、JVM调优基础概念

  • 吞吐量:执行用户代码时间 /(执行用户代码时间 + 垃圾回收时间)
  • 响应时间:STW越短,响应时间越短
  • 日志设定:-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:+NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause
    会有5个日志文件,每个20M,当一个文件满20M之后使用下一个文件,循环使用,日志总大小不会超过100M

5.2、CPU经常100%?

CPU100% -> 有线程占用系统资源 -> 找出哪个进程CPU高(top) -> 找出具体的线程(top -Hp) -> 导出线程的堆栈(jstack) -> 查看哪个方法(栈帧)消耗时间(jstack)

5.3、CPU内存飙升?

  1. 导出堆内存 (jmap)
  2. 分析(jhat, jvisualvm, mat, jprofiler)

5.4、常用命令

jps 查看所有Java进程
jstack [pid] 查看线程堆栈
jinfo [pid] 查看Java进程的JVM信息
jstat gc [pid] 查看GC信息
jmap -histo [pid] | head [num] 查看Java进程占用内存前num的类
jmap -dump:format=b,file=xxx pid :
在线排查工具arthas

当内存特别大的时候,jmap -dump执行期间会对进程产生很大影响,甚至卡顿,设置HeapDumpOnOutOfMemoryError从参数,OOM时产生转储文件

线程名要是有意义的名称,使用线程池时需要使用自定义的ThreadFactory,方面排查问题。

六、面试常见知识点

6.1、Young GCFull GC

Young GC:回收新生代(Eden : s1 : s2 = 8 : 1 : 1)
Full GC:回收老年代、新生代、方法区(JDK >= 1.8 会回收meta space元空间)
新生代 : 老年代 = 1 : 2

6.2、进入老年代的几种情况

  1. 大对象直接进入老年代:通过JVM参数-XX:PretenureSizeThreshold可以设置大对象的大小,这个参数只在Serial和ParNew两个垃圾收集器下有效。(避免大对象在Eden区和Survivor区之前复制而降低效率)
  2. 长期存活对象:在Eden与Survivor区之前复制次数达到15次。
  3. 对象动态年龄判断:eden+s1区存活对象复制到s2时,如果已超过s2空间的一半,年龄1+年龄2+…+年龄n > 50%,将年龄大于等于n的对象放入老年代
  4. 老年代空间分配担保机制:young gc之前,会计算老年代的剩余可用空间。如果老年代剩余可用空间小于年轻代所有对象(包括垃圾对象)空间之和,且没有配置-XX:-HandlePromotionFailure(担保参数,1.8默认配置),则直接触发full gc;如果配置了参数,则判断老年代剩余可用空间是否小于每次young gc进入老年代的平均对象大小,是则进行full gc。
    在这里插入图片描述

6.3、内存分配策略

待完善
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

it00zyq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值