2021秋招最新JAVA面试题|JVM剖析与常用的调优总结

Posted BugGuys

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2021秋招最新JAVA面试题|JVM剖析与常用的调优总结相关的知识,希望对你有一定的参考价值。

JAVA基础篇面试题

1. 什么是GC Roots

GC Roots是一组活跃的引用;常用于判断对象是否被回收的可达性分析法中;

可达性分析:通过一系列称为GC Roots的跟对象作为起始节点集,从这些结点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”,如果某个对象到根节点间没有任何引用链相连,则证明此对象可再被使用。

能够作为GC Roots对象

  • 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、布局变量、临时变量等。
  • 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
  • 在方法区中常量引用对象,譬如字符串常量池里的引用。
  • 在本地方法栈JNI(Native)引用的对象。
  • Java虚拟机内部的引用,如基本数据类型对应的Class现象,一些常驻的异常对象(比如NullPointException、OutOfMemoryError)等,还有系统类加载器。
  • 所有被同步锁(synchronized关键字)持有的对象。
  • 反应Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
  • 其他对象临时性的加入,共同构成GC Roots集合。

在HotSpot虚拟机中,GC Roots中一般为全局性引用(常量或类静态属性)和执行上下文(栈帧中的本地变量表)中;

2. JVM调优和参数配置

JVM参数类型:

  1. 标配参数(了解):

    java -version
    java -help
    java -showversion
    
  2. x参数(了解):

    -Xint # 解释执行
    -Xcomp # 第一次使用久编译成本地代码
    -Xmixed #混合模式 先编译再执行 默认
    
  3. xx参数:

    • Boolean类型

      -XX:+或者-某个属性值
      +表示开启 -表示关闭
      
      # 执行时增加PrintGCDetails参数(垃圾回收细节)
      java -jar -XX:+PrintGCDetails 某个.jar
      # 查看正在运行的Java进程,他的某个jvm参数是否配置
      jinfo -flag PrintGCDetails(参数) pid
      # 配置使用串行垃圾回收器
      -XX:+UseSerialGC
      
    • KV设置类型

      -XX:属性Key=属性值Value
      
      # 配置元空间大小
      -XX:MetaspaceSize=128m
      #Young区升到养老区的次数
      -XX:MaxTenuringThreshold=15
      # 查看全部带的参数
      jinfo -flags pid
      
    • 注意

      -Xms #等价于-XX:InitialHeapSize 默认本机内存的1/64
      -Xmx #等价于-XX:MaxHeapSize 默认本机内存的1/4
      
  4. 其余默认参数

    -XX:+PrintFlagsInitial #查看JVM初始化参数
    java -XX:+PrintFlagsInitial
    
    -XX:+PrintFlagsFinal # 查看修改更新的参数 := 带:表示被修改过的值 
    java -XX:+PrintFlagsFinal -version
    # 运行程序并打印参数
    java -XX:+PrintFlagsFinal -jar xxx.jar
    
    
    # 查看程序使用的默认JVM参数
    java -XX:+PrintCommandLineFlags -version  
    
    

3. 常用的JVM调优参数

-Xss #设置单个线程栈的大小,一般默认为512k~1024k 等价于 -XX:ThreadStackSize 若为0则使用JVM默认的值
-Xmn #设置年轻代大小
-XX:MetaspaceSize #设置元空间大小,本质和永久代类似,都是对JVM规范中方法区的实现,不过元空间与永久代之间最大的区别在于,元空间不在虚拟机中,而是使用本地内存,因此,默认情况下,元空间的大小仅受本地内存限制。默认固定就21810376byte=21M
-XX:MaxDirectMemorySize #设置直接内存(堆外内存)大小
-XX:+PrintGCDetails # 打印GC的详细日志记录

-XX:SurvivorRatio # 设置新生代eden和S0,S1空间的比例
默认-XX:SurvivorRatio=8 # Eden:S0:S1=8:1:1
若设置
-XX:SurvivorRatio=4, Eden:SO:S1=4:1:1
SurvivorRatio值就是设置Eden区的比例,S0和S1相同

-XX:NewRatio # 配置年轻代与老年代在堆结构的占比
默认 -XX:NewRatio=2 # 新生代占1,老年代占2,年轻代占整个堆的1/3
若配置
-XX:NewRatio=4 # 新生代占1,老年代占4,年轻代占整个堆的1/5
NewRatio值是老年代的占比,新生代占1

