当前位置: 首页 > article >正文

Java面试黄金宝典11

1. 什么是 JMM 内存模型

  • 定义

JMM(Java Memory Model)即 Java 内存模型,它并非真实的物理内存结构,而是一种抽象的概念。其主要作用是规范 Java 虚拟机与计算机主内存(Main Memory)之间的交互方式,目的是屏蔽不同硬件和操作系统在内存访问上的差异,确保 Java 程序在各种平台上都能获得一致的内存访问效果。

在 JMM 的体系中,线程之间的共享变量存于主内存之中。而每个线程都拥有自己的本地内存(Local Memory),这里的本地内存是一个抽象概念,它涵盖了缓存、写缓冲区、寄存器等区域。线程若要对共享变量进行操作,必须先将变量从主内存拷贝到本地内存,操作完成后再把结果刷新回主内存。

  • 原理

JMM 是基于缓存一致性协议构建的,它通过定义一系列规则来保障多线程环境下的内存可见性和有序性。举例来说,当一个线程对共享变量的值进行修改后,它需要把修改后的值刷新回主内存;而其他线程在读取该变量时,就需要从主内存中重新获取最新的值,以此来保证各个线程看到的共享变量值是一致的。

  • 要点
  1. 主内存:是所有线程共享的内存区域,用于存储共享变量。
  2. 本地内存:每个线程私有的内存区域,存储着该线程使用的共享变量的副本。
  3. 线程通信:线程之间的通信是通过主内存来完成的,线程不能直接访问其他线程的本地内存。

  • 应用

JMM 与硬件内存架构紧密相关,它在一定程度上模拟了硬件的内存访问模式。同时,JMM 为 Java 中的并发编程奠定了基础,像 volatilesynchronized 等关键字的语义都依赖于 JMM 来实现。例如,volatile 关键字通过保证变量的可见性,确保一个线程对 volatile 变量的修改能立即被其他线程看到,这背后就是 JMM 的规则在起作用。

2. JMM 中的 happens - before 原则

  • 定义

happens - before 原则是 JMM 中用于判断数据是否存在竞争、线程是否安全的重要规则。如果操作 A happens - before 操作 B,那么操作 A 的执行结果对操作 B 是可见的,并且操作 A 的执行顺序在操作 B 之前。不过需要注意的是,这里的 “顺序” 并非严格的时间先后顺序,而是强调操作结果的可见性。

  • 原理

happens - before 原则的核心在于保障多线程环境下的有序性和可见性。它并不要求前一个操作在时间上一定先于后一个操作执行,而是着重于前一个操作的结果对后一个操作是可见的。例如,在多线程环境中,可能会存在指令重排序的情况,但只要满足 happens - before 原则,就不会影响程序的正确性。

  • 要点
  1. 程序顺序规则:在一个线程内部,每个操作 happens - before 该线程中任意后续操作。这保证了单线程程序的执行顺序符合代码的编写顺序。
  2. 监视器锁规则:对一个锁的解锁操作 happens - before 随后对这个锁的加锁操作。这确保了在同一时刻只有一个线程能够访问被锁保护的代码块,并且线程在释放锁后,其他线程能够看到该线程对共享变量所做的修改。
  3. volatile 变量规则:对一个 volatile 域的写操作 happens - before 任意后续对这个 volatile 域的读操作。这保证了 volatile 变量的可见性,使得一个线程对 volatile 变量的修改能够立即被其他线程感知。
  4. 传递性:如果 A happens - before B,且 B happens - before C,那么 A happens - before C。通过传递性,可以将多个操作之间的 happens - before 关系进行扩展。

  • 应用

happens - before 原则是 JMM 的核心内容,它为 Java 程序员提供了一种简单的方式来推理多线程程序的正确性。在编写并发程序时,遵循 happens - before 原则可以有效避免数据竞争和内存可见性问题。例如,在使用线程池时,线程池的任务提交和任务执行之间就存在 happens - before 关系,开发者可以利用这一原则来确保任务执行的正确性。

3. 什么是堆中的永久代

  • 定义

在 Java 8 之前,Java 堆内存被划分为新生代、老年代和永久代。永久代主要用于存储类的元数据信息,如类的结构、方法、字段、常量池等。永久代虽然和堆内存一起进行管理,但它的垃圾回收机制与堆的其他部分有所不同。

  • 原理

