你需要明白的JVM相关问题

1、OOM有碰到过么?怎么排查处理的?

OOM(Out of Memory)是指Java应用程序因为无法再分配所需的内存而失败的情况。这种情况通常发生在应用程序请求的内存超出了Java虚拟机(JVM)可用的内存限制,导致JVM无法再分配更多的内存空间。

排查和处理OOM的步骤:

  1. 确认OOM异常

    • 首先,检查应用程序的日志或控制台输出,确认是否出现了OOM相关的异常信息,通常是 java.lang.OutOfMemoryError
  2. 分析OOM的类型

    • Heap Space: 如果是堆内存空间不足导致的OOM,通常会提示 java.lang.OutOfMemoryError: Java heap space
    • PermGen/Metaspace: 在旧版Java中,可能出现 java.lang.OutOfMemoryError: PermGen space 或者在新版Java中是 java.lang.OutOfMemoryError: Metaspace,表示永久代(PermGen)或元空间(Metaspace)的内存不足。
  3. 查看堆内存使用情况

    • 使用监控工具(如VisualVM、JConsole、Java Mission Control等)查看应用程序的堆内存使用情况。特别注意内存的分配情况和垃圾收集器的活动。确认是不是堆内存过大或者对象生命周期管理不当导致的内存泄漏。
  4. 分析堆转储文件(Heap Dump)

    • 当OOM发生时,可以配置JVM生成堆转储文件。使用工具如Eclipse Memory Analyzer(MAT)来分析这些堆转储文件,定位哪些对象占用了大量内存或者发现了内存泄漏。
  5. 检查代码和内存使用

    • 审查应用程序的代码,特别是检查是否存在内存泄漏或者不恰当的对象引用。例如,未关闭的资源、长期持有的对象引用等情况都可能导致内存不断增长。
    • 确保及时释放不再使用的对象或资源,避免创建过多临时对象。
  6. 优化JVM参数和配置

    • 根据应用程序的特性和实际需求,调整JVM的堆大小、永久代大小(如果使用旧版Java)、垃圾收集器算法等参数。合理配置这些参数可以减少OOM的发生频率。
  7. 增加物理内存或升级硬件

    • 如果应用程序的内存需求真的超过了现有硬件的限制,可以考虑增加物理内存或者升级服务器硬件来满足更高的内存需求。
  8. 监控和预防

    • 实施监控和预警机制,及时发现和处理内存问题。定期审查和优化应用程序的内存使用,预防OOM的发生。

通过以上排查和处理步骤,可以有效地定位和解决Java应用程序中的OOM问题,提升应用程序的稳定性和性能。

结合自身遇到过的实际场景,补充案例。

1、说说内存溢出跟内存泄漏的区别?

  1. 内存泄露:申请的内存空间没有被正确释放,导致内存被白白占用。
  2. 内存溢出:申请的内存超过了可用内存,内存不够了。可能是泄漏导致的。

1、StackOverFlow碰到过么?什么原因?怎么处理的?

StackOverflow错误(不是指技术问答网站)通常是指在递归调用过程中栈空间耗尽导致的错误,而不是Java虚拟机的Out of Memory错误(OOM)。这种错误通常由于递归调用层次太深或者每次递归调用创建的栈帧过大导致。

原因:
  1. 递归调用深度过大:如果递归调用没有终止条件,或者递归调用的层次非常深,每次递归调用都会在栈中创建一个新的栈帧,导致栈空间很快耗尽。

  2. 每个方法调用创建的栈帧过大:某些情况下,即使递归调用的深度并不是特别大,但每次方法调用创建的栈帧过大也可能导致栈空间溢出。

处理方法:
  1. 优化递归算法

    • 确保递归调用有明确的终止条件,避免无限递归。
    • 考虑是否可以将递归算法改写为迭代算法,迭代通常不会涉及栈空间的增长问题。
  2. 增加栈空间

    • 对于JVM来说,可以通过调整 -Xss 参数增大栈空间大小(默认是1MB),但这通常是一种治标不治本的做法,不推荐长期使用,因为递归深度仍然可能超出新的限制。
  3. 尾递归优化

    • 尾递归是一种特殊的递归形式,可以被优化为迭代调用,从而不会增加栈深度。Java并没有内置尾递归优化,但可以手动将递归改写为迭代形式。
  4. 代码审查和优化

    • 审查代码中的递归调用,确保每次递归调用的栈帧大小合理,并避免不必要的大内存分配或操作。
  5. 使用数据结构

    • 在某些情况下,可以考虑使用栈(Stack)或者其他数据结构替代递归调用,这样可以避免栈空间溢出问题。