-XX:MaxTenuringThreshold # 设置垃圾最大年龄
如果设置为0的话,年轻代对象不经过Survivor区,直接进入老年代,如果将值设的偏大,则可以让对象在年轻代存活时间长;只能在[0~15]次内设置,不能设置不在区间的值;

# 通常配置的参数
-Xms128m
-Xmx4096m
-Xss1024k
-XX:MetaspaceSize=512m
-XX:+PrintCommandLineFlags
-XX:+PrintGCDetails
-XX:+UseSerialGC
# 我们项目的配置
-Dio.netty.leakDetectionLevel=PARANOID 
-agentlib:jdwp=transport=dt_socket,address=0,suspend=n,server=y #远程调试
-server #设置jvm使server模式,特点是启动速度比较慢,但运行时性能和内存管理效率很高,适用于生产环境。在具有64位能力的jdk环境下将默认启用该模式,而忽略-client参数。-client是设置jvm使用client模式,特点是启动速度比较快,但运行时性能和内存管理效率不高,通常用于客户端应用程序或者PC应用开发和调试。
-Xmx512m #最大堆内存
-XX:MaxPermSize=256m #JVM最大允许分配的非堆内存,按需分配
-Xnoclassgc #禁用类垃圾回收。此选项关闭与 JVM 不再使用的 Java™ 技术类关联的存储器的垃圾回收。缺省行为如 -Xclassgc 所定义。除非 IBM 支持团队指示,否则建议不要启用此选项。原因是该选项可能导致无限制的本机内存增长,从而导致内存耗尽错误。
-XX:+UseParNewGC #使用这个参数后会在新生代进行并行回收,老年代仍旧使用串行回收。新生代S区任然使用复制算法。操作系统是多核CPU上效果明显,单核CPU建议使用串行回收器。打印GC详情时ParNew标识着使用了ParNewGC回收器。默认关闭。
-XX:+UseConcMarkSweepGC #并发标记清除,即使用CMS收集器。它是和应用程序线程一起执行,相对于Stop The World来说虚拟机停顿时间较少。停顿减少,吞吐量会降低。它使用的是 标记清除算法,运作过程为四个步骤,分别是 初始标记—并发标识—重新标记—并发清除。它是老年代的收集算法,新生代使用ParNew收集算法。默认关闭
-XX:+UseCMSCompactAtFullCollection # Full GC后,进行一次整理,整理过程是独占的,会引起停顿时间变长。仅在使用CMS收集器时生效。
-XX:CMSFullGCsBeforeCompaction=0 #设置在执行多少次Full GC后对内存空间进行压缩整理。
-XX:+CMSClassUnloadingEnabled 
-XX:+CMSParallelRemarkEnabled 
-XX:CMSInitiatingOccupancyFraction=80 
-XX:+PrintClassHistogram 
-XX:+PrintGCDetails 
-XX:+PrintGCTimeStamps 
-XX:+PrintHeapAtGC 
-Xloggc:gc.log #gc日志记录
-jar game-logic.jar

4. 分析GC日志

