【面试宝典】41道JVM高频题库整理(附答案背诵版)

为什么Java 可以一次编写,处处运行?

Java的“一次编写,处处运行”主要得益于Java的设计理念和Java虚拟机(JVM)的存在。

首先,Java语言是一种高级语言,其编写的程序需要编译成字节码文件,而不是直接编译成机器语言。这使得Java编写的程序可以在任何安装了Java虚拟机的设备上运行,因为JVM负责将字节码转换为特定操作系统和硬件架构的机器语言。

其次,Java虚拟机(JVM)为每个操作系统提供了一种中间层,使得Java程序不需要关心底层操作系统的差异。比如,Windows系统和Linux系统的底层实现机制是不同的,但是只要他们都安装了JVM,那么Java程序就可以在这两个系统上运行,而不需要做任何修改。

举个例子,假设我们开发了一个Java Web应用,我们在本地的Windows系统上进行开发和测试,然后我们需要将这个应用部署到Linux服务器上。由于Java的“一次编写,处处运行”的特性,我们不需要对程序做任何修改,只需要确保服务器上安装了JVM,就可以直接运行我们的应用了。

请解释Java虚拟机(JVM)及其主要功能

JVM(Java Virtual Machine)是Java虚拟机的简称,它是运行所有Java程序的抽象计算机。也就是说,JVM是一个能够运行Java字节码的虚拟的计算机平台。

JVM的主要功能是负责Java程序的加载、链接、初始化、执行以及提供一个与硬件无关的运行环境。Java源代码经过编译后会生成字节码文件,然后由JVM解释或编译执行。这样的设计使得Java程序能够在各种硬件和操作系统上运行,实现了“一次编写,处处运行”。

此外,JVM还负责内存管理和垃圾回收,它会自动管理对象的生命周期,当一个对象不再被引用时,JVM会自动回收其占用的内存,这极大地简化了程序员的工作。

总的来说,JVM是Java技术的核心和基石,是实现Java跨平台运行的关键。

JVM是由哪些核心组件构成的

Java虚拟机(JVM)主要由以下几个部分组成:

1、 类加载器(Class Loader):负责从文件系统或者网络中加载Java类,对字节码进行验证,然后解析和初始化类。

2、 运行时数据区(Runtime Data Area):这是JVM的主要组成部分,包括方法区(Method Area)、堆区(Heap)、虚拟机栈(Java Stacks)、程序计数器(PC Registers)和本地方法栈(Native Method Stacks)。这些区域负责存储在JVM运行过程中产生的数据。

3、 执行引擎(Execution Engine):负责解释和执行字节码。它包括一个解释器(Interpreter)和一个即时编译器(JIT Compiler)。解释器负责将字节码逐条解释执行,而即时编译器则是在运行时将热点代码直接编译成机器码执行,提高了执行效率。

4、 垃圾回收器(Garbage Collector):负责自动管理和回收JVM中的内存资源,当对象不再被引用时,垃圾回收器会自动回收其占用的内存。

5、 本地方法接口(Java Native Interface,JNI):允许Java代码调用其他语言写的本地方法,比如C、C++等。

6、 本地方法库:这是一个集合,包含了用其他语言实现的本地方法。

列举并解释一些常用的JVM参数