永久代的设计初衷是为了存储类的元数据,因为这些数据在程序运行期间通常是固定不变的。JVM 在类加载时会将类的元数据信息存储在永久代中,直到类被卸载。由于类的元数据相对稳定,所以永久代的垃圾回收频率较低。

  • 要点
  1. 存储内容:永久代存储的是类的元数据,而不是对象实例。
  2. 大小限制:永久代有固定的大小限制,当存储的元数据超过这个限制时,就容易出现 OutOfMemoryError: PermGen space 错误。
  3. 版本变化:从 Java 8 开始,永久代被元空间(Metaspace)取代。

  • 应用

元空间与永久代的主要区别在于,元空间使用本地内存(Native Memory),而不是 JVM 堆内存。这使得元空间的大小不再受 JVM 堆大小的限制,减少了 OutOfMemoryError: PermGen space 错误的发生。例如,在一些大型的 Java 应用中,可能会加载大量的类,使用永久代时容易出现空间不足的问题,而使用元空间则可以更好地应对这种情况。

4. 什么样的对象需要回收

  • 定义

在 Java 中,当一个对象不再被任何引用指向时,该对象就成为了垃圾对象,需要被回收。判断一个对象是否为垃圾对象通常有两种方法:引用计数法和可达性分析法。

  • 原理
  1. 引用计数法:为每个对象维护一个引用计数器,当有一个新的引用指向该对象时,计数器加 1;当一个引用不再指向该对象时,计数器减 1。当计数器的值为 0 时,表明该对象没有任何引用指向它,就被认为是垃圾对象。
  2. 可达性分析法:从一组被称为 GC Roots 的对象开始,通过引用关系向下搜索,能够被搜索到的对象称为可达对象,这些对象是存活的;而不能被搜索到的对象则被认为是垃圾对象。

  • 要点
  1. 垃圾对象定义:不再被引用的对象即为垃圾对象。
  2. 方法优缺点:引用计数法实现简单,但存在循环引用的问题,即两个对象相互引用,导致它们的引用计数器永远不为 0,从而无法被回收;可达性分析法是目前主流的垃圾对象判断方法,能够有效解决循环引用的问题。

  • 应用

在 Java 中,对象的引用分为强引用、软引用、弱引用和虚引用。不同类型的引用对对象的存活时间有不同的影响。例如,强引用是最常见的引用类型,只要强引用存在,对象就不会被回收;软引用在系统内存不足时才会被回收,常用于实现缓存;弱引用在垃圾回收时会被直接回收;虚引用主要用于跟踪对象被垃圾回收的状态。

5. 什么可作为 GC Roots 的对象

  • 定义

GC Roots 是可达性分析法中作为起始点的对象集合,以下几种对象可以作为 GC Roots:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象:当一个方法被调用时,会在虚拟机栈中创建一个栈帧,栈帧中的本地变量表可能会引用一些对象,这些对象可以作为 GC Roots。
  2. 方法区中类静态属性引用的对象:类的静态属性是属于类的,而不是属于某个对象的,因此这些静态属性引用的对象可以作为 GC Roots。
  3. 方法区中常量引用的对象:常量在程序运行期间是固定不变的,常量引用的对象也可以作为 GC Roots。
  4. 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象:当 Java 程序调用 Native 方法时,会在本地方法栈中创建栈帧,栈帧中引用的对象可以作为 GC Roots。

  • 原理

从 GC Roots 开始进行可达性分析,可以确定哪些对象是可达的,哪些对象是不可达的。GC Roots 是程序中正在使用的对象,它们引用的对象也被认为是有用的,不能被回收。通过这种方式,可以确保在垃圾回收过程中,不会误回收那些还在被使用的对象。

  • 要点
  1. 起始点作用:GC Roots 是垃圾回收的起始点,通过它可以构建出对象的引用关系图。
  2. 多种类型:不同类型的 GC Roots 代表了不同的程序上下文,涵盖了虚拟机栈、方法区和本地方法栈等。

  • 应用

在某些情况下,临时对象也可能成为 GC Roots,例如在垃圾回收过程中,正在被使用的对象可能会被标记为 GC Roots,以确保在回收过程中这些对象不会被错误地回收。此外,在 Java 中,类加载器也可以作为一种特殊的 GC Roots,因为类加载器会引用类的元数据和类的实例对象。

6. 有哪些 GC 算法

常见的 GC 算法有以下几种:

  1. 标记 - 清除算法(Mark - Sweep):该算法分为两个阶段,首先标记出所有需要回收的对象,然后在标记完成后统一回收所有被标记的对象。
  2. 标记 - 整理算法(Mark - Compact):同样先标记出需要回收的对象,然后将存活的对象向一端移动,最后清理掉端边界以外的内存。
  3. 复制算法(Copying):将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
  4. 分代收集算法(Generational Collection):根据对象的存活周期将内存划分为不同的区域,一般分为新生代和老年代。新生代使用复制算法,老年代使用标记 - 清除或标记 - 整理算法。

  • 原理

不同的 GC 算法基于不同的内存使用场景和对象存活特性设计。例如,复制算法适用于对象存活率较低的场景,因为它只需要复制存活的对象,而不需要标记和清除操作;标记 - 整理算法适用于对象存活率较高的场景,因为它可以避免内存碎片的产生。

  • 要点
  1. 标记 - 清除算法:简单直接,但会产生内存碎片,随着时间的推移,可能会导致无法分配连续的大内存空间。
  2. 复制算法:实现简单,回收效率高,但需要额外的内存空间,且当对象存活率较高时,复制操作的开销会比较大。
  3. 标记 - 整理算法:可以避免内存碎片,但移动对象的操作会带来一定的性能开销。
  4. 分代收集算法:结合了不同算法的优点,根据对象的存活周期进行分区管理,是目前主流的垃圾回收算法。

  • 应用

不同的垃圾回收器采用了不同的 GC 算法组合,例如,Serial 收集器在新生代采用复制算法,在老年代采用标记 - 整理算法;CMS 收集器在老年代采用标记 - 清除算法。开发者可以根据应用程序的特点和性能需求选择合适的垃圾回收器和 GC 算法。

7. 什么是 Minor GC, Full GC

  1. Minor GC:也称为新生代 GC,指的是发生在新生代的垃圾回收动作。由于新生代中的对象大多生命周期较短,所以 Minor GC 非常频繁,一般回收速度也比较快。
  2. Full GC:也称为全局 GC,指的是发生在整个堆(包括新生代、老年代、永久代 / 元空间)的垃圾回收动作。Full GC 通常会导致应用程序出现较长时间的停顿,因为它需要回收整个堆的内存。

  • 原理

Minor GC 主要是为了回收新生代中不再使用的对象,当新生代的内存空间不足时,就会触发 Minor GC。Full GC 通常是在老年代空间不足、永久代 / 元空间不足或者调用 System.gc() 方法时触发。

  • 要点
  1. 回收区域:Minor GC 只回收新生代的内存;Full GC 回收整个堆的内存,包括新生代、老年代和永久代 / 元空间。
  2. 停顿时间:Full GC 的停顿时间比 Minor GC 长,因为它涉及的回收范围更广,操作更复杂。

  • 应用

频繁的 Full GC 会影响应用程序的性能,因此在开发过程中需要尽量避免 Full GC 的发生。可以通过调整堆内存的大小、优化对象的生命周期等方式来减少 Full GC 的频率。例如,合理设置新生代和老年代的比例,避免创建过多的大对象,及时释放不再使用的对象等。

8. 什么是 YGC

  • 定义

YGC 是 Young Generation Garbage Collection 的缩写,即新生代垃圾回收,与 Minor GC 是同一个概念。它主要负责回收新生代中的垃圾对象,由于新生代中的对象大多是朝生夕灭的,所以 YGC 发生的频率比较高。

  • 原理

当新生代中的 Eden 区或 Survivor 区内存不足时,就会触发 YGC。YGC 通常采用复制算法,将存活的对象复制到另一个 Survivor 区或老年代。

  • 要点
  1. 回收范围:YGC 只针对新生代进行垃圾回收。
  2. 性能特点:YGC 的回收速度相对较快,停顿时间较短,因为新生代中的对象存活率较低,复制操作的开销较小。

  • 应用

在监控 Java 应用程序的性能时,YGC 的频率和耗时是重要的指标。过高的 YGC 频率可能意味着新生代内存分配不合理,需要调整堆内存的大小或对象的生命周期。例如,如果 YGC 过于频繁,可以适当增大新生代的内存空间,减少对象进入老年代的概率。