处理StackOverflow错误需要理解其产生的原因,并通过合适的方法来解决。一般来说,避免过深的递归调用和过大的栈帧是预防此类问题的关键。

结合自身遇到过的实际场景,补充案例。

2、如何判断对象仍然存活?jvm是怎么判断某个对象是不是垃圾的?

两种方式:引用计数算法、可达性分析算法

引用计数算法:

引用计数器:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;
当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。
可达性分析算法:

将一系列 GC Roots 作为初始的存活对象合集(Gc Root Set),然后从该合集出发,探索所有能够被该集合引用到的对象,并将其加入到该集合中,
这个过程我们也称之为标记(mark)。最终,未被探索到的对象便是死亡的,是可以回收的。

3、GC Roots 有哪些?

「固定的GC Roots:」

  • 1.在「「虚拟机栈(栈帧的本地变量表)中所引用的对象」」,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
  • 在方法区中「类静态属性引用的对象」,譬如 Java 类的「引用静态变量」
  • 在方法区中「「常量引用的对象」」,譬如字符串常量池中的引用。
  • 在方法区栈中 「「JNI (譬如 Native 方法)引用的对象」」
  • Java 「「虚拟机内部的引用」」,如基本数据类型对应的 Class 对象,一些常驻的异常对象(空指针异常、OOM等),还有类加载器。
  • 所有「「被 Synchronized 持有的对象」」。
  • 反应 Java 虚拟机内部情况的 「「JMXBean、JVMTI 中注册的回调本地代码缓存等」」

「临时GC Roots:」

  • 「为什么会有临时的 GC Roots ?」」:目前的垃圾回收大部分都是「「分代收集和局部回收」」,如果只针对某一部分区域进行局部回收,那么就必须要考虑的「「当前区域的对象有可能正被其他区域的对象所引用」」,这时候就要将这部分关联的对象也添加到 GC Roots 中去来确保根可达算法的准确性。这种算法是利用了「「逆向思维」」,找到使用的对象,剩下的就是垃圾,也被称为"间接垃圾收集"。

4、什么时候触发Full GC?

一共有6种触发条件:

  • System.gc()等命令触发:System.gc()、jmap -dump 等命令会触发 full gc。
  • Young GC 之前检查老年代:在要进行 Young GC 的时候,发现老年代可用的连续内存空间 < 新生代历次Young GC后升入老年代的对象总和的平均大小,说明本次 Young GC 后可能升入老年代的对象大小,可能超过了老年代当前可用内存空间,那就会触发 Full GC。
  • Young GC 之后老年代空间不足:执行 Young GC 之后有一批对象需要放入老年代,此时老年代就是没有足够的内存空间存放这些对象了,此时必须立即触发一次 Full GC
  • 老年代空间不足,老年代内存使用率过高,达到一定比例,也会触发 Full GC。
  • 空间分配担保失败( Promotion Failure),新生代的 To 区放不下从 Eden 和 From 拷贝过来对象,或者新生代对象 GC 年龄到达阈值需要晋升这两种情况,老年代如果放不下的话都会触发 Full GC。
  • 方法区内存空间不足:如果方法区由永久代实现,永久代空间不足 Full GC。

5、对象什么时候会进入老年代?

一共有四种情况:

  1. 长期存活的对象将进入老年代:在对象的对象头信息中存储着对象的迭代年龄,迭代年龄会在每次 YoungGC 之后对象的移区操作中增加,每一次移区年龄加一.当这个年龄达到 15(默认)之后,这个对象将会被移入老年代。#可以通过这个参数设置这个年龄值。- XX:MaxTenuringThreshold
  2. 大对象直接进入老年代:有一些占用大量连续内存空间的对象在被加载就会直接进入老年代.这样的大对象一般是一些数组,长字符串之类的对。#HotSpot 虚拟机提供了这个参数来设置。-XX:PretenureSizeThreshold
  3. 动态对象年龄判定:HotSpot 虚拟机并不是永远要求对象的年龄必须达到- XX:MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代
  4. 空间分配担保:假如在 Young GC 之后,新生代仍然有大量对象存活,就需要老年代进行分配担保,把 Survivor 无法容纳的对象直接送入老年代。

6、CMS垃圾回收器的缺点有哪些?

