Java面试题之JVM

Java面试题之JVM

  • 1. JVM的组成部分及其作用?
  • 2. JVM的堆和栈的区别?
  • 3. 简述一下垃圾回收机制?(垃圾回收的原理?)
  • 4. 垃圾回收器都有什么?该怎么选择?
  • 5. 如何判断垃圾可以回收了?
  • 6. 垃圾回收算法有哪几种?
  • 7. 年轻代、年老代、永久代区别?
  • 8. 什么是Full GC、Major GC、Minor GC?
  • 9.永久代会发生垃圾回收么?
  • 10. GC Roots 的定义
  • 11. 什么是STW?
  • 12. 什么是双亲委派模型以及优缺点?
  • 13. JVM类加载的过程?
  • 14. 内存泄漏和内存调优的区别、联系与解决办法?
  • 15. JVM调优方面有关参数的设置?
  • 16. 上述参数怎么设置合适呢?
  • 17. JVM的调优的具体的方案?
  • 18.jconsole和VisualVM使用?
  • 19.jmap和jstack?
  • 20.MAT(Memory Analyzer Tool)?

1. JVM的组成部分及其作用?

Java面试题之JVM_第1张图片
程序计数器:用于存放当前执行的字节码的行号指示器。它负责在多线程的情况下提供每个线程独立执行的能力,并保证每个线程都能正常运行。
java虚拟机栈:与线程一一对应,每个方法从执行开始到执行结束的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。它的主要作用是存储局部变量表、操作数栈、动态链接和方法出口信息。
本地方法栈:与虚拟机栈所发挥的作用非常相似,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。
:是Java内存管理的核心区域,几乎所有的对象实例都在这里分配内存。堆是由所有线程共享的一块内存区域,在虚拟机启动时创建。堆的大小可以在运行时动态调整。
方法区:用于存储已被JVM加载的类信息、常量、静态变量以及即时编译器编译后的代码等数据。它也是所有线程共享的内存区域。

2. JVM的堆和栈的区别?

存放的内容:堆用于存储所有对象实例。而栈用于存储基本数据类型和对象引用。
内存分别:堆的分配不受限制,动态分配的内存区域,堆的大小可以在运行时动态调整,只要内存足够就可以分配更多对象。而栈的大小是固定的,每个线程在创建时都会分配一个栈,栈的大小是有限的,如果超出栈的大小限制,会导致栈溢出。
生命周期:堆是垃圾回收的主要区域,垃圾回收器会自动回收不再使用的对象占用的内存。而栈中的对象生命周期与线程生命周期相同,当线程结束时,栈中的对象也会被销毁。
可见度:堆中的数据可以共享,多个线程可以共享同一个堆中的对象。而栈中的数据是私有的,每个线程的栈是独立的,无法共享数据。
物理地址:堆和栈的内存分配方式不同,堆是动态分配的,而栈是顺序分配的。

总之,JVM的堆和栈是两个不同的内存区域,它们的作用和特点不同。堆主要用于存储对象实例,垃圾回收器会自动回收不再使用的对象占用的内存;而栈主要用于存储基本数据类型和对象引用,每个线程都有自己的栈,其大小是有限的。

3. 简述一下垃圾回收机制?(垃圾回收的原理?)

垃圾回收的原理主要是通过自动管理内存空间,自动回收不再使用的内存空间,避免内存泄漏和内存浪费。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行。具体来说,垃圾回收器会标记所有活动对象,然后清除未被标记的对象,释放其占用的内存空间。不同的垃圾回收算法实现方式不同,但基本原理都是基于标记清除、复制和标记整理等方法。垃圾回收的主要目标是自动管理内存,避免内存泄漏和内存浪费,提高程序的稳定性和性能。

垃圾回收主要回收的是堆里面得内容

4. 垃圾回收器都有什么?该怎么选择?

