java面试题之JVM进阶

JVM进阶

  • 1. JVM垃圾回收的时候如何确定垃圾?是否知道什么是GC Roots?
  • 2. JVM调优和参数设置
  • 3. JVM常用配置参数
  • 4. 谈谈关于OOM的认识
  • 5. GC垃圾回收算法和垃圾收集器
  • 6. 查看服务器默认的垃圾回收器,如何配置垃圾回收器以及对垃圾回收的理解
  • 7. G1垃圾回收器

1. JVM垃圾回收的时候如何确定垃圾?是否知道什么是GC Roots?

垃圾:内存中已经不再被使用到的空间(对象)
判断方法:

  • 引用计数法(计数器为0的对象就是不再被引用的,可回收;无法解决循环依赖问题
  • 枚举根节点做可达性分析(根搜索路径):通过一系列名为“GC Roots”的对象作为起始点,也就是从GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明对象不可达。给定一个即可的引用作为根节点出发,通过引用关系遍历对象图,能被遍历到的对象就判定为存活,没有遍历到的对象判定为死亡

GC Roots:tracing GC的根集合,一组必须活跃的引用

哪些可以作为GC Roots的对象:

  • 虚拟机栈中引用的对象:栈中的局部变量,也叫作局部变量表
  • 方法区中的类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法Native中引用的对象

2. JVM调优和参数设置

JVM的参数类型:

  • 标配参数:java -version java -help java -showversion java -c xxx.class -> xxx.txt 进行反汇编(得到JVM执行的class文件)
  • X参数:-Xint 解释执行 -Xcomp 第一次使用就编译成本地代码 -Xmixed 混合模式
  • XX参数:
    • 布尔类型:-XX:+或者-某个属性值, + 表示开启,- 表示关闭。如==-XX:+PrintGCDetails==:打开详细的GC处理日志
    • KV设置类型:-XX:属性key=属性值value 如:-XX:MetaspaceSize=128m -XX:MaxTenuringThreshold=15
    • jinfo:使用jps -l查看对应线程的id,查看某一个JVM属性的具体值,jinfo -flag PrintGCDetails 线程id

使用jinfo -flags 线程id查看所有的默认JVM参数初始值以及修改的配置值
java面试题之JVM进阶_第1张图片-Xms:等同于==-XX:InitialHeapSize== ,初始堆内存,设置初始分配大小,默认为物理内存的1/64
-Xmx:等同于==-XX:MaxHeapSize==,最大堆内存,最大分配内存,默认为物理内存的1/4

查看JVM的默认值

  • java -XX:+PrintFlagsinitial 查看JVM初始化的参数默认值
  • java -XX:+PrintFlagsFinal 主要查看修改更新的内容 :=表示修改的值 =表示JVM默认加载
  • java -XX:+PrintFlagsFinal -version
    java面试题之JVM进阶_第2张图片
  • PrintFlagsFinal举例,运行java命令的同时打印出参数:java -XX:+PrintFlagsFinal -XX:MetaSpaceSize512m 运行的类名称
  • java -XX:+PrintCommandLineFlags (可查看默认的垃圾回收器)
    请添加图片描述

  1. 调优诊断工具:Arthas
    直接在github上下载:wget http://alibaba.github.io/arthas/arthas-boot.jar
    使用java -jar arthas-boot.jar运行,可以监控所有JVM进程
  2. 能否进行JVM调优,使得在老年代的Full GC不会频繁的发生?
  3. 内存太大使得GC停顿时间太长,控制Full GC的频率关键在于太多数对象生命周期不会太长,不能有成批量的对象在老年代进行GC
  4. 什么情况下需要进行JVM调优:
    • Heap内存持续上涨到设置的最大内存值
    • Full GC次数频繁
    • GC停顿时间过长
    • 出现OOM
    • 系统吞吐量与响应性能下降
  5. MinorGC尽可能多的收集垃圾对象,降低Full GC的发生频率,Full GC是导致延迟低和吞吐量的最大原因
  6. 堆大小的调整,从Minor GC持续时间,Minor GC的次数,Full GC的最大持续时间和频率几个角度出发
  7. 吞吐量优先的GC收集器:-XX:+UseParallelGC -XX:UseParallelOldGC
  8. 响应时间的GC收集器:CMS或者G1

3. JVM常用配置参数

元空间与永久代的区别:
java8以后,永久代已经被移除,使用元空间替代。
最大区别在于:永久代使用的是JVM的堆内存,元空间使用的是本机物理内存。因此元空间仅仅受本地内存限制


  • -Xms
  • -Xmx
  • -Xss:-XX:ThreadStackSize 设置单个线程的大小(栈空间),一般默认为512K-1024K(初始值为0则表示使用默认值1024KB)
  • -Xmn: 设置年轻代大小
  • -XX:MetaSpaceSize 设置元空间大小
  • -XX:+PrintGCDetails:打开详细的GC处理日志
  • -XX:SurvivorRatio:设置新生代Eden区和Survivor From/To区的比例 默认为8:1:1 设置-XX:SurvivorRatio=4则Eden:From:To = 4:1:1
  • -XX:NewRatio:配置老年代和年轻代在堆中的比例 默认为2:1 设置-XX:NewRatio=4 则New:Old = 1:4
  • -XX:MaxTenuringThreshold:设置垃圾的年龄 如果设置为0,则年轻代的对象不经过Survivor区,直接进入老年代,对于老年代较多的应用,可以提高效率;如果将值设置较大,则年轻代对象在Survivor区进行多次复制,增加对象在年轻代的存活时间(该值默认为0-15)

设置参数案例:

-XX:SurvivorRatio=4 -XX:MetaspaceSize=512m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseSerialGC

4. 谈谈关于OOM的认识

  • java.lang.StackOverFlowError:一直递归调用方法,栈溢出错误

  • java.lang.OutOfMemoryError:java heap space :创建对象太多,java堆内存溢出

  • java.lang.OutOfMemoryError:GC overhead limit exceed:GC回收时间过长,超过98%的时间用来做GC并且回收了不到2%的堆内存

  • java.lang.OutOfMemoryError:Direct buffer memory:写NIO程序经常使用ByteBuffer来读取或者写数据,一种基于通常channel和缓冲区Buffer的IO方式,可以使用Native函数库直接分配堆外内存,通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,可以在一些场景中显著提高性能,因为避免了java堆和Native堆中来回复制数据。如果不断的分配本地内存,堆内存很少使用,JVM不需要执行GC,DirectByteBuffer对象就不会被回收;而堆内存充足,单本地内存已经使用完了,再次尝试分配本地内存会出现OOM

  • java.lang.OutOfMemoryError:unable to create new native thread:高并发请求服务器时,经常出现该异常。
    原因:

    • 应用创建了太多线程,一个应用进程创建多个线程,超过系统承载极限
    • 服务器并不允许你的应用程序创建这么多线程,Linux系统默认允许单个进程可以创建的线程数为1024个,一旦超过这个数量,直接报该错误

    解决:

    • 降低应用程序创建线程的数量,分析应用程序是否真的需要创建这么多线程,修改代码
    • 对于有的应用确实需要创建许多线程,可通过修改Linux服务器配置,扩大默认限制
  • java.lang.OutOfMemoryError:Metaspace:元空间内存溢出,而元空间存放的信息有

    • 虚拟机加载的类信息
    • 常量池(运行时常量池在方法区中,字符串常量池在堆中)
    • 静态变量
    • 即时编译的代码
      当不断生成类往元空间送,类占据的空间总是会超过MetaspaceSize

5. GC垃圾回收算法和垃圾收集器

  • 回收算法:

    • 复制
    • 标记清除
    • 标记压缩(整理)
    • 分代收集
  • 垃圾回收器(回收算法的实现):

    • Serial:串行GC,为单线程环境设计且只使用一个线程进行垃圾回收,会暂停所有的用户线程
    • ParNew:并行GC(多线程),复制算法,搭配CMS
    • ParallelOld:并行GC(多线程),复制算法,老年代收集器
    • SerialOld:是Serial收集器的老年代版本,串行GC(单线程),“标记-整理”
    • Parallel:多个垃圾收集器并行工作,用户线程是暂停的,适用于科学计算和大数据计算
    • CMS:Concurrent Mark Sweep并发标记GC(多线程),用户线程和垃圾收集线程同时执行(可能交替执行),不需要暂停用户线程,适用于对响应时间有要求的场景
    • G1:全区域(整个堆)的垃圾回收器Garbage First,将堆内存分割成不同的区域然后并发的执行垃圾回收

6. 查看服务器默认的垃圾回收器,如何配置垃圾回收器以及对垃圾回收的理解

java -XX:+PrintCommandLineFlags -version 查看JVM默认参数配置

或者使用jinfo -flags 线程id

在VM options中修改GC回收器:

-XX:+UseParallelGC

-XX:+UseCMSGC

-XX:+UseSerialGC

-XX:+UseG1GC


7大垃圾回收器分类以及理解

Young Generation Old Generation
Serial Serial MSC(Serial Old)
Parallel Scavenge Parallel Compacting(Parallel Old)
ParNew CMS

另外,G1 GC在Young和Old区都能用

  • SerialGC:为单线程环境设计且只使用一个线程进行垃圾回收,会暂停所有的用户线程,只使用一个线程进行垃圾回收可能产生较长的等待(Stop-The-World,STW),是JVM在Client模式下默认的新生代垃圾回收器。

    对应的参数为 -XX:+UseSerialGC,开启后会使用:Serial(Young)+ SerialOld(Old)的收集器组合,新生代使用复制算法,老年代使用标记整理压缩算法

  • ParNewGC:使用多线程进行垃圾回收,在垃圾收集时,会STW暂停其他所有工作线程直到收集结束。

    对应的参数为:-XX:+UseParNewGC,开启后会使用:ParNewGC(New区)+ SerialOld(Old区)的收集器组合(过时),只影响新生代的收集,不影响老年代,新生代使用复制算法,老年代使用标记整理压缩算法

  • Parallel Scavenge默认的回收器ParallelGCParallelGC(New区)+ ParallelOld(Old区)的组合,并行的多线程垃圾收集器,也叫吞吐量优先收集器。与ParNew的区别在于存在自适应调整策略,动态调整最合适的停顿时间

  • Parallel Old:Parallel Scavenge的老年代版本,使用多线程的标记整理,可触发新生代的ParallelScavenge收集器

  • CMS:并发标记清除,是一种获取最短的回收停顿时间为目标的收集器,并发收集停顿低,与用户线程一起执行

    对应参数:-XX:+UseConcMarkSweepGC,开启后会使用:ParNewGC(New区)+ CMS(Old区)+ SerialOld的收集器组合,SerialOld作为CMS出错的后备收集器。CMS的过程:

    • 初始标记Initial Mark(STW)
    • 并发标记Concurrent Mark(并发)
    • 重新标记Remark(STW)
    • 并发清除Concurrent Sweep(并发)

    优缺点:并发收集停顿低;并发执行对CPU资源压力大,采用的标记清除算法会导致大量碎片

  • SerialOld:Serial收集器的老年代版本,使用标记整理,作为CMS的后备收集器。对应参数:-XX:+UseSerialOldGC(过时)


GC日志信息的参数说明:

  • DefNew:Default New Generation
  • Tenured:Old
  • ParNew:Parallel New Generation
  • PSYoungGen:Parallel Scavenge
  • ParOldGen:Parallel Old Generation

JVM中的Server和Client模式是什么?

32位OS,无论硬件如何都默认使用Client的JVM模式;64位OS,默认使用Server的JVM模式


如何选择7大垃圾回收器(从吞吐量,延迟,内存中选择两个设计)

  • 单CPU或者小内存,单机程序使用-XX:+UseSerialGC
  • 多CPU,需要大吞吐量,使用-XX:+UseParallelGC或者-XX:+UseParallelOldGC
  • 多CPU,追求低停顿时间,快速响应,使用-XX:+UseConcMarkSweepGC或者-XX:+UseParNewGC
参数 新生代收集器你 新生代算法 老年代收集器 老年代算法
-XX:+UseSerialGC SerialGC 复制 SerialOldGC 标记整理
-XX:+UseParNewGC ParNew 复制 SerialOldGC 标记整理
-XX:+UseParallelGC -XX:+UseParallelOldGC Parallel [Scavenge] 复制 Parallel Old 标记整理
-XX:+UseConcMarkSweepGC ParNew 复制 CMS+SerialOld 标记清除
-XX:+UseG1GC G1整体采用标记整理 局部是通过复制,不产生内存碎片

7. G1垃圾回收器

  • 以前收集器的特点:
    • 年轻代和老年代都是各自独立且连续的内存块
    • 年轻代Eden+From+To进行复制算法
    • 老年代收集必须扫描整个区域
    • 尽可能少且快速的执行GC为设计原则
  • G1:Garbage First 面向服务端应用的收集器

G1的STW可控,在停顿时间STW上添加了预测机制,可以指定期望停顿时间。解决内存碎片问题,又保留了CMS垃圾收集器低暂停时间的优点。对于Java heap的改变为:不再区分老年代和新生代,宏观上将整个内存划分成多个独立的区域

  • 回收步骤:
    • 初始标记
    • 并发标记
    • 最终标记
    • 筛选回收
  • 参数设置:
    • -XX:+UseG1GC
    • -XX:MaxGCPauseMills = 100 最大停顿时间为100ms
    • -Xmx32g 设置最大内存
  • 相比CMS的优势:
    • G1不会产生内存碎片
    • 可以较为精确的控制停顿时间

你可能感兴趣的