IT评测·应用市场-qidao123.com

标题: 【面试】JVM [打印本页]

作者: 李优秀    时间: 2025-3-13 23:19
标题: 【面试】JVM
1、JVM 内存布局

根据Java虚拟机规范的界说,JVM的运行时内存区域注要由堆、虚拟机栈、本地方法栈、方法区和程序计数器以及运行时常量池组成。此中堆、方法区以及运行时常量池是线程之间共享的区域,而栈(本地方法栈+虚拟机栈)、程序计数器都是线程独享的。
演变过程

JVM 程序计数器
用于记录虚以机正在执行的字节码指令的地址。它是线程私有的,为每个线程维护一个独立的程序计数器,用于指示下一条将要被执行的字节码指令的位置。它包管线程执行一个字节码指令以后,才会去执行下一个字节码指令。
在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时间能够知道该线程前次运行到哪儿了。在线程中程序计数器私有重要是为了线程切换后能规复到精确的执行位置。以是程序计数器一定是线程私有的。
JVM 虚拟机栈

JVM中的方法栈是线程私有的,每一个方法的调用会在方法栈中加入一个栈帧,比如这样启动 main 方法
  1. public static void main(String[] args) {
  2.     methodA();
  3. }
  4. public static void methodA() {
  5.         int a = 0;
  6.         int b = a + 3;
  7.         methodB();
  8. }
  9. public static void methodB() {
  10. }
复制代码
栈中压入 main 方法的栈帧,执行到 methodA 方法,栈中压入 methodA 方法的栈帧,执行到 methodB 方法,栈中压入 methodB 方法的栈帧,每个方法执行完成之后,这个方法所对应的栈帧就会出栈,每个栈帧中大概存储这五个内容:局部变量表(存储局部变量的空间)、操纵数栈(线程执行时使用到的数据存储空间)、动态链接(方法区的引用,例如类信息,常量、静态变量)、返回地址(存储这个方法被调用的位置,由于方法执行后还需要到方法被调用的位置)、附加信息(增长的一些规范里面没有的信息,可以添加自己的附加信息),这就是栈和栈帧。
本地方法栈
和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。

堆是存储对象实例的运行时内存区域。它是虚拟机运行时的内存总体的最大的一块,也一直占据着虚拟机内存总量的一大部分。Java堆由Java虚拟机管理,用于存放对象实例,几乎全部的对象实例都要在上面分配内存。别的,Java堆还用于垃圾接纳,虚拟机发现没有被引用的对象时,就会对堆中对象进行垃圾接纳,以开释内存空间。
现代垃圾收集器大部分都基于分代收集理论设计,堆空间细分为:
Java 7及之前堆内存逻辑上分为三部分:新生代+老年代+永世代,Java 8及之后堆内存逻辑上分为三部分:新生代+老年代+元空间

设置堆巨细与OOM
Java堆区用于存储Java对象实例,那么堆的巨细在JVM启动时就已经设定好了,可以通过选项"-Xmx"和"-Xms"来进行设置

一旦堆区中的内存巨细凌驾"-Xmx"所指定的最大内存时,将会抛出OutOfMemoryError异常。
通常会将-Xms和-Xmx两个参数设置相同的值,其目标是为了能够在java垃圾接纳机制清理完堆区后不需要重新分隔盘算堆区的巨细,从而进步性能。
默认情况下,初始内存巨细:物理电脑内存巨细/ 64 最大内存巨细:物理电脑内存巨细/4。
注意:设置的堆巨细不包含元空间(或永世代)
TLAB
先来看对象的创建过程,为对象分配空间的任务等同于在java堆中划分一块巨细确定的内存出来,假设java堆空间内存是绝对规整的,全部使用过的内存都放在一边,没有使用过的内存放在另一半,中心放着一个指针作为分界点的指示器,当分配内存就仅仅是把指针想空闲的空间移动一段与对象巨细相当的距离,这种分配方法称为指针碰撞(Bump The Pointer)。
对象创建在虚拟机中黑白常频繁的活动,纵然仅仅修改一个指针所指向的位置,在并发情况下也并不是线程安全的,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又使用了原来的指针来分配内存的情况。
解决方案其一就是使用本地线程分配缓冲(TLAB),对Eden区继续进行划分,JVM为每个线程分配了一个私有缓存区域,它包含在Eden区内,即每个线程在java堆中预先分配一小块内存,哪个线程需要分配内存,就在哪个线程的本地缓冲区中分配,当本地缓冲使用完,分配新的缓存区时才需要同步锁定。

尽管不是全部对象实例都能在TLAB中成功分配内存,但是JVM确定是将TLAB作为内存分配的首选,可以通过选项 -XX:UseTLAB 设置是否开启TLAB空间,默认的情况下TLAB占用的内存非常小,仅占用Eden空间的1%。
堆的内存分配过程
方法区
用于存诸已被加载的类信息、常量、静态变量、即时编译后的代码等数据的内存区域。每加载一个类,方法区就会分配一定的内存空间,用于存储该类的相干信息,这部分空间随着需要而动态变革。方法区的详细实现形式可以有多种,比如堆、永世代、元空间等。

运行时常量池
是方法区的一部分。用于存储编译阶段生成的信息,重要有字面量和符号引用常量两类。此中字面量包括了文本字符串、被声明final的常量值、根本数据范例的值和其他。此中符号引用常量包括了类的全限定名称、字段的名称和描述符、方法的名称和描述符。
堆和栈的区别
2、Java 的堆是如何分代的

Java的堆内存分代是指将差别生命周期的堆内存对象存储在差别的堆内存区域中,这里的差别的堆内存区域被界说为"代”。这样做有助于提升垃圾接纳的效率,由于这样的话就可以为差别的"代"设置差别的接纳策略。
一样平常来说,Java中的大部分对象都是朝生夕死的,同时也有一部分对象会长期存在。由于如果把这两部分对象放
到一起分析和接纳,这样效率实在是太低了。通过将差别时期的对象存储在差别的内存池中,就可以节省名贵的时
间和空间,从而改善体系的性能。
Java的堆由新生代(Young Generation)和老年代(Old Generation)组成。新生代存放新分配的对象,老年代存放长期存在的对象。新生代(Young)由年轻区(Eden)、Survivor区组成(From Survivor、To Survivor)。默认情况下,新生代的Eden区和Survivorl区的空间巨细比例是8:2,可以通过-X:SurvivorRatio参数调整。

对象的分代晋升
一样平常情况下,对象将在新生代进行分配,起首会实验在Eden区分配对象,当Eden内存耗尽,无法满意新的对象分
配请求时,将触发新生代的GC(Young GC、MinorGC),在新生代的GC过程中,没有被接纳的对象会从Eden区被
般运到Survivo区,这个过程通常被称为"晋升"。
同样的,对象也可能会晋升到老年代,触发条件重要看对象的巨细和年龄。对象进入老年代的条件有三个,满意一
个就会进入到老年代:
什么是永世代
永世代(Permanent Generation)是HotSpot)虚拟机在从前版本中使用的一个永世内存区域,是VM中垃圾收集堆之外的另一个内存区域,它重要用来实现方法区的,此中存储了Class类信息、常量池以及静态变量等数据。
Java8以后,永世代被重构为元空间(MetaSpace)。但是,和新生代、老年代一样,永世代也是可能会发生GC的。而且,永世代也是有可能导致内存益出。只要永世代的内存分配凌驾限定指定的最大值,就会出现内存溢出。
3、如果 YoungGC 存活的对象所需要的空间比 Survivor 区域的空间大怎么办

毕竟一块Survivor区域的比例只是年轻的10%而已。这时间就需要把对象移动到老年代。
空间分配包管机制
如果Survivor区域的空间不够,就要分配给老年代,也就是说,老年代起到了一个兜底的作用。但是,老年代也是
可能空间不足的。以是,在这个过程中就需要做一次空间分配包管(CMS):

4、YoungGC 和 FullGC 的触发条件是什么

YoungGC的触发条件比较简单,那就是当年轻代中的eden区分配满的时间就会触发。
FullGC的触发条件比较复杂也比较多,重要以下几种:

5、什么是 Stop The World

Java中Stop-The-Vorld机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他全部线程都被挂起。这是
Java中一种全局停息现象,全局停顿,全部java代码制止,native代码可以执行,但不能与JVM交互。
不管选择哪种GC算法,stop-the-world都是不能彻底避免的,只能只管低落STW的时长。
为什么需要STW呢
起首,如果不停息用户线程,就意味着期间会不停有垃圾产生,永远也清理不干净。其次,用户线程的运行必然会导致对象的引用关系发生改变,这就会导致两种情况:漏标和错标。

6、JVM 如何判断对象是否存活

JVM有两种算法来判断对象是否存活,分别是引用计数法和可达性分析算法

但是,并不是说当进行完可达性分析算法后,即可证明某对象可以被GC。对象是否存活,需要两次标志:
7、JVM 有哪些垃圾接纳算法

① 标志 - 清除算法(Tracing Collector)
标志-清除 算法是最底子的收集算法,它是由 标志 和 清除 两个步调组成的。第一步是标志存活的对象,第二步是清除没有被标志的垃圾对象。