[GC (Allocation Failure) [PSYoungGen: 2048K->504K(2560K)] 2048K->1034K(9728K), 0.0063607 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 2156K->512K(2560K)] 2687K->1527K(9728K), 0.0019089 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 512K->512K(2560K)] 1527K->1607K(9728K), 0.0011604 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 512K->0K(2560K)] [ParOldGen: 1095K->1279K(7168K)] 1607K->1279K(9728K), [Metaspace: 3476K->3476K(1056768K)], 0.0149073 secs] [Times: user=0.20 sys=0.00, real=0.02 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 1279K->1279K(9728K), 0.0009314 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 1279K->1252K(7168K)] 1279K->1252K(9728K), [Metaspace: 3476K->3476K(1056768K)], 0.0268015 secs] [Times: user=0.20 sys=0.00, real=0.03 secs] 
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.wooduan.hall.rpc.automation.test.TestJVM.main(TestJVM.java:11)
Heap
 PSYoungGen      total 2560K, used 207K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 10% used [0x00000000ffd00000,0x00000000ffd33e28,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
 ParOldGen       total 7168K, used 1252K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 7168K, 17% used [0x00000000ff600000,0x00000000ff739118,0x00000000ffd00000)
 Metaspace       used 3550K, capacity 4498K, committed 4864K, reserved 1056768K
  class space    used 388K, capacity 390K, committed 512K, reserved 1048576K

5. 四种引用

强引用

概念:当内存不足时,JVM开始内存回收,对于强引用的对象,就算出现了OOM也不会对该对象进行回收;

说明:最常见的一种对象引用方式,只要有强引用指向一个对象就保证对象还活着,处于可达状态,垃圾回收器是不会回收此类对象。即使该对象永远不会被使用,JVVM也不会回收,因此是造成Java内存泄漏的主要原因之一;

软引用

概念:一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference类实现,当系统内存足够时,不会被回收,当系统内存不足时才会被回收;

用途:通常在对内存使用很频繁的程序中,例如高速缓存就有用到软引用;、

场景:需要读大量的本地图片

  • 如果每次从硬盘加载会严重影响性能;
  • 如果一次全部加载会造成内存溢出;

因此这种情况可以使用软引用去解决;通过一个HashMap保存图片的路径和相应图片对象关联的引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片所咱占用的空间,有效避免了OOM(OutOfMemory)的问题;

// -Xms5m -Xmx5m -XX:+PrintGCDetails
    public static void testSoftReference(String[] args) {
        Object o = new Object();
        SoftReference<Object> softReference = new SoftReference<>(o);
        System.out.println("前:"+o);
        // 通过get获取
        System.out.println("前:"+softReference.get());
        o = null;
        try{
            byte[] bytes = new byte[5*1024*1024];
        } catch (Throwable ignore){
            System.out.println("异常");
        } finally {
            System.out.println("后:"+o); // null
            System.out.println("后:"+softReference.get()); // null 内存不够用回收
        }
    }

弱引用

概念:只要有GC回收,就被回收;

 public static void weakReference(String[] args) {
        Object o = new Object();
        WeakReference<Object> weakReference = new WeakReference<>(o);
        System.out.println("前:"+o);
        // 通过get获取
        System.out.println("前:"+weakReference.get());
        o = null;
        try{
            System.gc();
        } finally {
            System.out.println("后:"+o); // null
            System.out.println("后:"+weakReference.get()); // null 有GC就回收
        }
    }

常见应用:WeakHashMap,key对象为弱引用,当GC后回收;

public static void weakHashMap(String[] args) {
        Map<Integer, String> map = new WeakHashMap<>();
        Integer key = new Integer(1);
        map.put(key, "hh");
        System.out.println(map); // {1=hh}
        key = null;
        System.gc();
        System.out.println(map);// {}
}
// 等同于
{
   		Map<Integer, String> map = new WeakHashMap<>();
        map.put(new Integer(2), "hh");
        System.out.println(map); // {2=hh}
        System.gc();
        System.out.println(map); // {}
}

虚引用

概念:PhantomReference 不会决定对象的生命周期,如果对象仅持有虚引用,那么他和没有任何引用一样,在任何时候都可能被垃圾回收器回收,不能单独通过他访问对象,虚引用必须与引用队列ReferenceQuene一起使用;

作用:跟踪对象被垃圾回收的状态,仅仅是提供了一种确保对象被finalize后,做某些事情的机制。其get()方法总是返回null,因此无法访问对应被引用对象;在被回收后,可以通过其queue获取到被回收的引用对象;

意义:说明一个对象已经进入Finalization阶段,可以被gc回收,用来实现比finalization机制更灵活的回收操作;设置虚引用关联的唯一目的在于对这个对象被回收时收到一个系统通知或者后续添加进一步的处理。Java允许使用finalize()方法在垃圾收集器将对象从内存中清除前做必要的清理工作(类似AOP的后置通知)。

引用队列:在引用的对象Object@6d6f6e28被回收时,会将引用对象WeakReference@135fbaa4放入队列中;

 public static void referenceQueue(String[] args) {
        Object o = new Object();
        ReferenceQueue<Object> queue = new ReferenceQueue<>();
        WeakReference<Object> weakReference = new WeakReference<>(o, queue);
        System.out.println("前 对象地址: "+o); // Object@6d6f6e28
        System.out.println("前 弱引用地址: "+weakReference);// WeakReference@135fbaa4
        System.out.println("前 弱引用对象地址: "+weakReference.get());//Object@6d6f6e28
        System.out.println("前 引用队列中的地址: "+queue.poll());//null

        o = null;
        System.gc();

        System.out.println("后 对象地址: "+o);//null
        System.out.println("后 弱引用地址: "+weakReference);//WeakReference@135fbaa4
        System.out.println("后 弱引用对象地址: "+weakReference.get());// null
        System.out.println("后 引用队列中的地址: "+queue.poll());// WeakReference@135fbaa4
    }

虚引用代码:

public static void phantomReference(String[] args) {
        Object o = new Object();
        ReferenceQueue<Object> queue = new ReferenceQueue<>();
        PhantomReference<Object> phantomReference = new PhantomReference<>(o, queue);
        System.out.println("前 对象地址: "+o);// Object@6d6f6e28
        System.out.println("前 引用地址: "+phantomReference);// PhantomReference@135fbaa4
        System.out.println("前 引用的对象地址: "+phantomReference.get());// null 虚引用无法通过get获取
        System.out.println("前 引用队列元素地址: "+queue.poll());// null
        o = null;
        System.gc();
        System.out.println("后 对象地址: "+o);// Object@6d6f6e28
        System.out.println("后 引用地址: "+phantomReference);// PhantomReference@135fbaa4
        System.out.println("后 引用的对象地址: "+phantomReference.get());// null
        System.out.println("后 引用队列元素地址: "+queue.poll());// PhantomReference@135fbaa4
    }

6. 常见的JVM异常/错误

异常Throwable分为Error和Exception,错误和异常

  • StackOverflowError: 栈空间溢出错误,死循环等会抛出;

  • OutOfMemoryError:java heap space: 堆内存溢出错误;

  • OutOfMemoryError:GC overhead limit exceeded:GC回收时间过长,连续多次超过了98%的时间用来做GC并且回收了不到2%的堆内存才会抛出,如果不抛错误的后果就是GC清理的内存很快被再次填满,迫使继续GC,形成死循环,造成的结果就是CPU100%,没有回收到空间;

  • OutOfMemoryError:Direct buffer memory:直接内存不足(直接内存使用的是本地内存);由于不断分配本地内存,而堆内存使用少,因此JVM无需执行GC,DirectByteBuffer对象们不会被回收,因此,当本地空间使用完后再次分配内存就会出现该错误;

    导致的原因:

    写NIO程序时经常使用ByteBuffer来读取或写入数据,这是基于通道和缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储再Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作,这样能在一些场景中提高性能,并且避免了JAVA堆和Native堆来回复制数据;

    ByteBuffer.allocate(capability) 分配JVM堆内存,属于GC管辖范围,但需要在两个堆中拷贝数据;

    ByteBuffer.allocateDirect(capability) 分配OS本地内存,不属于GC管辖,不需要内存拷贝,速度较快;

    堆外内存通常是本机内存的1/4;

  • OutOfMemoryError:unable to create new native thread:这个异常和平台有关,由于一个进程创建的线程数超过了系统所能承载的线程数,就会报错,例如linux允许单个进程可创建的线程数是1024个(root账户无上限);遇到这种问题,要么优化代码降低线程数,要么修改系统配置,扩大linux默认限制;

    linux查看与修改线程数:

    查看:ulimit -n

    修改,重启生效:

    /etc/security/limits.d/90-nproc.conf文件尾添加

    ​ soft nproc 204800

    ​ hard nproc 204800
    /etc/security/limits.d/def.conf文件尾添加

    ​ soft nofile 204800

    ​ hard nofile 204800

  • OutOfMemoryError:Metaspace:Java8以后使用Metaspace代替永久代,他是方法区在HotSpot中的实现,他与持久代的区别就是元空间并不在JVM内存中,而是使用的是本地内存;

以上是关于2021秋招最新JAVA面试题|JVM剖析与常用的调优总结的主要内容,如果未能解决你的问题,请参考以下文章

2021最新秋招Java软件工程师面试笔试必备面试题及答案汇总

秋招已经开始准备了!Java面试题最新Java开发岗面试知识笔记

备战秋招冲击大厂Java面试题系列—JVM及垃圾回收机制

Java并发编程面试题(2021最新版)

Java并发编程面试题(2021最新版)

Java并发编程面试题(2021最新版)