CMS

  • 主要是为了解决垃圾回收时暂停时间过长的问题,它在执行垃圾回收时不会停止所有的用户线程,而是在用户线程运行的同时进行垃圾回收。因此,它在处理大量对象时可以提供更好的吞吐量;
  • CMS仅仅作用于老年代,是基于标记清除算法,所以清理过程中会有大量的空间碎片;
  • CMS垃圾回收器采用了并发的方式进行标记和清除操作,因此在标记和清除阶段,应用程序的线程可以继续运行,不会被完全暂停。伴随程序运行的过程会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,所以无法在本次回收中处理他们,所有只能下一次处理。

G1

  • 在Java9之后G1是默认的垃圾回收器;
  • 适用于多核处理器、大内存容量的服务端系统;
  • G1将空间划分很多块,然后他们进行各自回收,比如,堆比较大的时候可以采用复制算法,碎化问题不严重。整体属于标记整理法,局部之间是复制算法。
  • G1垃圾回收器采用了并发和并行的方式进行垃圾回收,因此在执行垃圾回收时不会停止所有的用户线程。它通过精确控制并发操作的时机和范围,尽可能地减少垃圾回收对应用程序的影响。

Serial

  • 这是最早单线程的垃圾回收器,也是最基本的垃圾回收器,它采用单线程的方式进行垃圾回收,一次只处理一个线程。由于它的简单性,因此执行效率较高,但在多核处理器上的性能表现不佳。

ParNew

  1. 这是Parallel垃圾回收器的一种变体,它使用多个线程进行垃圾回收,可以同时处理多个对象。与Parallel垃圾回收器相比,ParNew垃圾回收器在处理大量对象时具有更好的性能表现。

应用程序的特点和需求:如果应用程序对响应时间要求较高,可以选择CMS或G1垃圾回收器;如果应用程序对吞吐量和CPU使用率要求较高,可以选择Parallel或G1垃圾回收器;如果应用程序中的对象数量较少,可以选择Serial垃圾回收器。

5. 如何判断垃圾可以回收了?

引用计数法:是一种简单且慢速的垃圾回收机制,每个对象都包含一个引用计数器,当有引用对象的时候+1,当引用离开作用区域或置位null的时候,引用计数-1。
可达性分析法:可达性分析法是通过以所有的“GC Roots”对象为出发点,如果无法通过GC Roots的引用追踪到的对象,那我们认为这些对象就不会再次被使用了,现在主流的程序语言都是通过可达性分析法来判断对象是否存活的。

6. 垃圾回收算法有哪几种?

停止-复制:先暂停程序的运行,将所有活着的对象从当前堆复制到另一个堆里,没有被复制的就是垃圾。当对象被复制到新的堆里,是一个挨着一个紧凑的排列的;
缺点:浪费空间,两个堆来回倒腾;效率问题,需要暂停程序的执行;
标记-清除:遍历所有的引用,找出所有的存活对象,并会给每个存活的对象一个标记,此过程中不会回收对象,只有全部标记成功后,才会清除没被标记的对象,剩下的所有存活的对象在堆里是不连续的;
标记-整理:与标记清除的第一个标记阶段是一样的,标记所有存活的对象,然后移动这些存活的对象,并且按照内存地址进行排序,把内存地址末端的对象全部回收。
分代回收算法:把Java堆分为年轻代和老年代,新生代的存活率比较低用复制算法,老年代的存活率比较高,用标记清除或者标记整理法。

7. 年轻代、年老代、永久代区别?

涉及到的相关问题:

  • 对象什么时候进入到年老代?
  • 年轻代分哪几个区域及其作用
  • 年轻代和年老代所占的比例