该算法的优点是当存活对象比较多的时间,性能比较高,由于该算法只需要处理待接纳的对象,而不需要处理存活的对象。但是缺点也很显着,清除之后会产生大量不一连的内存碎片。导致之后程序在运行时需要分配较大的对象时,无法找到富足的一连内存。
② 标志 - 整理算法(Compacting Collector)
上述的 标志-清除 算法会产生内存区域使用的间断,以是为了将内存区域尽可能地一连使用, 标志-整理 算法应运而生。标志-整理 算法也是由两步组成,标志 和 整理。
和标志清除算法一样,先进行对象的标志,通过GC Roots节点扫描存活对象进行标志,将全部存活对象往一端空闲空间移动,按照内存地址依次排序,并更新对应引用的指针,然后清理末了内存地址以外的全部内存空间。

但是同样,标志整理算法也有它的缺点,一方面它要标志全部存活对象,另一方面还添加了对象的移动操纵以及更新引用地址的操纵,因此标志整理算法具有更高的使用本钱。
③ 复制算法(Copying Collector)
无论是标志-清除算法照旧垃圾-整理算法,都会涉及句柄的开销或是面对碎片化的内存接纳,以是,复制算法 出现了。
复制算法将内存区域均分为了两块(记为S0和S1),而每次在创建对象的时间,只使用此中的一块区域(例如S0),当S0使用完之后,便将S0上面存活的对象全部复制到S1上面去,然后将S0全部清理掉。复制算法重要被应用于新生代,它将内存分为巨细相同的两块,每次只使用此中的一块。在恣意时间点,全部动态分配的对象都只能分配在此中一个内存空间,而另外一个内存空间则是空闲的。

但是缺点也是很显着的,可用的内存减小了一半,存在内存浪费的情况。以是 复制算法 一样平常会用于对象存活时间比较短的区域,例如 年轻代,而存活时间比较长的 老年代 是不适合的,由于老年代存在大量存活时间长的对象,采用复制算法的时间会要求复制的对象较多,效率也就急剧降落,以是老年代一样平常会使用上文提到的 标志-整理算法。
单纯的从时间黑白上面来看:标志-清除 < 标志-复制 < 标志-整理。单纯从结果来看:标志-整理 > 标志-复制 >= 标志-清除
8、什么是三色标志算法

三色标志算法是一种垃圾接纳的标志算法,它可以让JVM不发生或仅短时间发生STW(Stop The World),从而达到清除JVM内存垃圾的目标。 JVM中的CMS、G1垃圾接纳器所使用垃圾接纳算法即为三色标志法。
三色标志法将对象分为三种状态:白色、灰色和黑色。

三色标志法的标志过程可以分为三个阶段:初始标志(Initial Marking)、并发标志(Concurrent Marking)和
重新标志(Remark)。

在重新标志阶段结束之后,垃圾接纳器会执行清除操纵,将未被标志为可达对象的对象进行接纳,从而开释内存空
间。这个过程中,垃圾接纳器会将全部未被标志的对象标志为白色(White)。
以上三个标志阶段中,初始标志和重新标志是需要STW的,而并发标志是不需要STW的。此中最耗时的实在就是
并发标志的这个阶段,由于这个阶段需要遍历整个对象树,而三色标志把这个阶段做到了和应用线程并发执行,大
大低落了GC的停顿时长。
9、新生代和老年代的垃圾接纳器有何区别

常见的垃圾接纳器如下:
垃圾收集器分类作用位置使用算法特点实用场景Serial串行新生代复制算法相应速率优先实用于单CPU情况下的client模式ParNew并行新生代复制算法相应速率优先多CPU情况Server模式下与CMS共同使用Parallel并行新生代复制算法吞吐量优先实用于背景运算而不需要太多交互的场景Serial Old串行老年代标志-整理(压缩)算法相应速率优先实用于单CPU情况下的Client模式Paraller Old并行老年代标志-整理(压缩)算法吞吐量优先实用于背景运算而不需要太多交互的场景CMS并发老年代标志-清除算法相应速率优先实用于互联网或B/S业务G1并发、并行新生代、老年代标志-整理(压缩)算法相应速率优先相应速率优先 新生代收集器有Serial、ParNew、Parallel Scavenge。
老年代收集器有Serial Old、Parallel Old、CMS。
整堆收集器有G1、ZGC。

jdk1.8默认使用ParallelGC。新生代采用的是Parallel Scavenge,老年代Parallel Old。
10、Java 中的四种引用有什么区别

  1. java
  2. Object obj = new Object();
复制代码
  1. SoftReference<Object> softRef = new SoftReference<>(new Object());
  2. Object obj = softRef.get(); // 可以通过 get() 方法获取到被引用的对象
