jvm高级面试题-2024

时间:2024-04-03 09:28:00

说下对JVM内存模型的理解

        JVM内存模型主要是指Java虚拟机在运行时所使用的内存结构。它主要包括堆、栈、方法区和程序计数器等部分。

        堆是JVM中最大的一块内存区域,用于存储对象实例。一般通过new关键字创建的对象都存放在堆中,堆的大小可以通过启动参数进行调整。堆被所有线程共享,但是它的访问是线程不安全的,需要通过锁机制来保证线程安全。

       栈用于存储方法调用和局部变量。每个线程在运行时都会有一个独立的栈,栈中的每个方法调用都会创建一个栈帧,栈帧包含了方法的参数、局部变量和返回值等信息。栈的大小是固定的,并且栈中的数据是线程私有的,不会被其他线程访问。

        方法区用于存储类的信息和静态变量。它是所有线程共享的内存区域,存储了类的结构信息、常量池、静态变量和方法字节码等。方法区的大小也可以通过启动参数进行调整。

        程序计数器是每个线程私有的,用于记录当前线程执行的字节码指令的地址每个线程都有一个独立的程序计数器,用于控制线程的执行流程。

说说对象分配规则

  1. 内存分配:新对象通常在堆内存中分配内存空间。
  2. 对象头:在为对象分配内存空间后,Java虚拟机会为对象分配一个对象头。对象头包含了一些关于对象的元信息,如对象的哈希码、锁状态、垃圾回收信息等。
  3. 零值初始化:在对象内存分配后,所有的成员变量会被初始化为零值。具体的零值取决于变量的数据类型。例如,整数类型会初始化为0,布尔类型会初始化为false,对象引用会初始化为null。
  4. 构造函数调用:一旦对象内存分配和零值初始化完成,Java虚拟机会调用对象的构造函数。
  5. 对象引用:最后,new 关键字会返回对象的引用,将这个引用分配给一个变量,以便后续可以通过该变量访问对象的属性和方法。
  6. 垃圾回收管理:Java虚拟机会自动管理对象的内存。如果对象不再被引用,它会被标记为垃圾,并在适当的时机由垃圾回收器回收,释放占用的内存。

常用的JVM启动参数有哪些

  1. -Xmx:指定Java堆内存的最大限制。例如,-Xmx512m 表示最大堆内存为512兆字节。
  2. -Xms:指定Java堆内存的初始大小。例如,-Xms256m 表示初始堆内存为256兆字节。
  3. -Xss:指定每个线程的堆栈大小。例如,-Xss256k 表示每个线程的堆栈大小为256千字节。
  4. -XX:MaxPermSize(对于Java 7及之前的版本)或 -XX:MaxMetaspaceSize(对于Java 8及以后的版本):指定永久代(Java 7及之前)或元空间(Java 8及以后)的最大大小。
  5. -XX:PermSize(对于Java 7及之前的版本)或 -XX:MetaspaceSize(对于Java 8及以后的版本):指定永久代(Java 7及之前)或元空间(Java 8及以后)的初始大小。
  6. -Xmn:指定年轻代的大小。例如,-Xmn256m 表示年轻代大小为256兆字节。

设置堆内存XMX应该考虑哪些因素

  1. 应用程序的内存需求:首先要了解应用程序的内存需求。这包括应用程序的数据量、并发用户数、对象创建频率等。不同的应用程序可能需要不同大小的堆内存。
  2. 应用程序的性能需求:性能目标对内存大小有很大的影响。如果需要更高的吞吐量和更低的延迟,可能需要分配更多的内存。但要小心不要分配过多,以避免浪费内存。
  3. 可用物理内存:要考虑服务器或计算机上的可用物理内存量。将-Xmx参数设置为超过物理内存容量的值可能会导致操作系统频繁地进行内存交换,降低性能。
  4. 垃圾回收的开销:堆内存越大,垃圾回收的开销通常也会增加。大堆内存可能需要更长的垃圾回收暂停时间。因此,要权衡内存大小和垃圾回收开销。
  5. 堆内存分代结构:Java堆内存通常分为年轻代、老年代和永久代(或元空间,取决于JVM版本)。不同代的分配比例和大小会影响-Xmx的设置。根据应用程序的特性,可以考虑调整不同代的大小。
  6. 监控和调整:监控应用程序的内存使用情况,使用工具如JVisualVM、JConsole等来观察堆内存的使用情况。根据监控数据进行动态调整-Xmx参数。
  7. 应用程序设计:合理的应用程序设计也可以影响堆内存需求。避免内存泄漏和不必要的对象创建可以降低内存需求。
  8. 并发性需求:多线程应用程序通常需要更多的堆内存,因为每个线程都需要一定的内存空间来存储栈帧和局部变量。
  9. JVM版本和垃圾回收器:不同的JVM版本和垃圾回收器可能对内存需求有不同的影响。某些垃圾回收器可能更适合大堆内存,而某些适用于小堆内存。

CPU百分百问题如何排查

  1. 查看系统负载:首先,使用系统监控工具比如top查看系统的负载情况。
  2. 确定是哪个进程导致CPU高占用:查找哪个进程或应用程序的CPU占用率很高。通常,系统监控工具会列出占用CPU较多的进程。注意,有时一个进程的子进程也可能引起CPU高占用。
  3. 查看日志文件:检查应用程序的日志文件,查找是否有异常或错误消息。
  4. 检查代码:如果是自己开发的应用程序,检查代码以查找是否存在性能问题,例如死循环、低效的算法、内存泄漏等。使用性能分析工具来帮助确定瓶颈。
  5. 查看数据库查询:如果应用程序与数据库交互,查询可能导致CPU负载高。通过检查数据库的慢查询日志和优化查询来解决问题。
  6. 监控线程:如果是多线程应用程序,检查是否有某些线程占用了大量CPU资源。使用线程分析工具来识别问题线程。
  7. 查看网络连接:有时,网络请求和连接问题也可能导致CPU高占用。查看是否有异常的网络连接或请求。
  8. 使用性能分析工具:使用专业的性能分析工具来检测瓶颈。例如,Java应用程序可以使用Arthas、VisualVM等工具进行分析。
  9. 应用程序优化:根据排查的结果,对应用程序进行优化,修复性能问题。

说下类加载器机制与双亲委派

Java类加载器机制是JVM用于加载类文件到内存中的核心机制。它采用了一种层级结构和双亲委派模型,确保了类的唯一性和安全性。
类加载机制是类加载器负责将类文件加载到JVM的内存中,使得类可以被实例化和调用。类加载器按照层级结构组织,形成了一个类加载器树。每个类加载器负责加载特定范围的类,通常分为以下几种类加载器:
        ○引导类加载器:它是JVM的一部分,用于加载Java核心类库,通常位于jre/lib/rt.jar中。
        ○扩展类加载器:负责加载jre/lib/ext目录下的JAR包。
        ○应用程序类加载器:也称为系统类加载器,负责加载应用程序classpath下的类。
        ○自定义类加载器:用户可以根据需要创建自己的类加载器,以加载特定位置或方式的类文件。
双亲委派模型是类加载器机制的核心概念之一。它规定了类加载器在尝试加载类时首先委派给父类加载器进行尝试,只有在父类加载器无法加载时才由子类加载器尝试加载。这个模型的目的是确保类的唯一性和安全性。即便是不同的类加载器加载相同的类,它们也会被视为不同的类,因为每个类加载器都有自己的类命名空间。双亲委派模型可以防止系统类库被篡改或替换,因为即使有人尝试加载一个与系统类库同名的类,它也不会覆盖系统类库。

说说你对垃圾收集器的理解

  1. 内存管理:垃圾收集器负责管理Java应用程序的堆内存。堆内存是用于存储Java对象的区域,而垃圾收集器负责分配、回收和释放这些内存。
  2. 自动回收:垃圾收集器自动识别不再被引用的对象,并将其标记为垃圾,然后释放这些垃圾对象占用的内存。这个过程是自动的,程序员无需手动释放内存。
  3. 内存泄漏防止:垃圾收集器可以防止内存泄漏,即程序中的对象无法被回收,导致内存消耗不断增加。通过垃圾收集器,不再使用的对象最终会被回收,释放内存。
  4. 性能影响:不同的垃圾收集器实现具有不同的性能特性。一些收集器专注于最小化停顿时间(低延迟),而其他收集器则专注于最大化吞吐量。选择合适的垃圾收集器取决于应用程序的性能需求。
  5. 分代垃圾收集:垃圾收集器通常使用分代策略,将堆内存分为不同的代(通常是年轻代和老年代),以便根据对象的生命周期采用不同的回收策略。年轻代通常使用快速的回收算法,而老年代则采用更复杂的算法。
  6. 垃圾回收算法:不同的垃圾收集器实现使用不同的垃圾回收算法,如标记-清除复制标记-整理等。这些算法有不同的优缺点,适用于不同类型的应用程序。

说下JVM中一次完整的 GC 流程

  1. 标记阶段:GC从根对象开始,通过根对象的引用链,标记所有可达的对象。根对象包括活动线程的栈帧中的局部变量、静态变量、JNI引用等。
  2. 垃圾标记:在标记阶段完成后,GC会确定哪些对象是垃圾对象,即不可达对象。这些对象将被标记为垃圾,可以被回收。
  3. 垃圾回收:在标记阶段完成后,GC会执行垃圾回收操作,回收被标记为垃圾的对象所占用的内存空间。回收的方式有不同的算法,例如标记-清除、复制、标记-整理等。
  4. 内存整理:在垃圾回收完成后,可能会产生内存碎片。为了提高内存的利用率,GC可能会对内存空间进行整理,将存活的对象紧凑地排列在一起,以便更好地分配新的对象。
  5. 内存分配:在垃圾回收和内存整理完成后,GC会为新的对象分配内存空间。分配的方式有不同的算法,例如指针碰撞、空闲列表等。
  6. 重新分配对象引用:在垃圾回收和内存分配完成后,GC会更新对象之间的引用关系,确保引用指向正确的对象。