年轻代:存放所有新建的对象,特点是对象更新速度快,短时间内产生大量的死亡对象;
年轻代分两个区域,Eden区和Survivor(Survivor0和Survivor1),Eden区是进行内存分配的地方,是一块连续的空闲内存区域,进行内存分配速度非常快,因为不需要进行可用内存块的查找。大多数新创建的对象首先会被分配到Eden区。
两个Survivor区用于存放从Eden区经过Minor GC后存活下来的对象。每次Minor GC后,Eden区中还存活的对象会被复制到其中一个Survivor区(另一个Survivor区暂时清空),随着时间的推移,这些对象在Survivor区中“熬过”的次数会逐渐增加,当它们的年龄达到某个阈值时,就会被移动到老年代(Old Generation)。
年老代:在年轻代经历了N(默认15)次仍存活的对象,就会被放倒年老代里,老年代的特点就是对象的生命周期较长,不会频繁的进行垃圾回收;
永久代:用于存放静态文件,如Java类,方法等。Java8后就取消了永久代的说法,使用meta space 数据空间和堆代替;

一般来说年轻代和年老代所占的比例大约为1:2,可通过Xmn参数设置大小,也可以通过-XX:NewRatio来设置年轻代与年老代的比例

8. 什么是Full GC、Major GC、Minor GC?

Full GC(全局垃圾回收):Full GC是清理整个堆空间的垃圾收集,包括年轻代和老年代。当老年代空间不足或永久代(在JDK 8以前的版本中)空间不足时,会触发Full GC。Full GC的触发会导致应用程序暂停,因为它需要清理整个堆空间,包括所有的对象和元数据信息。由于Full GC的暂停时间较长,因此应尽量避免频繁的Full GC。
Major GC(老年代垃圾回收):Major GC主要清理老年代空间。在JVM中,老年代主要存放长时间存活的对象。当老年代空间不足时,会触发Major GC进行垃圾回收。与Full GC不同的是,Major GC只会回收老年代中的垃圾对象,而不会影响年轻代或永久代(如果存在)。
Minor GC(年轻代垃圾回收):Minor GC主要清理年轻代空间,包括Eden区和Survivor区。年轻代是大多数新对象创建和销毁的地方。当Eden区满时,会触发Minor GC进行垃圾回收,清理不再使用的对象,并将仍在使用的对象复制到Survivor区或老年代。Minor GC的触发频率较高,但由于只涉及年轻代,因此通常对应用程序的性能影响较小。

当年老代空间不足的时候,会同时出发Full GC和Major GC

9.永久代会发生垃圾回收么?

Java8以前,永久代不属于堆内存,是属于永久代内存,不受垃圾回收机制的管理,因此无法发生垃圾回收。但是如果永久代发生了内存溢出,会触发Full GC,尝试回收永久代中无用的类。
Java8以后,被移除了之后,元空间使用的是本地内存,不属于堆内存,也不会被垃圾回收。

10. GC Roots 的定义

就是一些由对外到堆内的引用,包括:栈中引用的对象、方法区中已加载的静态类、方法区中常量引用的对象、启动未停止的线程

11. 什么是STW?

STW是 Stop-The-World 的简称,指在垃圾回收算法执行过程中,将JVM内存冻结,停顿的一种状态。在STW状态下,所有的线程都是停止运行的,除了垃圾回收线程。当STW发生时,除了GC所需要的线程,其他的线程都将停止工作,中断了的线程知道GC线程结束才会继续任务。STW是不可避免的,垃圾回收算法的执行一定会出现STW,而我们最好的解决办法就是减少停顿的时间。GC各种算法的优化重点就是为了减少STW,这也是JVM调优的重点。

12. 什么是双亲委派模型以及优缺点?

定义:如果一个类加载器需要加载类,那么首先他会把这个类请求委派给父类加载器去完成,每一层都如此,一直递归到顶层,当父类无法完成这个请求的时候,子类才会尝试加载。
双亲委派模型优点

  • 避免类的重复加载:双亲委派模型采用层次化的类加载器结构,每个类加载器都只加载自己负责的类,避免了类的重复加载。
  • 保证系统稳定运行:双亲委派模型可以保证核心Java API不被篡改,从而保证系统稳定运行。
  • 提高安全性:双亲委派模型可以防止用户自己写的类动态替换Java的一些核心类,如String,从而提高系统的安全性。
  • 实现灵活的类加载策略:双亲委派模型提供了一种灵活的类加载策略,可以实现自定义类加载器,从而满足特定场景下的类加载需求。

双亲委派模型缺点

  • 阻止了自定义类的动态更新:由于委派机制会先委派给父类加载器,因此当一个类已经被父类加载器加载后,即使该类被更新了,自定义类加载器也无法重新加载该类,从而阻止了自定义类的动态更新。
  • 限制了类的可见性:由于父类加载器无法访问子类加载器加载的类,因此在某些情况下,类的可见性可能受到限制。
  • 导致一些类无法被加载:由于委派机制会先委派给父类加载器,因此在一些特殊情况下,某些类可能无法被加载,例如当父类加载器无法加载某个类时,而子类加载器又没有实现加载该类的逻辑时,该类就无法被加载。

13. JVM类加载的过程?

不同问法:

  • 简述下类的生命周期?
  • JVM实例化对象的过程?

加载:根据类的全限定名称找到类文件,将类文件以二进制字节流的形式加载到虚拟机中;将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构;在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问的入口。
验证:验证阶段包括文件格式验证、元数据验证、字节码验证和符号引用验证。验证的目的是确保被加载的类文件的正确性和安全性。
准备:准备阶段是为类中定义的变量(静态变量)分配内存并设置类变量初始值的阶段。
解析:解析阶段是将常量池中的符号引用替换为直接引用的过程。解析的目的是将类的符号引用转换为直接引用,以便在运行时能够直接访问类的数据。
初始化:初始化阶段是执行类构造器方法()的阶段。在之前的本步骤中,除了加载阶段用户可以通过自定义类加载器的方式局部参与,其余都是有jvm主导完成的
实例化过程有下面这俩步骤
创建对象实例:在类初始化完成后,就可以创建该类的对象实例了。在Java中,可以使用“new”关键字来创建对象实例。当使用“new”关键字创建对象时,JVM会在堆内存中为对象分配空间,并将对象的引用赋值给变量。
使用对象:一旦对象被实例化,就可以通过引用来使用对象的方法和属性了。

14. 内存泄漏和内存调优的区别、联系与解决办法?

内存泄漏:是指程序中已动态分配的堆内存由于某种原因未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统奔溃等严重后果。-memory leak
内存溢出:是指程序申请内存时,没有足够的内存供申请者使用; -out of memory

一次内训泄漏似乎不会有大的影响,但内存泄漏后堆积的结果就是内存溢出

内存泄漏解决办法

  • 内存中加载数据量过大,如一次从数据库中取出过多的书库;
  • 代码死循环了,或者循环过多,产生了重复的对象;
  • 启动参数设置是否的过小,去尝试修改;
  • 集合类中有对象的引用,使用后要及时清空,JVM不能回收这部分内容;

内存溢出解决办法

  • 如果是内存泄漏导致的溢出,可查看对象到GC Roots的引用链,找到泄漏对象是通过怎样引用路径与哪些GC Roots关联,才导致无法回收,根据引用链的信息,可找到这些对象的位置,然后找出泄漏的位置去改正;
  • 如果不是泄漏导致的,检查参数值,看看是否有调整空间,没有在从代码层面检查,某些对象的生命周期是否合理,存储结构是否合理,再去改正;

15. JVM调优方面有关参数的设置?

