1. 什么是 JMM 内存模型
JMM(Java Memory Model)即 Java 内存模型,它并非真实的物理内存布局,而是一种抽象的概念。其主要作用是规范 Java 假造机与盘算机主内存(Main Memory)之间的交互方式,目标是屏蔽不同硬件和操作体系在内存访问上的差别,确保 Java 步调在各种平台上都能获得同等的内存访问效果。
在 JMM 的体系中,线程之间的共享变量存于主内存之中。而每个线程都拥有自己的本地内存(Local Memory),这里的本地内存是一个抽象概念,它涵盖了缓存、写缓冲区、寄存器等区域。线程若要对共享变量举行操作,必须先将变量从主内存拷贝到本地内存,操作完成后再把效果刷新回主内存。
JMM 是基于缓存同等性协议构建的,它通过界说一系列规则来保障多线程情况下的内存可见性和有序性。举例来说,当一个线程对共享变量的值举行修改后,它必要把修改后的值刷新回主内存;而其他线程在读取该变量时,就必要从主内存中重新获取最新的值,以此来包管各个线程看到的共享变量值是同等的。
- 主内存:是所有线程共享的内存区域,用于存储共享变量。
- 本地内存:每个线程私有的内存区域,存储着该线程利用的共享变量的副本。
- 线程通讯:线程之间的通讯是通过主内存来完成的,线程不能直接访问其他线程的本地内存。
JMM 与硬件内存架构精密相关,它在肯定程度上模拟了硬件的内存访问模式。同时,JMM 为 Java 中的并发编程奠定了基础,像 volatile、synchronized 等关键字的语义都依赖于 JMM 来实现。例如,volatile 关键字通过包管变量的可见性,确保一个线程对 volatile 变量的修改能立即被其他线程看到,这背后就是 JMM 的规则在起作用。
2. JMM 中的 happens - before 原则
happens - before 原则是 JMM 中用于判断命据是否存在竞争、线程是否安全的紧张规则。如果操作 A happens - before 操作 B,那么操作 A 的执行效果对操作 B 是可见的,并且操作 A 的执行顺序在操作 B 之前。不外必要注意的是,这里的 “顺序” 并非严格的时间先后顺序,而是强调操作效果的可见性。
happens - before 原则的焦点在于保障多线程情况下的有序性和可见性。它并不要求前一个操作在时间上肯定先于后一个操作执行,而是着重于前一个操作的效果对后一个操作是可见的。例如,在多线程情况中,大概会存在指令重排序的情况,但只要满足 happens - before 原则,就不会影响步调的正确性。
- 步调顺序规则:在一个线程内部,每个操作 happens - before 该线程中恣意后续操作。这包管了单线程步调的执行顺序符合代码的编写顺序。
- 监视器锁规则:对一个锁的解锁操作 happens - before 随后对这个锁的加锁操作。这确保了在同一时刻只有一个线程可以或许访问被锁掩护的代码块,并且线程在释放锁后,其他线程可以或许看到该线程对共享变量所做的修改。
- volatile 变量规则:对一个 volatile 域的写操作 happens - before 恣意后续对这个 volatile 域的读操作。这包管了 volatile 变量的可见性,使得一个线程对 volatile 变量的修改可以或许立即被其他线程感知。
- 通报性:如果 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 在类加载时会将类的元数据信息存储在永久代中,直到类被卸载。由于类的元数据相对稳固,以是永久代的垃圾回收频率较低。
- 存储内容:永久代存储的是类的元数据,而不是对象实例。
- 巨细限定:永久代有固定的巨细限定,当存储的元数据超过这个限定时,就容易出现 OutOfMemoryError: PermGen space 错误。
- 版本变化:从 Java 8 开始,永久代被元空间(Metaspace)代替。
元空间与永久代的主要区别在于,元空间利用本地内存(Native Memory),而不是 JVM 堆内存。这使得元空间的巨细不再受 JVM 堆巨细的限定,减少了 OutOfMemoryError: PermGen space 错误的发生。例如,在一些大型的 Java 应用中,大概会加载大量的类,利用永久代时容易出现空间不足的问题,而利用元空间则可以更好地应对这种情况。
4. 什么样的对象必要回收
在 Java 中,当一个对象不再被任何引用指向时,该对象就成为了垃圾对象,必要被回收。判断一个对象是否为垃圾对象通常有两种方法:引用计数法和可达性分析法。
- 引用计数法:为每个对象维护一个引用计数器,当有一个新的引用指向该对象时,计数器加 1;当一个引用不再指向该对象时,计数器减 1。当计数器的值为 0 时,表明该对象没有任何引用指向它,就被以为是垃圾对象。
- 可达性分析法:从一组被称为 GC Roots 的对象开始,通过引用关系向下搜刮,可以或许被搜刮到的对象称为可达对象,这些对象是存活的;而不能被搜刮到的对象则被以为是垃圾对象。
- 垃圾对象界说:不再被引用的对象即为垃圾对象。
- 方法优缺点:引用计数法实现简单,但存在循环引用的问题,即两个对象相互引用,导致它们的引用计数器永久不为 0,从而无法被回收;可达性分析法是目前主流的垃圾对象判断方法,可以或许有用办理循环引用的问题。
在 Java 中,对象的引用分为强引用、软引用、弱引用和虚引用。不同类型的引用对对象的存活时间有不同的影响。例如,强引用是最常见的引用类型,只要强引用存在,对象就不会被回收;软引用在体系内存不足时才会被回收,常用于实现缓存;弱引用在垃圾回收时会被直接回收;虚引用主要用于跟踪对象被垃圾回收的状态。
5. 什么可作为 GC Roots 的对象
GC Roots 是可达性分析法中作为起始点的对象聚集,以下几种对象可以作为 GC Roots:
- 假造机栈(栈帧中的本地变量表)中引用的对象:当一个方法被调用时,会在假造机栈中创建一个栈帧,栈帧中的本地变量表大概会引用一些对象,这些对象可以作为 GC Roots。
- 方法区中类静态属性引用的对象:类的静态属性是属于类的,而不是属于某个对象的,因此这些静态属性引用的对象可以作为 GC Roots。
- 方法区中常量引用的对象:常量在步调运行期间是固定稳定的,常量引用的对象也可以作为 GC Roots。
- 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象:当 Java 步调调用 Native 方法时,会在本地方法栈中创建栈帧,栈帧中引用的对象可以作为 GC Roots。
从 GC Roots 开始举行可达性分析,可以确定哪些对象是可达的,哪些对象是不可达的。GC Roots 是步调中正在利用的对象,它们引用的对象也被以为是有用的,不能被回收。通过这种方式,可以确保在垃圾回收过程中,不会误回收那些还在被利用的对象。
- 起始点作用:GC Roots 是垃圾回收的起始点,通过它可以构建出对象的引用关系图。
- 多种类型:不同类型的 GC Roots 代表了不同的步调上下文,涵盖了假造机栈、方法区和本地方法栈等。
在某些情况下,临时对象也大概成为 GC Roots,例如在垃圾回收过程中,正在被利用的对象大概会被标记为 GC Roots,以确保在回收过程中这些对象不会被错误地回收。此外,在 Java 中,类加载器也可以作为一种特殊的 GC Roots,由于类加载器会引用类的元数据和类的实例对象。
6. 有哪些 GC 算法
常见的 GC 算法有以下几种:
- 标记 - 扫除算法(Mark - Sweep):该算法分为两个阶段,起首标记出所有必要回收的对象,然后在标记完成后统一回收所有被标记的对象。
- 标记 - 整理算法(Mark - Compact):同样先标记出必要回收的对象,然后将存活的对象向一端移动,最后清算掉端边界以外的内存。
- 复制算法(Copying):将可用内存按容量划分为巨细相当的两块,每次只利用此中一块。当这一块内存用完了,就将还存在世的对象复制到另外一块上面,然后再把已利用过的内存空间一次清算掉。
- 分代网络算法(Generational Collection):根据对象的存活周期将内存划分为不同的区域,一般分为新生代和老年代。新生代利用复制算法,老年代利用标记 - 扫除或标记 - 整理算法。
不同的 GC 算法基于不同的内存利用场景和对象存活特性设计。例如,复制算法适用于对象存活率较低的场景,由于它只必要复制存活的对象,而不必要标记和扫除操作;标记 - 整理算法适用于对象存活率较高的场景,由于它可以避免内存碎片的产生。
- 标记 - 扫除算法:简单直接,但会产生内存碎片,随着时间的推移,大概会导致无法分配连续的大内存空间。
- 复制算法:实现简单,回收服从高,但必要额外的内存空间,且当对象存活率较高时,复制操作的开销会比较大。
- 标记 - 整理算法:可以避免内存碎片,但移动对象的操作会带来肯定的性能开销。
- 分代网络算法:结合了不同算法的优点,根据对象的存活周期举行分区管理,是目前主流的垃圾回收算法。
不同的垃圾回收器采用了不同的 GC 算法组合,例如,Serial 网络器在新生代采用复制算法,在老年代采用标记 - 整理算法;CMS 网络器在老年代采用标记 - 扫除算法。开辟者可以根据应用步调的特点和性能需求选择合适的垃圾回收器和 GC 算法。
7. 什么是 Minor GC, Full GC
- Minor GC:也称为新生代 GC,指的是发生在新生代的垃圾回收动作。由于新生代中的对象大多生命周期较短,以是 Minor GC 非常频仍,一般回收速率也比较快。
- Full GC:也称为全局 GC,指的是发生在整个堆(包括新生代、老年代、永久代 / 元空间)的垃圾回收动作。Full GC 通常会导致应用步调出现较长时间的停顿,由于它必要回收整个堆的内存。
Minor GC 主要是为了回收新生代中不再利用的对象,当新生代的内存空间不足时,就会触发 Minor GC。Full GC 通常是在老年代空间不足、永久代 / 元空间不足或者调用 System.gc() 方法时触发。
- 回收区域:Minor GC 只回收新生代的内存;Full GC 回收整个堆的内存,包括新生代、老年代和永久代 / 元空间。
- 停顿时间: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 区或老年代。
- 回收范围:YGC 只针对新生代举行垃圾回收。
- 性能特点:YGC 的回收速率相对较快,停顿时间较短,由于新生代中的对象存活率较低,复制操作的开销较小。
在监控 Java 应用步调的性能时,YGC 的频率和耗时是紧张的指标。过高的 YGC 频率大概意味着新生代内存分配不公道,必要调整堆内存的巨细或对象的生命周期。例如,如果 YGC 过于频仍,可以适当增大新生代的内存空间,减少对象进入老年代的概率。
9. 什么是 FGC
FGC 是 Full Garbage Collection 的缩写,即全局垃圾回收,与 Full GC 是同一个概念。它会对整个堆(包括新生代、老年代、永久代 / 元空间)举行垃圾回收,通常会导致应用步调出现较长时间的停顿。
FGC 通常在以下几种情况下触发:老年代空间不足、永久代 / 元空间不足、调用 System.gc() 方法、堆中产生了大对象等。FGC 会采用标记 - 扫除或标记 - 整理算法来回收内存。
- 回收范围:FGC 会回收整个堆的内存,涉及的范围广,操作复杂。
- 性能影响:FGC 的停顿时间较长,会对应用步调的性能产生较大影响,由于在回收过程中,应用步调必要暂停执行。
为了减少 FGC 的发生,可以接纳以下措施:公道调整堆内存的巨细、避免创建大对象、实时释放不再利用的对象等。例如,在设计数据布局时,尽量避免利用过大的数组或聚集,以减少大对象的产生。
10. 什么是 CMS
CMS(Concurrent Mark Sweep)是一种以获取最短回收停顿时间为目标的垃圾回收器,它基于标记 - 扫除算法实现,主要针对老年代举行垃圾回收。CMS 网络器在 JDK 1.5 时期被引入,适用于对响应时间要求较高的应用步调。
CMS 网络器的工作过程分为四个阶段:
- 初始标记(Initial Mark):标记出所有直接与 GC Roots 关联的对象,这个阶段会导致应用步调出现短暂的停顿。
- 并发标记(Concurrent Mark):从初始标记阶段标记的对象开始,并发地遍历整个老年代,标记出所有必要回收的对象,这个阶段不会导致应用步调停顿。
- 重新标记(Remark):修正并发标记阶段因用户步调继承运行而导致标记产生变更的那一部分对象的标记记录,这个阶段会导致应用步调出现短暂的停顿。
- 并发扫除(Concurrent Sweep):并发地扫除所有被标记的对象,这个阶段不会导致应用步调停顿。
- 目标上风:CMS 网络器的主要优点是停顿时间短,恰当对响应时间要求较高的应用步调,如 Web 应用。
- 算法缺陷:CMS 网络器基于标记 - 扫除算法,会产生内存碎片,随着时间的推移,大概会导致无法分配连续的大内存空间。
- 资源占用:CMS 网络器在并发标记和并发扫除阶段会占用一部分 CPU 资源,大概会影响应用步调的性能,尤其是在 CPU 资源紧张的情况下。
由于 CMS 网络器存在内存碎片和占用 CPU 资源等问题,从 JDK 9 开始,CMS 网络器被标记为废弃,保举利用 G1 网络器。G1 网络器结合了标记 - 整理和复制算法,避免了内存碎片的产生,并且可以更好地控制停顿时间,可以或许在不同的场景下提供更稳固的性能。
友情提示:本文已经整理成文档,可以到如下链接免积分下载阅读
https://download.csdn.net/download/ylfhpy/90523338
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |