JVM面试真题总结(九)

文章收录在网站:http://hardyfish.top/

文章收录在网站:http://hardyfish.top/

文章收录在网站:http://hardyfish.top/

文章收录在网站:http://hardyfish.top/

在这里插入图片描述

描述CMS垃圾收集的工作过程

CMS(Concurrent Mark Sweep)垃圾收集器的工作过程

主要可以分为以下四个阶段:

初始标记(Initial Mark)

  • 这个阶段的目标是标记所有的 GC Roots 能直接关联到的对象。
  • 这个阶段需要停止所有的用户线程,但是一般情况下,这个阶段会很快完成。

并发标记(Concurrent Mark)

  • 在这个阶段,垃圾收集器会遍历对象图,并标记所有的存活对象,从初始标记的对象开始。
  • 这个阶段是与用户线程并发执行的,不需要停止用户线程。

重新标记(Remark)

  • 因为在并发标记阶段,用户线程还在运行,可能会修改了对象图,所以需要重新标记一次,以确保标记的准确性。
  • 这个阶段需要停止所有的用户线程。

并发清除(Concurrent Sweep)

  • 在这个阶段,垃圾收集器会清除所有未被标记的对象。
  • 这个阶段是与用户线程并发执行的,不需要停止用户线程。

如何防止内存泄漏?

防止内存泄漏主要需要对代码进行精细的管理和控制,以下是一些常用的方法:

及时释放对象:

  • 当对象不再使用时,及时将其引用设置为null,以便让垃圾回收器能够回收。

使用弱引用(WeakReference):

  • 在Java中,使用WeakReference可以在不影响垃圾回收器回收对象的同时,还能使用到这个对象。
  • 当该对象只剩下弱引用时,垃圾回收器会回收它。

避免使用静态变量:

  • 静态变量会在类加载时初始化,并在整个程序运行期间都存在,容易造成内存泄漏。
  • 如果非要使用,应确保在不需要时将其设置为null

及时关闭流、数据库连接等资源:

  • 这些资源都是有限的,使用完后必须及时关闭,否则会造成资源泄漏。

使用finally块:

  • 在Java中,finally块始终会被执行,无论是否有异常发生。
  • 因此,可以在finally块中释放资源。

避免在对象中保存过多的数据:

  • 如果一个对象保存了大量的数据,那么当这个对象被其他对象引用时,会消耗大量的内存。

使用内存分析工具:

  • 内存分析工具(如MAT,VisualVM等)可以帮助你找出内存泄漏的来源,从而更好地修复问题。

对集合类进行适当的管理:

  • 对于集合类,如List,Map等,应该避免让它们过大,并且在不再使用它们时,应该清空或者设置为null

注意线程的使用:

  • 如果线程对象得不到及时的销毁,也会造成内存泄漏。
  • 因此,对于线程对象,我们要特别小心,确保其生命周期得到良好的管理。

简述内存屏障及其类型

内存屏障(Memory Barrier),也被称为内存栅栏,是一种处理器指令

  • 用于防止特定操作的重排序。

它可以确保某些内存操作的顺序性,以及它们对其他处理器可见的顺序。

内存屏障是处理器设计和多线程编程中的重要概念

  • 它是实现诸如volatile,synchronized等高级同步构造的基础。

内存屏障主要有以下四种类型:

LoadLoad屏障:

  • 这种屏障确保了屏障之前的所有Load操作在屏障之后的Load操作之前完成。
    • 即不允许Load操作的重排序。

StoreStore屏障:

  • 这种屏障确保了屏障之前的所有Store操作在屏障之后的Store操作之前完成。
    • 即不允许Store操作的重排序。

LoadStore屏障:

  • 这种屏障确保了屏障之前的所有Load操作在屏障之后的Store操作之前完成。

StoreLoad屏障:

  • 这种屏障确保了屏障之前的所有Store操作在屏障之后的Load操作之前完成。
  • 这是最强的一种内存屏障,也是开销最大的一种。

在Java中,volatile关键字和synchronized关键字的实现就使用了内存屏障。

例如,对volatile变量的写操作会插入StoreStore和StoreLoad屏障

  • 读操作会插入LoadLoad和LoadStore屏障。

而synchronized关键字在锁定和解锁时也会插入相应的内存屏障,以确保操作的顺序性和可见性。

哪些情况会导致栈内存溢出?

栈内存溢出一般发生在递归调用且递归深度过深的场景。

  • 当一个线程请求的栈深度大于JVM所允许的深度,将抛出StackOverflowError异常。

栈内存主要用于存储局部变量和执行动态链接,还用于方法调用和返回。

  • 每次方法调用都会创建一个新的栈帧,这个栈帧会被添加到线程的栈顶。
  • 如果这个方法调用其他方法,那么新的栈帧会继续被添加到栈顶。
    • 当方法调用完成,相应的栈帧会被弹出栈。

每个线程都有一个私有的JVM栈,其大小可以固定也可以动态扩展。

  • 如果固定大小的栈满了,或者动态扩展的栈无法继续扩展,那么JVM就会抛出StackOverflowError

例如,如果你写了一个递归函数,没有提供适当的递归出口,那么这个函数就会无限递归下去

  • 每次递归都会向栈添加一个新的栈帧,最终导致栈内存溢出。
public void recursive() {
    recursive();
}

以上面的代码为例,这个方法会不断地调用自己,每次调用都会创建一个新的栈帧并压入栈中

  • 但是没有任何方法可以弹出栈帧,因此最终会导致栈内存溢出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值