堆内存大小:通过-Xms和-Xmx参数来设置JVM的初始堆内存和最大堆内存大小。例如,-Xms512m表示初始堆内存为512MB,-Xmx1024m表示最大堆内存为1024MB。
垃圾回收器:通过-XX:+UseG1GC、-XX:+UseParallelGC或-XX:+UseConcMarkSweepGC等参数来选择不同的垃圾回收器。不同的垃圾回收器适用于不同的应用场景,需要根据实际情况选择。
垃圾回收日志:通过-XX:+PrintGCDetails、-XX:+PrintGCDateStamps和-Xloggc:等参数来开启和配置垃圾回收日志的输出。这些日志可以帮助分析垃圾回收的性能问题。
线程数:通过-XX:MaxPermSize参数来设置永久代(PermGen)的内存大小。对于Java 8及更高版本,可以使用元空间(Metaspace)来替代永久代,通过-XX:MaxMetaspaceSize参数来设置元空间的内存大小。
类加载器:通过-XX:+UseCompressedClassPointers和-XX:+UseCompressedOops等参数来开启压缩指针,以减少内存占用和提高性能。
内存缓存:通过-XX:+UseConcMarkSweepGC和-XX:+UseParallelGC等参数来开启或关闭CMS和Parallel GC垃圾回收器中的内存缓存。
堆外内存:通过-XX:MaxDirectMemorySize参数来设置直接内存的最大内存大小。直接内存主要用于NIO等操作,可以用来提高IO性能。
堆内内存:通过-XX:NewRatio参数来设置新生代和老年代的比例,以优化堆内内存的使用。
JIT编译器:通过-XX:+TieredCompilation、-XX:+UseFastAccessorMethods和-XX:-UseOnStackReplacement等参数来配置JIT编译器的行为。

16. 上述参数怎么设置合适呢?

总的来说还是要根据自己的实际情况来,下面会说一些实际的废话~

堆内存大小:初始堆内存和最大堆内存的大小应根据应用程序的需求进行设置。如果应用程序需要处理大量数据或执行复杂计算,可能需要更大的堆内存。初始堆内存和最大堆内存的大小应保持适当的比例,以避免频繁的垃圾回收和内存溢出。
垃圾回收器:不同的垃圾回收器适用于不同的应用场景。例如,G1垃圾回收器适用于具有大量存活对象的长时间运行的服务器应用程序,而Parallel垃圾回收器适用于需要快速垃圾回收的批处理工作负载。选择合适的垃圾回收器可以提高应用程序的性能和响应能力。
垃圾回收日志:开启垃圾回收日志可以帮助分析垃圾回收的性能问题。通过分析日志,可以了解垃圾回收的频率、持续时间和消耗的资源,并根据分析结果调整堆内存大小和其他参数。
线程数:线程数应根据应用程序的需求进行设置。如果应用程序需要处理大量并发请求或执行多任务处理,可能需要更多的线程。同时,过多的线程可能会导致资源竞争和上下文切换开销,因此需要根据实际情况进行权衡。
类加载器:开启压缩指针可以减少内存占用和提高性能。如果应用程序使用了大量的类和对象,开启压缩指针可以节省内存空间并提高指针访问速度。
内存缓存:根据应用程序的需求和实际情况进行内存缓存的设置。如果应用程序需要频繁地进行IO操作或访问大量数据,开启内存缓存可以提高性能和响应能力。
堆外内存:直接内存主要用于NIO等操作,可以用来提高IO性能。如果应用程序需要大量的直接内存,可以设置较大的直接内存大小。
JIT编译器:JIT编译器的行为可以根据应用程序的性能要求进行配置。例如,可以启用TieredCompilation和UseFastAccessorMethods来提高编译速度和运行时性能。

17. JVM的调优的具体的方案?

有很多种问法,比如:

  • Full Gc是怎么定位到问题,并去解决的?
  • 你有没有在实际项目中遇到Jvm内存泄漏导致溢出的解决方案?
  • 系统运行慢,是怎么排查的?
  • 系统CPU负载过高怎么排查的?