9. 什么是 FGC

  • 定义

FGC 是 Full Garbage Collection 的缩写,即全局垃圾回收,与 Full GC 是同一个概念。它会对整个堆(包括新生代、老年代、永久代 / 元空间)进行垃圾回收,通常会导致应用程序出现较长时间的停顿。

  • 原理

FGC 通常在以下几种情况下触发:老年代空间不足、永久代 / 元空间不足、调用 System.gc() 方法、堆中产生了大对象等。FGC 会采用标记 - 清除或标记 - 整理算法来回收内存。

  • 要点
  1. 回收范围:FGC 会回收整个堆的内存,涉及的范围广,操作复杂。
  2. 性能影响:FGC 的停顿时间较长,会对应用程序的性能产生较大影响,因为在回收过程中,应用程序需要暂停执行。

  • 应用

为了减少 FGC 的发生,可以采取以下措施:合理调整堆内存的大小、避免创建大对象、及时释放不再使用的对象等。例如,在设计数据结构时,尽量避免使用过大的数组或集合,以减少大对象的产生。

10. 什么是 CMS

  • 定义

CMS(Concurrent Mark Sweep)是一种以获取最短回收停顿时间为目标的垃圾回收器,它基于标记 - 清除算法实现,主要针对老年代进行垃圾回收。CMS 收集器在 JDK 1.5 时期被引入,适用于对响应时间要求较高的应用程序。

  • 原理

CMS 收集器的工作过程分为四个阶段:

  1. 初始标记(Initial Mark):标记出所有直接与 GC Roots 关联的对象,这个阶段会导致应用程序出现短暂的停顿。
  2. 并发标记(Concurrent Mark):从初始标记阶段标记的对象开始,并发地遍历整个老年代,标记出所有需要回收的对象,这个阶段不会导致应用程序停顿。
  3. 重新标记(Remark):修正并发标记阶段因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段会导致应用程序出现短暂的停顿。
  4. 并发清除(Concurrent Sweep):并发地清除所有被标记的对象,这个阶段不会导致应用程序停顿。

  • 要点
  1. 目标优势:CMS 收集器的主要优点是停顿时间短,适合对响应时间要求较高的应用程序,如 Web 应用。
  2. 算法缺陷:CMS 收集器基于标记 - 清除算法,会产生内存碎片,随着时间的推移,可能会导致无法分配连续的大内存空间。
  3. 资源占用:CMS 收集器在并发标记和并发清除阶段会占用一部分 CPU 资源,可能会影响应用程序的性能,尤其是在 CPU 资源紧张的情况下。

  • 应用

由于 CMS 收集器存在内存碎片和占用 CPU 资源等问题,从 JDK 9 开始,CMS 收集器被标记为废弃,推荐使用 G1 收集器。G1 收集器结合了标记 - 整理和复制算法,避免了内存碎片的产生,并且可以更好地控制停顿时间,能够在不同的场景下提供更稳定的性能。

 

 友情提示:本文已经整理成文档,可以到如下链接免积分下载阅读

https://download.csdn.net/download/ylfhpy/90523338 


http://www.kler.cn/a/600354.html

相关文章:

  • Charles汉化步骤 charles中文版怎么用
  • 诊断过拟合的方法及解决方法
  • ZW3D二次开发_非模板表单_输入框类控件_逐字符回调
  • qt的slider样式定制
  • 从 0 到 1 构建 Python 分布式爬虫,实现搜索引擎全攻略
  • 什么是PHP伪协议
  • 如何将maltab开发的app嵌入PPT中展示并且可实时互动
  • 【QA】外观模式在Qt中有哪些应用?
  • 算法-枚 举
  • 二次封装 el-tooltip
  • 《基于Python的财务数据可视化与决策支持系统开发》开题报告
  • 从零开始学习PX4源码14(board-字符设备串口)
  • SOFABoot-09-模块隔离
  • MongoDB 配合python使用的入门教程
  • Docker学习笔记(十二)docker镜像没有vi怎么优雅的编辑文本
  • 2025最新-智慧小区物业管理系统
  • torch.nn和torch.nn.function的区别
  • 探索Google Test(gtest):C++单元测试的强大工具
  • ES聚合学习(三)
  • 常见CMS漏洞(一):WordPress