JVM参数主要分为两类:标准参数(-开头)和非标准参数(-X开头)。以下是一些常用的JVM参数:

  1. -Xms< size>:设置JVM初始堆内存大小。例如:-Xms256m,表示初始堆内存大小为256MB。

  2. -Xmx< size>:设置JVM最大堆内存大小。例如:-Xmx1024m,表示最大堆内存大小为1024MB。

  3. -Xss< size>:设置每个线程的栈大小。例如:-Xss1m,表示每个线程的栈大小为1MB。

  4. -XX:MetaspaceSize=< size>:设置元空间的初始大小(Java 8中替代了永久代的概念)。例如:-XX:MetaspaceSize=128m,表示元空间初始大小为128MB。

  5. -XX:MaxMetaspaceSize=< size>:设置元空间的最大大小。例如:-XX:MaxMetaspaceSize=256m,表示元空间最大大小为256MB。

  6. -XX:NewSize=< size>:设置新生代的初始大小。例如:-XX:NewSize=128m,表示新生代初始大小为128MB。

  7. -XX:MaxNewSize=< size>:设置新生代的最大大小。例如:-XX:MaxNewSize=256m,表示新生代最大大小为256MB。

  8. -XX:SurvivorRatio=< ratio>:设置新生代中Eden区与Survivor区的比例。例如:-XX:SurvivorRatio=8,表示Eden区与Survivor区的比例为8:1。

  9. -XX:PermSize=< size>:设置永久代的初始大小(仅在Java 7及更早版本中使用)。例如:-XX:PermSize=64m,表示永久代初始大小为64MB。

  10. -XX:MaxPermSize=< size>:设置永久代的最大大小(仅在Java 7及更早版本中使用)。例如:-XX:MaxPermSize=128m,表示永久代最大大小为128MB。

  11. -XX:+UseSerialGC:使用串行垃圾回收器。

  12. -XX:+UseParallelGC:使用并行垃圾回收器。

  13. -XX:+UseConcMarkSweepGC:使用CMS垃圾回收器。

  14. -XX:+UseG1GC:使用G1垃圾回收器。

  15. -XX:+PrintGCDetails:打印详细的垃圾回收信息。

这些参数可以根据实际情况调整,以优化JVM的性能和资源利用。在实际应用中,通常需要根据程序的需求和运行环境来调整这些参数,以达到最佳性能。

HotSpot是什么?它在JVM中的作用是什么?

HotSpot是Sun公司(后被Oracle收购)开发的一款高性能的Java虚拟机(JVM)实现。它的名字源于它采用的热点技术(HotSpot Technology),即通过动态分析程序运行时的热点代码(经常执行的代码),对这些热点代码进行优化和即时编译(Just-In-Time Compilation,简称JIT),从而提高程序的运行速度。

HotSpot JVM具有以下特点:

  1. 高性能:HotSpot JVM通过即时编译器(JIT Compiler)将字节码动态地编译成本地机器码,提高了程序的执行效率。同时,HotSpot JVM还采用了许多性能优化技术,如内联缓存、逃逸分析等。

  2. 跨平台:HotSpot JVM可以运行在多种操作系统和硬件平台上,包括Windows、Linux、macOS等。

  3. 自动内存管理:HotSpot JVM负责内存分配和垃圾回收,提供了多种垃圾回收器(如串行、并行、CMS、G1等)以满足不同场景下的性能需求。

  4. 丰富的调优参数:HotSpot JVM提供了大量的调优参数,使得开发者可以根据实际需求对JVM进行性能调优。

  5. 持续演进:HotSpot JVM作为Java平台的主要实现,得到了持续的更新和优化,以适应新的技术和硬件发展。

总之,HotSpot JVM是Java虚拟机的一种高性能实现,广泛应用于各种Java应用程序和开发环境中。

描述JVM的内存区域划分

JVM的内存主要可以分为以下五个区域:

1、程序计数器(Program Counter Register):这是线程私有的内存区域。它的作用是记录当前线程执行的字节码的行号指示器,用于指示当前线程的执行位置。

2、Java虚拟机栈(Java Virtual Machine Stacks):这也是线程私有的内存区域。每个方法执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