Full GC(全局垃圾收集)是垃圾收集器对整个堆进行垃圾回收的过程。如果应用程序频繁地进行Full GC,可能会导致应用程序性能下降,因此需要找到引起Full GC频繁发生的原因并解决它们:
当程序出现JVM调优问题,如死循环或内存泄漏,可以采取以下步骤进行定位和解决:
识别问题:首先,需要确认程序确实存在内存泄漏和死循环问题。可以通过观察程序运行时的内存使用情况,使用工具如VisualVM,JConsole等,观察内存使用情况,如果内存使用持续增长并且无下降趋势,那么可能存在内存泄漏问题。同时,可以观察程序的CPU使用情况,如果CPU使用率持续很高并且无下降趋势,那么可能存在死循环问题。此外,还可以通过程序日志、错误信息等途径获取问题的线索。
生成dump文件:如果确认了内存泄漏和死循环问题,接下来需要生成dump文件。在Java中,可以使用jmap、jstack等工具生成dump文件。这个文件包含了程序在运行时的所有对象和它们之间的关系。可以使用MAT(Memory Analyzer Tool)等工具打开dump文件进行分析。
分析dump文件:打开dump文件后,使用MAT可以查看每个对象的类、字段和方法等信息,以及对象之间的引用关系。通过分析这些信息,可以找到哪些对象占用了大量内存以及哪些对象无法被垃圾收集器回收等。同时,也可以通过分析dump文件找到程序的死循环原因。
定位问题代码:根据分析结果,可以定位到具体哪一行代码导致了内存泄漏或无用对象过多等问题。可以使用调试工具如Eclipse、IntelliJ IDEA等对程序进行调试,设置断点并逐步执行程序,以找到问题的根源。
解决内存泄漏问题:根据定位的问题代码,可以采取以下措施来解决内存泄漏问题:

  • 优化对象生命周期:确保对象在使用完后及时被垃圾收集器回收。
  • 避免静态变量和单例模式导致的内存泄漏:尽量避免使用静态变量和单例模式,特别是在长时间运行的应用程序中。
  • 使用弱引用:使用弱引用可以避免对象被永久保存下来,从而避免内存泄漏问题。
  • 修复程序逻辑错误:有时候内存泄漏是由于程序逻辑错误导致的,需要仔细检查程序代码,确保逻辑正确。
    优化JVM参数:在进行内存管理和垃圾回收时,JVM提供了一些参数供我们进行调优。例如,可以调整新生代和老年代的大小、选择使用哪种垃圾收集器等。可以根据实际情况调整这些参数,以达到更好的性能。
    测试并监控:在修复了内存泄漏和优化了JVM参数后,需要进行测试以验证是否问题已经解决。同时,需要持续监控程序的运行情况,以确保程序仍然表现出良好的性能。可以使用监控工具如VisualVM、JConsole等对程序进行监控,及时发现并解决问题。

18.jconsole和VisualVM使用?

JConsole:查看应用程序的性能数据,并对其进行监控和分析。通过使用 JConsole,可以获取有关内存使用情况、线程、类加载、MBeans 等方面的信息。此外,JConsole 还提供了插件机制,可以使用插件进行扩展。
如何启动 JConsole 取决于您的操作系统和 Java 安装环境。如果从命令行启动,您需要在已安装 JDK 的系统上运行 jconsole 命令。如果从 GUI shell 启动,则需要找到 JDK 安装路径,打开 bin 文件夹,并双击 jconsole 文件。
VisualVM :1.8以下自带,更高级版本需要下载用

19.jmap和jstack?

jstack用于生成当前Java应用程序的线程堆栈信息,可以用来诊断线程阻塞、死锁等问题。它可以将线程堆栈信息输出到标准输出或保存到文件中,方便开发人员和系统管理员进行分析和调试。
jmap主要用于生成Java应用程序的内存转储快照(heapdump),即堆内存的映像文件。它可以用来分析Java应用程序的内存使用情况,查找内存泄漏、对象过多占用内存等问题。

总的来说,jstack和jmap都是Java开发中常用的工具,但jstack更侧重于线程堆栈信息的诊断,而jmap更侧重于内存使用情况的分析。在实际应用中,可以根据需要选择合适的工具进行使用。

20.MAT(Memory Analyzer Tool)?

这是一个基于Eclipse的内存分析工具,用于快速、功能丰富的JAVA heap分析。它可以用于查找内存泄漏和减少内存消耗,并从众多的对象中进行分析,快速计算出在内存中对象的占用大小。当然在idea中也可以使用,idea中也可以用jprofiler插件来分析dump文件

你可能感兴趣的:(java,jvm,python)