亦直问JVM,百度笔试题百度校招面试经验

本文详细介绍了JVM的内存区域,包括堆、方法区、虚拟机栈、本地方法栈和程序计数器,以及类加载过程的加载、验证、准备、解析和初始化阶段。此外,还探讨了垃圾回收的引用计数、标记清除、标记复制和标记整理算法,以及CMS和G1垃圾收集器的特点。文章还讨论了触发Full GC的情况以及如何避免Stop-The-World(STW)现象。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    *   [6.10.3 OOM种类](about:blank#6103_OOM_334)

    *   [6.10.4 逃逸分析](about:blank#6104__364)

    *   [6.10.5 可达性](about:blank#6105__375)

    *   [6.10.6 JVM何时需要调优?](about:blank#6106_JVM_387)

    *   [6.10.7 JVM性能检测工具](about:blank#6107_JVM_397)

    *   [6.10.7 内存泄漏](about:blank#6107__404)

    *   [6.10.8 CPU 100%排查指令](about:blank#6108_CPU_100_412)

6.1 JVM的堆、栈、方法区分别指什么?


6.1.1 堆(Heap 线程共享)

被所有线程共享的一块内存区域,在虚拟机启动的时候创建,用于存放对象实例。对可以按照可扩展来实现(通过-Xmx 和-Xms 来控制)当队中没有内存可分配给实例,也无法再扩展时,则抛出OutOfMemoryError(OOM)异常。

6.1.2 方法区(线程共享)

被所有方法线程共享的一块内存区域。用于存储已经被虚拟机加载的类信息,常量,静态变量等。这个区域的内存回收目标主要针对常量池的回收和堆类型的卸载。

6.1.3 虚拟机栈(VM Stack 线程私有)

线程私有的。每个方法在执行的时候也会创建一个栈帧,存储了局部变量,操作数,动态链接,方法返回地址。每个方法从调用到执行完毕,对应一个栈帧在虚拟机栈中的入栈和出栈。通常所说的栈,一般是指在虚拟机栈中的局部变量部分。局部变量所需内存在编译期间完成分配,如果线程请求的栈深度大于虚拟机所允许的深度,则StackOverflowError。如果虚拟机栈可以动态扩展,扩展到无法申请足够的内存,则OutOfMemoryError。

6.1.4 本地方法栈(线程私有)

和虚拟机栈类似,主要为虚拟机使用到的Native方法服务。也会抛出StackOverflowError 和OutOfMemoryError。

6.1.5 程序计数器(线程私有)

是当前线程锁执行字节码的行号指示器,每条线程都有一个独立的程序计数器,这类内存也称为“线程私有”的内存。正在执行java方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果是Native方法,则为空。

在这里插入图片描述

总结:

1. 堆是线程共享的内存区域,栈是线程独享的内存区域。

2. 堆中主要存放对象实例,栈中主要存放各种基本数据类型、对象的引用。

堆内存是线程共享的不完全正确,具体原因查看这篇文章:https://mp.weixin.qq.com/s/Wws24Fhg1nH4dHvtcFYi2g

6.2 描述一下类加载过程?


系统加载 Class 类型的文件主要三步:加载->连接->初始化。连接过程又可分为三步:验证->准备->解析

6.2.1 加载

类加载过程的第一步,主要完成下面3件事情:

  1. 通过全类名获取定义此类的二进制字节流

  2. 将字节流所代表的静态存储结构转换为方法区的运行时数据结构

  3. 在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口

加载阶段和连接阶段的部分内容是交叉进行的,加载阶段尚未结束,连接阶段可能就已经开始了。

总结:通过类名获取二进制字节流,将静态存储结构转换为方法区运行时数据结构,内存中生成Class对象。

6.2.2 验证

总结:验证Class文件格式规范,描述信息符合Java语言规范,程序语义符合规范,确保解析动作正确运行。

6.2.3 准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:

这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在 Java 堆中。

这里所设置的初始值"通常情况"下是数据类型默认的零值(如0、0L、null、false等),比如我们定义了public static int value=111 ,那么 value 变量在准备阶段的初始值就是 0 而不是111(初始化阶段才会复制)。特殊情况:比如给 value 变量加上了 fianl 关键字public static final int value=111 ,那么准备阶段 value 的值就被复制为 111。

6.2.4 解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。

符号引用就是一组符号来描述目标,可以是任何字面量。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。在程序实际运行时,只有符号引用是不够的,举个例子:在程序执行方法时,系统需要明确知道这个方法所在的位置。Java 虚拟机为每个类都准备了一张方法表来存放类中所有的方法。当需要调用一个类的方法的时候,只要知道这个方法在方发表中的偏移量就可以直接调用该方法了。通过解析操作符号引用就可以直接转变为目标方法在类中方法表的位置,从而使得方法可以被调用。

综上,解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,也就是得到类或者字段、方法在内存中的指针或者偏移量。

6.2.5 初始化

初始化是类加载的最后一步,也是真正执行类中定义的 Java 程序代码(字节码),初始化阶段是执行类构造器 ()方法的过程。

对于() 方法的调用,虚拟机会自己确保其在多线程环境中的安全性。因为 () 方法是带锁线程安全,所以在多线程环境下进行类初始化的话可能会引起死锁,并且这种死锁很难被发现。

对于初始化阶段,虚拟机严格规范了有且只有5种情况下,必须对类进行初始化:

  1. 当遇到 new 、 getstatic、putstatic或invokestatic 这4条直接码指令时,比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。

  2. 使用 java.lang.reflect 包的方法对类进行反射调用时 ,如果类没初始化,需要触发其初始化。

  3. 初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。

  4. 当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。

  5. 当使用 JDK1.7 的动态动态语言时,如果一个 MethodHandle 实例的最后解析结构为 REF_getStatic、REF_putStatic、REF_invokeStatic、的方法句柄,并且这个句柄没有初始化,则需要先触发器初始化。

6.3 什么是双亲委派原则?(常问)


即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派该父类加载器的 loadClass() 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader 中。当父类加载器无法处理时,才由自己来处理。当父类加载器为null时,会使用启动类加载器 BootstrapClassLoader 作为父类加载器。

image.png

双亲委派模型避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类。

即父类加载,不重复加载。

6.4 对象在堆上的分配原则(分代回收机制)?(问JVM必问)


大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。

image.png

在这里插入图片描述

6.5 描述一下JVM的垃圾回收机制?(常问)


6.5.1 引用计数算法

给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。

这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。

6.5.2 标记清除

该算法分为“标记”和“清除”阶段:首先标记出所有不需要回收的对象在标记完成后统一回收掉所有没有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。适用于对象存活比较多的时候适用、老年代。这种垃圾收集算法会带来几个明显的问题:

  1. 提前GC

  2. 碎片空间

  3. 扫描了两次:标记存活对象;清除没有标记的对象

image.png

6.5.3 标记复制算法

为了解决效率问题,“标记-复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。

适合场景:

  • 存活对象少 比较高效

  • 扫描了整个空间(标记存活对象并复制异动)

  • 适合年轻代

缺点:

  • 需要空闲空间

  • 需要复制移动对象

image.png

6.5.4 标记整理算法

根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。

image.png

6.6 有哪些常见的垃圾回收器?简单介绍一下


6.6.1 CMS

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。

CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。

从名字中的Mark Sweep这两个词可以看出,CMS 收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:

  1. 初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快;

  2. 并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。

  3. 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。

  4. 并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。

image.png

从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:并发收集、低停顿。但是它有下面三个明显的缺点:

  1. 对 CPU 资源敏感

  2. 无法处理浮动垃圾

  3. 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生

6.6.2 G1

G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征。

被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备以下特点:

  1. 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。

  2. 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。

  3. 空间整合:与 CMS 的“标记-清理”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于“标记-复制”算法实现的。

  4. 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。

在这里插入图片描述

G1 收集器的运作大致分为以下几个步骤:

  1. 初始标记:stw从gc root 开始直接可达的对象

  2. 并发标记:gc root 对对象进行可达性分析 找出存活对象(可达性分析算法)

  3. 最终标记

  4. 筛选回收:根据用户期待的gc停顿时间指定回收计划

G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来) 。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。

回收模式:

1. young gc

Young GC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。在这种情况下,Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间。Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。最终Eden空间的数据为空,GC停止工作,应用线程继续执行。

2. mixed gc

Mix GC不仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的老年代分区。

它的GC步骤分2步:


1. 全局并发标记(global concurrent marking)

2. 拷贝存活对象(evacuation) 

6.6.3 CMS和G1的区别

  1. G1分区域 每个区域是有老年代概念的,但是CMS以整个区域为单位收集

  2. G1回收后马上合并空闲内存,CMS在STW的时候做

6.6.4 内存区域设置

  1. XX:G1HeapRegionSize: 设置G1收集器一个Region的大小。取值范围从1M到32M,且是2的指数。如果不设定,那么G1会根据Heap大小自动决定。

  2. 复制成活对象到一个区域,暂停所有线程

6.7 什么是三色标记算法?


提到并发标记,我们不得不了解并发标记的三色标记算法。它是描述追踪式回收器的一种有用的方法,利用它可以推演回收器的正确性。 首先,我们将对象分成三种类型的。

  • 黑色:根对象,或者该对象与它的子对象都被扫描

  • 灰色:对象本身被扫描,但还没扫描完该对象中的子对象

  • 白色:未被扫描对象,扫描完成所有对象之后,最终为白色的为不可达对象,即垃圾对象

当GC开始扫描对象时,按照如下图步骤进行对象的扫描:

  1. 根对象被置为黑色,子对象被置为灰色。

image.png

  1. 继续由灰色遍历,将已扫描了子对象的对象置为黑色。

image.png

  1. 遍历了所有可达的对象后,所有可达的对象都变成了黑色。不可达的对象即为白色,需要被清理。

image.png

写入屏障(在进行并发标记时若有对象的增删):

当黑色对象直接引用了一个白色对象后,我们就将这个黑色对象记录下来,在扫描完成后,重新对这个黑色对象扫描,这个就是增量更新(Incremental Update)

当删除了灰色对象到白色对象的直接或间接引用后,就将这个灰色对象记录下来,再以此灰色对象为根,重新扫描一次。这个就是原始快照(Snapshot At TheBeginning,SATB)

6.8 什么时候会触发Full GC?


6.8.1 System.gc()

System.gc()方法的调用。此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过-XX:+ DisableExplicitGC来禁止RMI(Java远程方法调用)调用System.gc()。

6.8.2 老年代写满

老年代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出错误:java.lang.OutOfMemoryError: Java heap space 。为避免以上两种状况引起的FullGC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。

6.8.3 持久代空间不足

Permanet Generation中存放的为一些class的信息等,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出错误信息:java.lang.OutOfMemoryError: PermGen space 。为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。

6.9 Minor GC、Major GC和Full GC之间的区别


  1. Minor GC: 从年轻代空间(包括 Eden 和 Survivor 区域) 回收内存被称为 Minor GC。

  2. Major GC: Major GC 是清理老年代

  3. Full GC: Full GC 是清理整个堆空间—包括年轻代和老年代

6.9 什么时STW?如何避免STW?

Java高频面试专题合集解析:

阿里Java岗面试百题:Spring 缓存 JVM 微服务 数据库 RabbitMQ等

当然在这还有更多整理总结的Java进阶学习笔记和面试题未展示,其中囊括了Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构资料和完整的Java架构学习进阶导图!

CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】阿里Java岗面试百题:Spring 缓存 JVM 微服务 数据库 RabbitMQ等

更多Java架构进阶资料展示

阿里Java岗面试百题:Spring 缓存 JVM 微服务 数据库 RabbitMQ等

阿里Java岗面试百题:Spring 缓存 JVM 微服务 数据库 RabbitMQ等

** 回收内存被称为 Minor GC。

  1. Major GC: Major GC 是清理老年代

  2. Full GC: Full GC 是清理整个堆空间—包括年轻代和老年代

6.9 什么时STW?如何避免STW?

Java高频面试专题合集解析:

[外链图片转存中…(img-bjlw4TkJ-1631094114563)]

当然在这还有更多整理总结的Java进阶学习笔记和面试题未展示,其中囊括了Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构资料和完整的Java架构学习进阶导图!

CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】[外链图片转存中…(img-IVRr7r2h-1631094114564)]

更多Java架构进阶资料展示

[外链图片转存中…(img-Qtz8QXye-1631094114566)]

[外链图片转存中…(img-Df63P76n-1631094114567)]

阿里Java岗面试百题:Spring 缓存 JVM 微服务 数据库 RabbitMQ等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值