优点:CMS 最主要的优点在名字上已经体现出来——并发收集、低停顿。

缺点:CMS 同样有三个明显的缺点。

  • Mark Sweep(标记-清除) 算法会导致内存碎片比较多
  • CMS 的并发能力比较依赖于 CPU 资源,并发回收时垃圾收集线程可能会抢占用户线程的资源,导致用户程序性能下降。
  • 并发清除阶段,用户线程依然在运行,会产生所谓的理“浮动垃圾”(Floating Garbage),本次垃圾收集无法处理浮动垃圾,必须到下一次垃圾收集才能处理。如果浮动垃圾太多,会触发新的垃圾回收,导致性能降低。

G1 主要解决了内存碎片过多的问题。
 

7、什么是双亲委派?为什么要这样?

双亲委派模型的工作过程:

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类
而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此
因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时 ,子加载器才会尝试自己去完成加载。

为什么这样?

答案是为了保证应用程序的稳定有序。保证安全。

例如类 java.lang.Object,它存放在 rt.jar 之中,通过双亲委派机制,保证最终都是委派给处于模型最顶端的启动类加载器进行加载,保证 Object 的一致。反之,都由各个类加载器自行去加载的话,如果用户自己也编写了一个名为 java.lang.Object 的类,并放在程序的 ClassPath 中,那系统中就会出现多个不同的 Object 类。

8、如何打破双亲委派?什么时候有用?

如果想打破双亲委派模型则需要重写 loadClass()方法。

如何打破双亲委派模式?

  1. 继承重写(Override Inheritance):可以通过创建一个新的子类来覆盖父类的方法或属性。这种方法允许你修改或扩展原有的行为。但需要注意,这可能会导致你需要处理更多的细节,特别是对于继承链的管理和代码的复杂性。

  2. 装饰器模式(Decorator Pattern):使用装饰器模式可以在运行时动态地扩展一个对象的功能。通过添加装饰器,你可以在不修改原有对象的情况下,为其增加新的行为或修改现有行为。

  3. 事件监听(Event Listening):有些情况下,你可以通过注册事件监听器来捕获和处理对象的特定行为。这种方式通常用于需要在对象行为发生时采取一些特定动作的场景。

  4. 依赖注入(Dependency Injection):在某些情况下,通过依赖注入的方式可以替代双亲委派模式。依赖注入允许你在运行时动态地改变对象的依赖关系,从而实现定制化的行为。

什么时候有用?

打破双亲委派模式通常在以下情况下是有用的:

  • 定制化需求:当你需要根据具体的需求修改或扩展现有的功能时,打破双亲委派模式可以让你更灵活地实现这些定制化需求。

  • 不同的业务逻辑:如果不同的部分需要不同的行为或处理方式,你可能需要打破双亲委派模式以便针对不同的情况提供不同的实现。

  • 性能优化:有时候,通过打破双亲委派模式可以实现更高效或更优化的实现方式,特别是在需要处理大量数据或频繁调用的情况下。

总之,打破双亲委派模式通常是为了实现更灵活、更定制化的软件设计,以满足特定的业务需求或性能优化要求。

9、为什么内存超过32G的64位JVM开启指针压缩优化失败?

在Java虚拟机(JVM)中,指针压缩(Pointer Compression)是一种优化技术,它可以减少指针的大小,从而在一定程度上降低内存占用。指针压缩通常在32位JVM上启用,因为32位系统的寻址空间有限,指针占用的内存较大。但在64位JVM上,由于寻址空间非常广阔(2的64次方),指针的大小本身不再是内存占用的瓶颈,因此默认情况下,64位JVM是不启用指针压缩的。

然而,即使在64位JVM上,有时也可以选择手动启用指针压缩。指针压缩的启用取决于几个因素:

  1. 堆内存的大小限制:在32位JVM中,由于地址空间限制,Java堆的大小通常受到4GB的限制。在64位JVM中,由于寻址空间更大,理论上可以支持非常大的堆内存,超过32GB。

  2. 启用指针压缩的条件:在64位JVM中,如果你希望启用指针压缩,需要考虑JVM实现是否支持,并且需要确保压缩后的指针可以正确映射到有效的地址范围内。通常,指针压缩的有效性受到堆内存大小和对象的内存分配模式的影响。

  1. 默认情况下的行为

    • 64位JVM默认情况下不启用指针压缩,因为64位地址空间很大,指针大小本身不是内存占用的主要限制因素。
  2. 手动启用指针压缩

    • 在一些特殊情况下,你可以手动启用指针压缩来降低内存占用。这通常需要通过JVM启动参数来完成。
  3. 常见的启动参数

    • -XX:+UseCompressedOops:这是用于启用指针压缩的参数。Oops是Java HotSpot VM中对“ordinary object pointers”的简称。
    • -XX:+UseCompressedClassPointers:这是用于启用类指针压缩的参数。当启用该选项时,JVM会尝试将类指针进行压缩,以节省更多内存。
  4. 启用示例

    • 若要启用指针压缩,可以在JVM启动命令中添加如下参数:
      java -XX:+UseCompressedOops -XX:+UseCompressedClassPointers YourMainClass
    • 这将使JVM在运行时尝试使用压缩指针和类指针的技术来优化内存使用。

为什么呢?

我们来算一下,既然指针压缩到了4byte,也就是32bit,同样按照之前算的,用排列组合的方式可以识别2^32个对象,也就是4G个对象,刚才同样也说了,在Java中,非简单对象都是必须以8byte对齐。

因此,其能够识别的最大内存就是4G*8byte=32GB

这也是为什么很多Java服务在运行中,官方都建议单个运行实例的内存设置不要超过32GB的根本原因。典型的如Elasticsearch,很多资料都说设置JVM大小不要超过32G,但是很少有提到为什么。

10、类加载过程是什么样的?

关于JVM必备的一些知识-CSDN博客

11、jvm内存区域有哪些?分别的作用是什么?

关于JVM必备的一些知识-CSDN博客

堆内存结构:

12、jvm内存分配过程中的TLAB有什么用?

TLAB 是 Java 虚拟机(JVM)中的 "Thread-Local Allocation Buffer" 的缩写,即线程本地分配缓冲区。TLAB 是为了优化对象分配和提高程序性能而引入的概念。

在 Java 中,每当需要分配新的对象时,JVM 通常会使用堆内存来进行分配。为了减少线程竞争和提高分配效率,JVM 会为每个线程预分配一个 TLAB。每个线程都有自己的 TLAB,这样在对象分配时,线程可以直接在自己的 TLAB 上分配内存,而不需要每次都去竞争堆上的全局锁。这种设计有效地减少了多线程环境下的内存分配的竞争和同步开销,从而提高了并发程序的性能。

总结一下,TLAB 是 JVM 中的线程本地分配缓冲区,用于提高对象分配的效率和降低竞争。

13、逃逸分析跟栈上分配是什么?有什么用?

逃逸分析(Escape Analysis)和栈上分配(Stack Allocation)是 Java 虚拟机优化技术的一部分,主要用于提升程序的性能和内存利用率。

  1. 逃逸分析(Escape Analysis)

    • 定义:逃逸分析是指编译器分析对象的动态作用域,即对象的引用是否逃出了方法的作用域。如果对象的引用没有逃出方法的作用域,编译器可以安全地将其分配在栈上,而不是在堆上。逃逸分析的主要目标是识别哪些对象可以在方法栈帧上分配,从而避免频繁的堆内存分配和垃圾回收操作。
    • 用途:逃逸分析可以显著提升程序的性能,尤其是在多线程环境下。如果对象被分配在栈上,它们的访问速度更快,因为栈上分配不需要加锁或者同步操作,而且这些对象的生命周期仅限于方法执行期间,减少了垃圾收集器的压力。
  2. 栈上分配(Stack Allocation)

    • 定义:栈上分配是指将对象分配到当前线程的方法栈帧上,而不是分配到堆上。这些对象的生命周期与方法的生命周期相同,当方法返回时,它们自动被销毁,无需进行垃圾回收。
    • 用途:栈上分配可以显著减少垃圾收集器的负担,因为这些对象不需要进行垃圾回收。同时,栈上分配还可以提高程序的执行速度,因为在方法栈帧上分配对象比在堆上分配对象更快速,不需要进行复杂的内存管理操作。

综上所述,逃逸分析和栈上分配都是优化技术,通过减少对象的堆分配和垃圾回收操作来提升程序的性能和响应速度。逃逸分析用于确定对象的引用是否会逃出方法作用域,以便安全地进行栈上分配,从而达到优化的目的。

14、Java对象的内存布局有了解吗?

对象内存布局

  • 「1.对象头」: 对象头又分为 「MarkWord」 和 「Class Pointer」 两部分。
    • 「MarkWord」:包含一系列的标记位,比如轻量级锁的标记位,偏向锁标记位,gc记录信息等等。
    • 「ClassPointer」:用来指向对象对应的 Class 对象(其对应的元数据对象)的内存地址。在 32 位系统占 4 字节,在 64 位系统中占 8 字节。
  • 「2.Length」:只在数组对象中存在,用来记录数组的长度,占用 4 字节
  • 「3.Instance data」: 对象实际数据,对象实际数据包括了对象的所有成员变量,其大小由各个成员变量的大小决定。(这里不包括静态成员变量,因为其是在方法区维护的)
  • 「4.Padding」:Java 对象占用空间是 8 字节对齐的,即所有 Java 对象占用 bytes 数必须是 8 的倍数,是因为当我们从磁盘中取一个数据时,不会说我想取一个字节就是一个字节,都是按照一块儿一块儿来取的,这一块大小是 8 个字节,所以为了完整,padding 的作用就是补充字节,「保证对象是 8 字节的整数倍」

15、java 中 finalize(),类似c语言中的析构函数,介绍一下?

利用这个点,可以让即将被回收的对象可以有最后的机会逃逸。不建议使用finalize()函数。

在 Java 中,finalize() 方法是一个特殊的方法,用于在对象被垃圾收集器回收之前执行一些清理操作。这个方法具体涉及到以下几个方面:

1. 作用和用途:
  • 资源释放和清理finalize() 方法允许在对象即将被垃圾收集器回收之前执行最后的资源释放和清理操作,如关闭文件、释放网络连接等。

  • 对象状态的清理:在某些情况下,对象的状态可能需要在被回收前进行特定的清理,以避免资源泄露或其他不良影响。

2. 执行时机和注意事项:
  • 非确定性finalize() 方法的执行时机不确定,这意味着不能依赖它来进行关键资源的释放或复杂的对象状态管理。垃圾收集器在决定回收对象时,可能会调用 finalize() 方法,但并不保证一定会调用,也不能确定何时调用。

  • 性能影响:由于 finalize() 方法执行的时机不确定,可能会导致垃圾收集器的性能问题。因此,官方建议尽量避免使用 finalize() 方法,而是通过显式的资源管理(如使用 try-with-resourcesfinally 块等)来确保资源的及时释放和对象状态的正确管理。

3. 示例:

以下是一个简单的示例,展示了如何在 Java 中使用 finalize() 方法来释放对象的资源:

public class ResourceExample {

    private File file;

    public ResourceExample(String filename) {
        this.file = new File(filename);
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            // 关闭文件等资源释放操作
            if (file != null && file.exists()) {
                file.delete(); // 示例中的资源释放操作
            }
        } finally {
            super.finalize();
        }
    }

    public static void main(String[] args) {
        ResourceExample example = new ResourceExample("example.txt");
        // 使用 example 对象
        example = null; // 让对象成为垃圾收集的候选对象
        // 在一定条件下,垃圾收集器可能会调用 example 的 finalize() 方法
        System.gc(); // 手动触发垃圾收集器
    }
}
总结:

尽管 finalize() 方法提供了在对象被垃圾收集器回收之前执行清理操作的机会,但由于其非确定性和性能问题,官方推荐避免使用它来管理重要资源或复杂对象的状态。更好的做法是利用现代 Java 提供的自动资源管理特性(如 try-with-resources)来确保资源的及时释放和程序的可靠性。

16、垃圾回收器跟垃圾回收算法了解吗?

关于JVM必备的一些知识-CSDN博客

17、JVM调优有哪些手段?有没有实际的经验例子可以讲解?

1、对于并发情况较高,新生代对象创建销毁较为频繁的应用,可以适当调整新生代老年代内存占比,适当增大新生代空间;

2、64位的jvm开启指针压缩;

3、对于CMS fullgc 偶尔影响服务接口性能的情况,可以考虑使用G1替代;

4、cms垃圾回收器,在在业务低峰期,job检测内存占比(Runtime),手动触发fullgc;避免在业务高峰期出现fullgc影响程序接口;

18、有没有jvm相关问题分析的实际经验例子讲解一下?

1、oom问题的排查定位及解决;

2、cpu资源销毁大,线程阻塞、死锁问题排查定位及修复;

资料参考


                        
链接:https://blog.csdn.net/weixin_61440595/article/details/137505233

https://zhuanlan.zhihu.com/p/651655476

  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

进窄门见微光行远路

如果对你有比较大的帮助

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

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

打赏作者

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

抵扣说明:

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

余额充值