JVM(九)--关于一些常见的面试题

JVM(九)–关于一些常见的面试题

1,GC什么时候开始?

minor gc

新生代分为一个eden区还有两个survivor,每次使用的时候只使用eden和survivor,当这两个空间存满了以后,就会触发minor gc,把仍然存活的放到另一个survivor中。当survivor中的对象每熬过一次gc就会加1,当年龄达到某个值时就会变为老年代。

major gc

老年代满了以后,会触发major gc。

担保机制在发生minor gc之前,虚拟机都会先检查老年代最大的可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,则minor gc就是安全的。若不成立,则虚拟机会查看HandlePromotionFailure设置是否允担保失败

2,jvm进程所占的虚存>虚拟机的堆栈设置参数,为什么不报错?

推测答案:堆栈参数设置是堆栈的临界值,但jvm进程占用虚拟内存的大小包括的范围不止堆栈,还有元空间,元空间实际占用的是本机的内存,不会受到堆栈参数的限制,若虚拟内存过大是元空间造成的(且没有超过元空间的限制)而不是堆内存过大造成的,就可能导致虚拟内存>堆栈参数,但是进程并不会报错。

3,什么情况下使用堆外内存?

  • 堆外内存适用于生命周期中等或较长的对象。(如果是生命周期较短的对象,在YGC的时候就被回收了,就不存在大内存且生命周期较长的对象在FGC对应用造成的性能影响 )。
  • 直接的文件拷贝操作,或者I/O操作。直接使用堆外内存就能少去内存从用户内存拷贝到系统内存的操作,因为I/O操作是系统内核内存和设备间的通信,而不是通过程序直接和外设通信的。
  • 同时,还可以使用 池+堆外内存 的组合方式,来对生命周期较短,但涉及到I/O操作的对象进行堆外内存的再使用。( Netty中就使用了该方式 )

4,堆外内存如何被回收?

看名字就知道堆外内存与堆内内存是相对应的:Java 虚拟机管理堆之外的内存,称为非堆内存,即堆外内存。

换句话说:堆外内存就是把内存对象分配在Java虚拟机的堆以外的内存,这些内存直接受操作系统管理(而不是虚拟机),这样做的结果就是能够在一定程度上减少垃圾回收对应用程序造成的影响

使用堆外内存的好处主要有以下两个:

  • 避免堆内内存Full GC造成的stop-the-world延迟,当然也可以降低OOM风险;
  • 绕过用户态到内核态的切换,实现高效数据读写,如零拷贝和内存映射。

分配堆外内存:

我们通过调用ByteBuffer.allocateDirect()方法分配堆外内存

  public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
  }

每个DirectByteBuffer对象在初始化时,都会创建一个对应的Cleaner对象,这个Cleaner对象会在合适的时候执行unsafe.freeMemory(address),从而回收这块堆外内存

当初始化一块堆外内存时,对象的引用关系如下:

在这里插入图片描述
其中first是Cleaner类的静态变量,Cleaner对象在初始化时会被添加到Clener链表中,和first形成引用关系,ReferenceQueue是用来保存需要回收的Cleaner对象。

如果该DirectByteBuffer对象在一次GC中被回收了,即:

在这里插入图片描述
此时,只有Cleaner对象唯一保存了堆外内存的数据(开始地址、大小和容量),在下一次FGC时,把该Cleaner对象放入到ReferenceQueue中(因为它本质上是个虚引用(Phantom Reference)),并触发clean方法

在这里插入图片描述

Cleaner对象的clean方法主要有两个作用

  • 把自身从Cleaner链表删除,从而在下次GC时能够被回收
  • 释放堆外内存
public void run() {
    if (address == 0) {
        // Paranoia
        return;
    }
    unsafe.freeMemory(address);
    address = 0;
    Bits.unreserveMemory(size, capacity);
}

Cleaner类的实现非常聪明,除了使用虚引用之外,还有一点:使用双向链表维护所有Cleaner,防止Cleaner本身在它们对应的DirectByteBuffer之前被回收。而传入的Runnable就相当于回调函数,在Cleaner被GC掉时调用,因此在Deallocator.run()方法中释放掉堆外内存,就可以随着DirectByteBuffer的清理而清理了

看到这里,可能有人会想,如果JVM一直没有执行FGC的话,无效的Cleaner对象就无法放入到ReferenceQueue中,从而堆外内存也一直得不到释放,无效内存就会很大,那怎么办?

这个倒不用担心,那些大神们当然早就考虑到这一种情况了。

其实,在初始化DirectByteBuffer对象时,会自动去判断,如果堆外内存的环境很友好,那么就申请堆外内存;如果当前堆外内存的条件很苛刻时(即有很多无效内存没有得到释放),这时候就会主动调用System.gc()强制执行FGC,从而释放那些无效内存

为了避免这种悲剧的发生,也可以通过-XX:MaxDirectMemorySize来指定最大的堆外内存大小,当使用达到了阈值的时候将调用System.gc来做一次full gc,以此来回收掉没有被使用的堆外内存

这里参考:

https://blog.csdn.net/Searchin_R/article/details/84570021

https://blog.csdn.net/nazeniwaresakini/article/details/104220245?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

这里在附上几个相关的链接:

https://blog.csdn.net/qfc8930858/article/details/90110922(jvm面试)
https://www.javazhiyin.com/61559.html(超长JVM总结,面试必备)
https://www.javazhiyin.com/63410.html(谈谈面试必问的Java内存区域(运行时数据区域)和内存模型(JMM))

感谢并参考:

https://blog.csdn.net/Searchin_R/article/details/84570021

https://blog.csdn.net/nazeniwaresakini/article/details/104220245?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值