3、本地方法栈(Native Method Stacks):这个区域与虚拟机栈类似,只不过虚拟机栈为虚拟机执行Java方法(字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。

4、Java堆(Java Heap):这是所有线程共享的内存区域,主要用于存放对象实例。这个区域的内存管理(包括内存分配和垃圾回收)是JVM管理的重点。

5、方法区(Method Area):这也是所有线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量等数据。

除了这五个区域外,还有一块是直接内存(Direct Memory),它并不是虚拟机运行时数据区的一部分,但是这部分内存也被频繁地使用。JDK1.4新引入了NIO(New Input/Output),引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

解释Java内存模型(JMM)及其重要性?

Java内存模型(Java Memory Model,简称JMM)是一种抽象的概念,它定义了Java程序中各种共享变量(主要是实例域、静态域和数组元素)的访问规则,以及在并发环境中如何进行线程同步的规定。

Java内存模型的主要目标是定义程序中各个变量的访问方式,以及在单线程内和多线程之间如何交互,如何保证数据的可视性和有序性,从而在并发环境中提供一种更安全、更高效的编程模型。

在Java内存模型中,主要包括以下几个方面的内容:

1、原子性:指一个操作是不可中断的,即不会被线程调度机制打断。

2、可见性:指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。

3、有序性:即程序执行的顺序按照代码的先后顺序执行。

4、重排序:为了提高程序运行效率,编译器和处理器可能会对指令进行重新排序,但是重新排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

5、volatile、synchronized、final、lock等关键字在内存模型中的具体语义和作用。

6、happens-before原则:这是Java内存模型中最核心的概念,它定义了内存操作之间的偏序关系,可以解决可见性和有序性问题。

总的来说,Java内存模型主要解决了多线程环境下共享数据的一致性、可见性等问题,是Java并发编程的基础。

对比Java内存模型与JVM内存模型的不同点

Java内存模型(Java Memory Model,简称JMM)和JVM内存模型是两个不同的概念,它们关注的问题和解决的问题是不同的。

  1. Java内存模型:Java内存模型主要关注的是多线程环境下,如何以线程安全的方式对共享变量进行操作。它定义了变量的读取、写入等操作的规则,并规定了在并发环境下,如何通过volatile、synchronized等关键字来保证共享变量的可见性和有序性。Java内存模型解决的是在多线程编程中,如何保证内存的可见性、原子性和有序性,以防止出现数据不一致的问题。

  2. JVM内存模型:JVM内存模型主要关注的是JVM的内存区域划分和内存管理。它将JVM内存划分为堆内存、栈内存、方法区、程序计数器等区域,并定义了每个区域的使用方式和作用。比如,堆内存主要用于存储对象实例,栈内存用于存储局部变量,方法区用于存储已被加载的类信息等。JVM内存模型主要解决的是内存的分配和回收问题。

总的来说,Java内存模型主要是为了解决多线程编程中的内存可见性和有序性问题,而JVM内存模型则是关注JVM如何管理和分配内存。

Java 8的内存结构有哪些显著变化?

在Java 8中,内存结构相较于之前的版本有一些变化。主要的变化在于永久代(PermGen)被移除,取而代之的是元空间(Metaspace)。以下是关于这两者的详细解释:

  1. 永久代(PermGen):在Java 7及其之前的版本中,永久代主要用于存储类的元数据、静态变量以及方法区等。永久代的内存大小是有限的,当加载的类过多时,可能会导致永久代内存溢出(java.lang.OutOfMemoryError: PermGen space),这在实际应用中是一个常见的问题。

  2. 元空间(Metaspace):在Java 8中,永久代被移除,取而代之的是元空间。元空间与永久代的主要区别在于它的内存分配。元空间并不位于Java堆内存中,而是使用本地内存(Native Memory)。这意味着元空间的大小不再受到Java堆内存的限制,而是受到本地内存的限制,这有助于减少永久代内存溢出的问题。当然,元空间也并非无限大,当元空间的内存分配超出限制时,仍然会抛出内存溢出异常(java.lang.OutOfMemoryError: Metaspace)。

除了上述变化外,Java 8中的内存结构大致保持不变,包括Java堆、栈、程序计数器、本地方法栈等。Java堆主要用于存储对象实例,栈用于存储局部变量、方法调用等,程序计数器用于存储当前线程的执行位置,本地方法栈用于支持本地方法的调用。

总结一下,Java 8中的内存结构变化主要是将永久代替换为元空间,这有助于解决永久代内存溢出的问题,同时使得内存分配更加灵活。在实际应用中,我们需要关注元空间的内存使用情况,以便在需要时进行调整。

为什么Java 8要移除永久代(PermGen)?

永久代(PermGen)在Java 8中被移除,主要是因为以下几个原因:

  1. 简化垃圾收集:在Java 7及其之前的版本中,永久代存储了大量的类的元数据,这使得垃圾收集器需要处理这部分内存,增加了垃圾收集的复杂性。移除永久代后,垃圾收集器只需要关注Java堆内存,从而简化了垃圾收集的过程。

  2. 避免内存溢出:永久代的内存大小是有限的,当加载的类过多时,可能会导致永久代内存溢出。而元空间使用的是本地内存,其大小只受限于本地内存的大小,因此更不容易出现内存溢出。

  3. 提高性能:永久代的内存管理需要消耗一定的性能。移除永久代后,可以减少内存管理的开销,从而提高系统的性能。

  4. 更好的内存控制和监控:永久代的内存分配和回收策略与Java堆不同,这使得对其进行控制和监控比较困难。而元空间使用的是本地内存,可以借助于本地内存管理工具进行更好的控制和监控。

总的来说,永久代被移除是为了简化垃圾收集,避免内存溢出,提高性能,以及实现更好的内存控制和监控。

对比堆内存和栈内存的特点和使用场景

堆和栈是Java内存中的两个重要区域,它们在内存分配、数据存储和生命周期等方面有以下主要区别:

  1. 内存分配:

    • 堆(Heap)是Java内存中用于存储对象实例的区域,它是一个运行时数据区,大小可动态扩展。堆内存由所有线程共享,因此在堆中分配的内存可以被所有线程访问。
    • 栈(Stack)是Java内存中用于存储局部变量、方法调用等的区域。每个线程都有一个独立的栈,栈内存由线程私有。栈的大小是固定的,当栈内存不足时,会导致栈溢出错误(java.lang.StackOverflowError)。
  2. 数据存储:

    • 堆中主要存储对象实例及其相关数据。当我们使用new关键字创建对象时,对象实例被分配到堆内存中。
    • 栈中主要存储基本数据类型(如int、float、boolean等)、对象引用变量以及方法调用相关信息(如方法调用的顺序、局部变量等)。
  3. 生命周期:

    • 堆内存中的对象实例的生命周期较长。它们会在垃圾收集器运行时被回收,具体回收时机取决于垃圾收集器的策略。
    • 栈内存中的数据随着方法的调用和返回而创建和销毁。当一个方法执行结束后,该方法在栈中的局部变量和相关信息会被自动销毁。
  4. 访问速度:

    • 访问堆内存中的对象实例相对较慢,因为它涉及到查找对象引用以及处理垃圾收集等过程。
    • 访问栈内存中的数据相对较快,因为栈内存由线程私有,且其数据结构简单,方便存取。

总之,堆和栈的主要区别在于内存分配、数据存储和生命周期。堆用于存储对象实例,大小可扩展,生命周期较长,访问相对较慢;而栈用于存储基本数据类型、对象引用变量和方法调用相关信息,大小固定,生命周期较短,访问相对较快。

在JVM的哪个内存区域中,内存溢出不太可能发生

在JVM中,程序计数器(Program Counter)是唯一一块不会发生内存溢出(OutOfMemoryError)的区域。

程序计数器是每个线程私有的内存区域,用于存储当前线程正在执行的Java方法的JVM字节码指令地址。如果正在执行的是本地方法,则计数器的值为空(undefined)。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

其他的内存区域,如Java堆(Heap)、栈(Stack)、元空间(Metaspace,Java 8引入,替代了以前的永久代)等,都可能发生内存溢出。例如,如果Java堆中的空闲内存不足以分配新的对象,就会抛出OutOfMemoryError;如果线程请求的栈深度超过了虚拟机所允许的最大深度,也会抛出OutOfMemoryError。

从垃圾收集(GC)的角度来看,JVM的堆内存如何分区?

从垃圾收集(Garbage Collection,GC)的角度看,Java堆(Heap)主要被划分为以下几个区域:

  1. 新生代(Young Generation):新生代是存放新创建的对象的地方。新生代又被分为三个部分:一个Eden区和两个Survivor区(Survivor 0和Survivor 1)。大部分情况下,新创建的对象首先被分配到Eden区。

  2. 老年代(Old Generation):当对象在新生代中存活时间较长,或者Survivor区无法容纳的时候,就会被移动到老年代。老年代的空间一般比新生代大,用于存放生命周期较长的对象。

  3. 持久代(Permanent Generation)或元空间(Metaspace):这部分内存主要用于存放JVM加载的类信息、常量、静态变量等数据。在Java 8中,持久代被废弃,改为使用元空间,元空间使用的是本地内存。

JVM的垃圾收集器主要根据对象所在的区域进行垃圾回收。新生代中的垃圾收集称为Minor GC,这种垃圾收集的频率较高,但每次收集的时间较短。老年代中的垃圾收集称为Major GC或Full GC,这种垃圾收集的频率较低,但每次收集的时间较长,可能会导致应用的暂停。

总的来说,从GC的角度看,Java堆主要被划分为新生代、老年代和持久代(或元空间),不同的区域对应不同的垃圾收集策略。

为什么堆内存需要划分为新生代和老年代??

将堆分为新生代和老年代是为了更高效地进行垃圾回收。这种划分基于两个观察结果,被称为“弱代假说”:

  1. 大部分对象都是朝生夕死的(Most objects soon become unreachable):许多对象创建后很快就不再被引用,因此可以被当做垃圾回收。例如,局部变量、临时数据等。

  2. 老对象引用新对象的情况比新对象引用老对象的情况要少(Old objects do not refer to young objects as much as young objects do to old objects)。

基于这两个观察结果,将堆分为新生代和老年代可以提高垃圾收集的效率:

  1. 对新生代进行频繁的小规模垃圾回收:由于大部分对象都是朝生夕死的,所以频繁地回收新生代可以及时回收大量不再使用的对象,防止它们占用过多内存。

  2. 对老年代进行较少的大规模垃圾回收:由于老年代中的对象通常有较长的生命周期,因此不需要频繁地对老年代进行垃圾回收。当进行老年代的垃圾回收时,通常需要停止应用程序,所以老年代的垃圾回收被称为"Stop-The-World"事件。

因此,将堆分为新生代和老年代,可以根据对象的生命周期采用不同的垃圾回收策略,从而提高垃圾回收的效率,减少垃圾回收对应用程序的影响。

由于内容太多,更多内容以链接形势给大家,点击进去就是答案了

16. 尽量避免内存泄漏的方法?

17. 常用的垃圾收集算法有哪些?

18. 为什么要采用分代收集算法?

19. 分代收集下的年轻代和老年代应该采用什么样的垃圾回收算法?

20. 什么是浮动垃圾?

21. 什么是内存碎片?如何解决?

22. 常用的垃圾收集器有哪些?

23. 谈谈你对 CMS 垃圾收集器的理解?

24. 谈谈你对 G1 收集器的理解?

25. 说下你对垃圾回收策略的理解/垃圾回收时机?

26. 谈谈你对内存分配的理解?大对象怎么分配?空间分配担保?

27. 说下你用过的 JVM 监控工具?

28. 如何利用监控工具调优?

29. JVM 的一些参数?

30. 谈谈你对类文件结构的理解?有哪些部分组成?

31. 谈谈你对类加载机制的了解?

32. 类加载各阶段的作用分别是什么?

33. 有哪些类加载器?分别有什么作用?

34. 类与类加载器的关系?

35. 谈谈你对双亲委派模型的理解?工作过程?为什么要使用

36. 怎么实现一个自定义的类加载器?需要注意什么?

37. 怎么打破双亲委派模型?

38. 有哪些实际场景是需要打破双亲委派模型的?

39. 谈谈你对编译期优化和运行期优化的理解?

40. 为何 HotSpot 虚拟机要使用解释器与编译器并存的架构?

41. 内存间的交互操作有哪些?需要满足什么规则?

  • 15
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值