复制代码
  1. WeakReference<Object> weakRef = new WeakReference<>(new Object());
  2. Object obj = weakRef.get(); // 可以通过 get() 方法获取到被引用的对象
复制代码
  1. ReferenceQueue<Object> queue = new ReferenceQueue<>();
  2. PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);
  3. Object obj = phantomRef.get(); // 返回值始终为 null
  4. Reference<?> ref = queue.remove(); // 可以从队列中获取到被回收的虚引用
复制代码
需要注意的是,软引用、弱引用和虚引用都继承自 Reference 类,而且它们在垃圾接纳器运行时都可能会被接纳。因此,在使用这些引用时,需要特别警惕,避免由于引用被接纳而导致程序出错。
11、Java 中类加载的过程是怎么样的


类加载重要分为三个阶段,加载,链接,初始化
将类的字节码文件加载到内存中,并创建一个对应的 Class 对象来表示该类。
又分为三个子阶段,验证,准备和解析

在链接阶段之后,就可以开始初始化了。在初始化阶段,通常情况下,初始化代码包括静态变量的赋值、静态代码块的执行等。如果一个类有父类,则需要先初始化父类,然后再初始化子类。
Java中的类什么时间会被加载
总之,Java中的类加载实在是延长加载的,除了一些底子的类以外,其他的类都是在需要使用类时才会进行加载。同时,Java还支持动态加载类,即在运行时通过程序来加载类,这为ava程序带来了更大的灵活性。
12、JVM 中一次完备的 GC 流程是怎样的


一次完备的 GC 流程(以 G1 为例)
(1)Young GC(Minor GC)Young GC 重要接纳年轻代的内存,分为以下几个阶段:
标志 GC Roots 直接引用的对象。需要停息全部应用线程(Stop-The-World,STW)。
从 GC Roots 开始,遍历对象图,标志全部存活对象。与应用线程并发执行,不需要停息应用线程。
处理并发标志阶段遗漏的对象。需要停息全部应用线程(STW)。
统计存活对象,开释完全空闲的区域。需要停息全部应用线程(STW)。
将存活对象从 Eden 区和 Survivor 区复制到另一个 Survivor 区。如果对象年龄达到阈值(默认 15),则晋升到老年代。
(2)Full GC(Major GC)Full GC 接纳整个堆内存和元空间,通常发生在以下情况:

Full GC 的流程如下:
标志 GC Roots 直接引用的对象。需要停息全部应用线程(STW)。
从 GC Roots 开始,遍历对象图,标志全部存活对象。与应用线程并发执行。
处理并发标志阶段遗漏的对象。需要停息全部应用线程(STW)。
统计存活对象,开释完全空闲的区域。需要停息全部应用线程(STW)。
将存活对象移动到堆的一端,开释一连的内存空间。需要停息全部应用线程(STW)。
13、如何排查 OOM 的问题

OOM(OutOfMemoryError) 是 Java 程序中常见的错误之一,表示 JVM 的内存资源已经耗尽,无法再分配新的内存。OOM 可能会导致程序崩溃,因此理解 OOM 的原因和排查方法非常重要。
OOM 的常见原因

排查 OOM 问题的步调
  1. 生成堆内存快照:
  2. jmap -dump:format=b,file=heapdump.hprof <pid>
复制代码
  1. 查看堆内存摘要:
  2. jmap -heap <pid>
复制代码
  1. 生成线程快照:
  2. jstack <pid> > threaddump.txt
复制代码
14、如何进行 JVM 调优

JVM 调优 是通过调整 JVM 参数、优化代码和使用符合的垃圾接纳器,以提升 Java 应用程序的性能和稳定性的过程。JVM 调优的焦点目标是减少 GC(垃圾接纳) 的开销、低落 内存占用、进步 吞吐量 和 相应速率。以下是 JVM 调优的详细步调和常用方法:
JVM 调优的目标

JVM 调优的常用工具
(1)监控工具

  1. jstat -gc <pid> 1000 10
复制代码

  1. jmap -dump:format=b,file=heapdump.hprof <pid>
复制代码

  1. jstack <pid> > threaddump.txt
复制代码
(2)分析工具

(3)GC 日志分析工具

JVM 调优的步调

使用性能分析工具(如 JProfiler、VisualVM)找出 CPU、内存、I/O 等方面的瓶颈。


  1. -Xloggc:/path/to/gc.log
  2. -XX:+PrintGCDetails
  3. -XX:+PrintGCDateStamps
复制代码
  1. -XX:+HeapDumpOnOutOfMemoryError
  2. -XX:HeapDumpPath=/path/to/heapdump.hprof
复制代码




免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/) Powered by Discuz! X3.4