JVM&GC面试题

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

为了解决引用计数法的循环引用问题,Java使用了可达性分析的方法。所谓GC roots或者tracing GC的根集合就是一组必须活跃的引用。

基本思路就是通过一系列名为GC Roots的对象作为起点,从这个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。即给定一个集合的引用作为根出发,通过引用关系遍历对象图,能被遍历到的(可达性的)对象就被判定为存活,没有被遍历到的就被判断为死亡。
在这里插入图片描述
Java中可以作为GC Roots的对象:

  • 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
  • 方法区中的类静态属性引用的对象。
  • 方法区中常量引用的对象
  • 本地方法栈中JNINative方法)引用的对象。

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

2.1 JVM参数

① 标配参数

例如:-version-helpjava -showversion
在这里插入图片描述在这里插入图片描述

② x参数(了解)

  • -Xint:解释执行
  • -Xcomp:第一次使用就编译成本地代码
  • -Xmixed:混合模式

③ xx参数

  • Boolean类型,公式:-XX+或者-某个属性值,+表示开启,-表示关闭。例:
    - 是否打印GC收集细节:-XX:-PrintGCDetails
    - 是否使用串行垃圾回收器:-XX:-UseSerialGC
  • KV设值类型,公式:-XX:属性key=属性值value。例:
    - 设置元空间内存:-XX:MetaspaceSize=128m
    - 设置最大生命周期:-XX:MaxTenuringThreadhold=15
    - -XX:InitialHeapSize等价-Xms
    - -XX:MaxHeapSize等价-Xmx

2.2 查看JVM默认值

使用jinfo查看当前运行程序的配置:jinfo -flag 具体参数 进程号
在这里插入图片描述
查看JVM参数:

  • -XX:+PrintFlagsInitial:主要查看初始默认JVM配置
  • -XX:+PrintFlagsFinal:主要查看修改更新JVM配置
  • -XX:+PrintCommandLineFlags:查看程序使用的默认JVM参数

2.3 JVM常用基本配置参数

  • -Xms:初始大小内存,默认为物理内存1/64,等价于-XX:InitialHeapSize
  • -Xmx:最大分配内存,默认为物理内存的1/4,等价于-XX:MaxHeapSize
  • -Xss:设置单个线程栈的大小,一般默认为512K~1024K
  • -Xmn:设置年轻代大小
  • -XX:MetaspaceSize:设置元空间大小
  • -XX:SurvivorRatio:设置新生代中edenfrom/to空间的比例,默认为8,即比例为8:1:1
  • -XX:NewRatio:设置年轻代与老年代在堆结构的占比,默认值为2,即年轻代站整个堆的1/3
  • -XX:MaxTenuringThreshold: 设置垃圾最大年龄,默认为15

重点:-XX:+PrintGCDetails
在这里插入图片描述
具体参数解释:
在这里插入图片描述
在这里插入图片描述

三、强引用、软引用、弱引用、虚引用分别是什么?

① 强引用

当内存不足,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死都不收。

强引用是最常见的普通对象引用,只要还有前引用指向一个对象,就能表明对象还活着,垃圾收集器不会碰到这种对象。在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾机制回收的,即使该对象以后永远都不能被用到,JVM也不会回收。因此强引用是造成Java内存泄露的主要原因之一。

对弈一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显示地将相应(强)引用赋值为null,一般认为就是可以被垃圾收集的了(当然具体回收时还要看垃圾收集策略)。

案例:

public class StrongReferenceTest {
    public static void main(String[] args) {
        Object obj1 = new Object();
        Object obj2 = obj1;
        obj1 = null;
        System.gc();
        System.out.println("obj1 = " + obj1);//obj1 = null
        System.out.println("obj2 = " + obj2);//obj2 = java.lang.Object@1b6d3586
    }
}

② 软引用

软引用是一种相对强化了一些的引用,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。

对于只有软引用的对象来说,当系统内存充足时,它不会被回收;当系统内存不足时,它会被回收。软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收。

内存足时案例:

public class SoftReferenceEnounghTest {
    public static void main(String[] args){
        Object o1 = new Object();
        SoftReference<Object> softReference = new SoftReference<>(o1);
        System.out.println(o1);//java.lang.Object@1b6d3586
        System.out.println(softReference.get());//java.lang.Object@1b6d3586

        o1 = null;
        System.gc();
        System.out.println(o1);//null
        System.out.println(softReference.get());//java.lang.Object@1b6d3586
    }
}

内存不足时案例:

public class SoftReferenceNotEnounghTest {
	//设置-Xmx24m
    public static void main(String[] args){
        Object o1 = new Object();
        SoftReference<Object> softReference = new SoftReference<>(o1);
        System.out.println(o1);
        System.out.println(softReference.get());

        o1 = null;

        try{
            byte[] bytes = new byte[30*1024*1024];
        }catch (Throwable e){
            e.printStackTrace();
        }finally {
            System.out.println(o1);
            System.out.println(softReference.get());
        }
    }
}

打印如下:
在这里插入图片描述

软引用的应用:假如有一个应用需要读取大量的本地图片:如果每次读取图片都从硬盘读取则会严重影响性能。如果一次性全部加载到内存中有可能造成内存泄露。此时使用软引用可以解决这个问题。
Map<String,SoftReference> imageCache = new HashMap<String,SoftReference>();

③ 弱引用

弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短。对于软引用对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。

public class WeakReferenceTest {
    public static void main(String[] args) {
        Object o1 = new Object();
        WeakReference<Object> weakReference = new WeakReference<>(o1);
        System.out.println(o1);//java.lang.Object@1b6d3586
        System.out.println(weakReference.get());//java.lang.Object@1b6d3586

        o1 = null;
        System.gc();
        System.out.println(o1);//null
        System.out.println(weakReference.get());//null
    }
}

WeakHashMap案例:

public class WeakHashMapTest {
    public static void main(String[] args){
        myHashMap();
        System.out.println("========");
        myWeakHashMap();
    }

    private static void myHashMap(){
        HashMap<Integer,String> map = new HashMap<>();
        Integer key = new Integer(1);
        String value = "HashMap";

        map.put(key,value);
        System.out.println(map);//{1=HashMap}

        key = null;
        System.out.println(map);//{1=HashMap}

        System.gc();
        System.out.println(map);//{1=HashMap}
    }

    private static void myWeakHashMap(){
        WeakHashMap<Integer,String> map = new WeakHashMap<>();
        Integer key = new Integer(2);
        String value = "WeakHashMap";

        map.put(key,value);
        System.out.println(map);//{2=WeakHashMap}

        key = null;
        System.out.println(map);//{2=WeakHashMap}

        System.gc();
        System.out.println(map);//{}
    }
}

④ 虚引用

虚引用需要java.lang.refPhantonReference类来实现。与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和队列(ReferenceQueue)联合使用。

四、请谈谈你对OOM的认识?

4.1 java.lang.StackOverFlowError

通常栈空间的大小为512K~1024K。StackOverFlowError案例:

public class StackOverflowErrorTest {
    public static void main(String[] args) {
        StackOverflowError();
    }
    private static void StackOverflowError(){
        StackOverflowError();
    }
}

在这里插入图片描述

4.2 java.lang.OutOfMemoryError:Java heap space

堆内存空间不足

public class OOMHeapSpaceTest {
    public static void main(String[] args) {
        //-Xmx8m -Xms8m
        byte[] ints = new byte[10  * 1024 * 1024];
    }
}

在这里插入图片描述

4.3 java.lang.OutOfMemoryError:GC overhead limit exceeded

GC回收时间长时会抛出java.lang.OutOfMemoryError:GC overhead limit exceeded。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存,连续多次GC都只回收了不到2%的极端情况下才会抛出。

假设不抛出GC overhead limit错误会发生什么情况呢?那就是GC清理的这么点内存很快会再次填满,迫使GC再次执行,这样就形成恶性循环,CPU使用率一直是100%,而GC缺没有任何成果。

-XX:MaxDirectMemorySize来设置到达指定大小后,即触发Full GC。注意该值是有上限的,默认是64M

public class OOMGCOverhead {
    public static void main(String[] args) {
        int i = 0;
        List<String> list = new ArrayList<>();
        try {
            while (true) {
                list.add(String.valueOf(++i));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

4.4 java.lang.OutOfMemoryError:Direct buffer memory

导致原因:写NIO程序经常使用ByteBuffer来读取或者写入数据,这是一种基于通道(Channel)与缓冲区(Buffer)的IO方式。 它可以使Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著的提高性能,因为避免了Java堆和Native堆中来回复制数据。

  • ByteBuffer.allocate(capabiltity):第一种方式是分配JVM堆内存,属于GC管辖方位,由于需要拷贝所以速度相对较慢
  • ByteBuffer.allocateDirect(capability):第二种方式分配OS本地内存,不属于GC管辖范围,由于不需要内存拷贝所以速度较快

但如果不断分配本地内存,堆内存很少使用,那么JVM就不需要GCDirectByteBuffer对象就不会被回收。这时候堆内存充足,但本地内存可能已经使用完了,再次尝试分配本内存就会出现OutOfMemoryError,那程序就会崩溃

public class DirectBufferMemoryTest {
    public static void main(String[] args) {
        //-XX:MaxDirectMemorySize=5m
        System.out.println("配置的maxDirect:"+(sun.misc.VM.maxDirectMemory()/(double)1024/1024)+" MB");
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6 * 1024 * 1024);
    }
}

在这里插入图片描述

4.5 java.langOutOfMemoryError:Metaspace

元空间不足,使用java -XX:+PrintFlagsInitial命令查看本机的初始化参数,-XX:Metaspacesize为218103768(大约为20.8M)

4.6 java.lang.OutOfMemoryError:unable to create new native thread

高并发请求服务器时,经常出现如下异常:java.lang.OutOfMemoryError:unbale to create new native thread,准确的将该native thread异常与对应的平台有关。

导致原因:

  • 应用创建了太对线程,一个应用进程创建多个线程,超过系统承载极限;
  • 服务器并不允许应用程序创建那么多线程,linux系统默认允许单个进程可以创建的线程数是1024个,如果应用创建超过这个数量,就会报java.lang.OutOfMemoryError:unable to create new native thread

解决办法: 想办法降低应用程序创建线程的数量,分析应用是否真的需要创建那么多线程,如果不是,改代码将线程数降到最低。对于有的应用,确实需要创建多个线程,远超过linux系统默认的1024个线程的限制,可以通过修改linux服务器配置,扩大linux默认限制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

HuCheng1997

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

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

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

打赏作者

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

抵扣说明:

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

余额充值