JVM面试题

595 阅读6分钟

1. 背景

储备足够多的知识是解决JVM调优的必要条件,努力掌握每一次机会,不断地去尝试,才能提高自己的水平

2. 面试题记录

2.1. 解释#年轻代垃圾回收机制

-Xms3G , -Xmx3G, -Xmn1G, -XX:SurvivorRatio=8 -XX:PrintGCDetails, -verbose:gc

eden区发生minor gc的时候有95% 以上的对象是垃圾对象

2.2 JVM调优

现象: TP99耗时偏高,有明显的毛刺,发现YGC和FGC频繁

解决: 频繁的YGC导致本该在YGC就被回收的对象进入了老年代,也导致了老年代继续触发FGC,频繁的STW,导致TP99偏高

优化目标:减少YGC的次数和耗时,减少FGC的次数和耗时

  1. 放大年轻代
  1. 元数据区的默认初始值只有21M,如果动态代理对象比较多,就会导致元空间进行FGC,导致STW,观察了一下元空间常住对象的大小,大概100M,直接指定元空间和最大容量为256M,防止动态调整
  1. 使用并发预清理

2.3 jvm垃圾回收算法

标记清除

复制算法

标记整理压缩

分代收集

2.3 三色标记法

黑白灰,初始状态,所有的对象都是白色的,只有GC Roots是黑色的,

白色代表没有被垃圾回收器访问过,黑色表示对象已经被GC收集器访问过,且这个对象的所有引用都已经被扫描过, 灰色表示至少存在一个引用没有被扫描过

原始快照和增量更新 并发标记会带来多标和漏标的问题,多标不一定是要解决的,如果变成了浮动垃圾,就等下次标记回收 漏标一定要解决的,相当于回收了不该回收的对象

2.3 双亲委派机制

类加载器就是用来把类加载到jvm中的一种东西,对于任何一个类,由他的加载器和它本身确定在jvm中的唯一性,

引导类加载器、扩展类加载器、应用类加载器、用户自定义加载器,用户可以继承ClassLoader类,实现其中的findClass方法来实现自定义的类加载器

保护核心类的安全,防止被篡改, 避免类的重复加载,一个类只会被加载一次 先检查自己有没有加载,如果没有加载就让父类加载器去加载,调用的loadClass()方法去加载 加载失败就会抛出ClassNotFoundException 要想打破双亲委派的话,就自定义一个类加载器,然后重写loadClass方法

2.4 java中的强引用,软引用,弱引用,虚引用

强引用,宁可OOM,虚拟机也不会回收持有强引用的对象, 软引用在jvm oom之前会回收到软引用对象 SoftReference和weakReference,不管内存足不足弱引用只要GC就会被回收

2.5 JVM的类加载机制

加载,连接, 初始化,使用,销毁

2.6 JVM什么时候 FGC

system.gc()

老年代空间不足

promotion failed 是在minor gc的时候,survivor space 放不下对象放入老年代,老年代也放不下导致的

concurrent mode failure 是在执行FGC的时候对象要放入老年代,而此时老年代空间不足导致的

2.7 什么样的对象直接进入老年代

大对象直接进入老年代,定义大对象是由jvm参数决定的,-XX:PretenureSizeThreshold, 当我们新分配的对象大于等于这个值,就会直接在老年代分配

长期存活的对象进入老年代,年龄计数器 > 15

动态年龄判断机制

2.8 老年代担保机制

两次判断:在发生minor gc之前,jvm会检查老年代的可用连续空间是否大于新生代的年龄总大小

通过计算历史minor gc后进入老年代对象的平均大小,如果小于老年代的可用连续空间,就minor gc

是否打开老年代担保机制参数:-XX:HandlePromotionFailure

2.8 如何解决网络时延的问题

  1. 第一个看我机器内存、cpu GC的情况,会导致我接口变慢,机器的CPU,内存过高,导致CPU时间片来回切换, 导致处理历元的时间延长,应该是普遍的延长,不应该是某几个站点
  1. 看我机器的TCP收发队列
  1. tcpdump 抓包wireshark看包的RTO
  1. ping主机网络时延

2.9 服务迭代上线以后发现内存偏高了20%,这个时候该怎么办

  1. 排查现有版本的问题,heap dump看看是否有大对象
  2. 发上去老版本,进行对比是否新老版本是否不一致

2.10 能够找到 Reference Chain 的对象,就一定会存活么?

这不一定,还要看reference类型。弱引用会在GC时会被回收,软引用会在内存不足的时候被回收。但没有Reference Chain的对象就一定会被回收。

2.11 你说你做过JVM参数调优和参数配置,请问如何查看JVM系统默认值

使用-XX:+PrintFlagsFinal参数可以看到参数的默认值。这个默认值还和垃圾回收器有关,比如UseAdaptiveSizePolicy。

2.12 栈帧的组成部分

栈帧包含:局部变量表、操作数栈、动态连接、返回地址

2.13 JIT是什么?

为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化。完成这个任务的编译器,就称为即时编译器(Just In Time Compiler),简称 JIT 编译器。

2.14 HashMap中的key,可以是普通对象么?需要什么注意的地方?

Map的key和value都可以是任何类型。但要注意的是,一定要重写它的equals和hashCode方法,否则容易发生内存泄漏。

2.15 解释一下MESI 协议

保持缓存的一致性, modify修改、exclusive独占、share共享、invalid失效, 4种状态能够两两转换,形成16种状态,其实就是一个状态机,

CPUA通过总线地址冲突检测到CPUB有这个值,于是向B请求这个值,这个时候值N变成共享状态 当CPUA要修改n的时候会通过总线发出失效命令,让CPUB的高速缓存对应的n的状态变成失效,CPUA变成独占状态

等待总线发出失效再确认ACK,严重影响了CPU的利用率,所以引入了store buffer,有了它以后cpu可以直接修改共享状态的变量 修改后直接扔到store buffer,由store buffer来等待其他cpu的ack确认,收到确认以后才将变量值写入本地缓存,这样CPU就可以继续去做其他事了, tore buffer的容量很小,

加入store buffer 和失效队列打破了一致性,希望在需要一致性的地方加入内存屏障,屏蔽的是store buffer和invalid queue 我们在写操作后边加入写屏障,cpu就必须等待存储队列的所有写操作都刷到对面的失效队列 在读操作之前加入读屏障,必须把失效队列的值都处理完,再去读变量,一写一读组合起来就达到了一致的效果

www.bilibili.com/video/BV1cT…