java学习——JVM

本文围绕JVM展开,介绍了其定义、主要组成部分,阐述了类加载器、双亲委派机制,分析了内存结构、垃圾回收机制及相关算法,对比了G1和CMS垃圾回收器,还讲解了Java引用类型、三色标记法等,最后提及元空间、分代回收、JIT和OOM等内容。

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

JVM是java知识体系中的一个重难点,本文仅是部分问题,后续遇到新的问题还会进行修改补充。

上一篇传送门:点我

谈谈什么是JVM

JVM全称是Java Virtual Machine,是java的虚拟机。它是指通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统。它提供了一个独立于硬件和操作系统的执行环境,使得Java程序能够在不同平台上具有跨平台的特性。(JVM运行的是字节码文件)

谈谈JVM的主要组成部分?

JVM的主要组成部分可以分为包括类加载子系统、运行时数据区、执行引擎、本地库接口和本地方法库
其中,类加载子系统负责加载Class信息到内存中,这些信息被加载到一块称为方法区的内存区域;运行时数据区是JVM在执行Java程序时用来存储和管理数据的内存区域;执行引擎负责执行虚拟机的字节码本地库接口为Java提供了融合不同编程语言的能力,允许Java程序调用非java代码;本地方法库则是包含了Java本地方法实现的代码库。
基本流程简要示意图

解释一下JVM类加载器的作用

类加载器(ClassLoader)负责将Class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类java.lang.Class的对象,作为方法区中类数据的访问入口。类加载器在整个装载阶段中,只能影响到类的加载,至于它是否可以运行,则有Execution Engine决定。
简单来说,类加载器就是负责加载Java类的字节代码到Java虚拟机中

JDK自带哪些类加载器

JDK自带了三个类加载器,分别是Bootstrap ClassLoader(启动类加载器)Extensions ClassLoader(拓展类加载器)System ClassLoader(系统类加载器)

其中,启动类加载器是最顶层的类加载器,主要负责加载Java的核心类库,如%JAVA_HOME%/lib目录下的核心jar包和类文件。由于它是由C++写的,不是Java类,而是由JVM内部实现,所以无法被java程序直接引用
扩展类加载器启动类加载器的子类加载器,负责加载Java的扩展类库,通常位于%JAVA_HOME%/lib/ext目录下。
系统类加载器扩展类加载器的子类加载器,也是Java应用程序默认的类加载器。它负责加载用户类路径(classpath)上的类库和类文件,这些类库和类文件通常由开发者自己编写或第三方提供。
!!!注意:有些地方会把System ClassLoader(系统类加载器)说成Application ClassLoader(应用类加载器),实际上它们俩是一个东西 没有区别!!!

谈谈双亲委派机制

JVM中的默认存在的三个类加载器分别是Bootstrap ClassLoader(启动类加载器)Extensions ClassLoader(拓展类加载器)System ClassLoader(系统类加载器)(引用上文的话)。它们三者之间存在着父子加载器的关系:系统类加载器的父加载器是拓展类加载器拓展类加载器的父加载器是启动类加载器
双亲委派机制指的是JVM在加载类时,会委派给扩展类加载器和启动类加载器进行加载,如果没加载到才由自己进行加载。
具体流程简短来说,就是JVM在加载类时,最先会通过双亲委派机制委托给拓展类加载器,拓展类再委托给启动类加载器,启动类加载器会最先进行加载,加载到了就直接成功,如果没有加载到,拓展类加载器再会尝试加载该类,如果仍然没有加载成功,最后系统类加载器就会自己加载该类
在这里插入图片描述

谈谈双亲委派机制的作用,为什么要用双亲委派机制?

双亲委派机制主要解决了类加载的重复和冲突以及确保程序安全性的问题。
当一个类被加载后,会被缓存起来,下次再次加载时可以直接使用缓存的结果,提高了加载的效率。另外,由于父类加载器优先加载类,所以可以保证类的全局一致性,避免了不同类加载器加载同一个类的问题。
JVM中有一个启动类加载器(Bootstrap ClassLoader),它负责加载核心类库,如java.lang包下的类。由于启动类加载器无法通过Java代码直接访问,因此可以防止恶意代码替换核心类库,确保了程序的安全性。

谈谈JVM的内存结构(运行时数据区)

JVM的内存结构分为堆、方法区、虚拟机栈、程序计数器、本地方法栈。
其中,是Java程序运行时创建的对象所存放的地方,几乎所有的对象实例都存放在堆中;方法区主要用于存储已被虚拟机加载类信息常量静态变量、即时编译器编译后的代码等数;虚拟机栈为每个线程私有,用于存储线程的方法调用局部变量程序计数器是当前线程所执行的字节码的行号指示器,用于记录线程执行的位置本地方法栈与虚拟机栈类似,也是用来保存执行方法的信息.执行Java方法是使用虚拟机栈,执行本地方法时使用本地方法栈

虚拟机栈、本地方法栈、程序计数器是线程独占的,而堆和方法区是线程共享的。

!!!注意:java方法是用java语言编写,本地方法则是用其他语言编写。

在这里插入图片描述

什么是垃圾回收

在JVM中,垃圾是在堆和方法区中(主要是在堆中)已经不会再被使用到的内存空间,而垃圾回收就是释放这些垃圾所占用的空间,以防止内存泄露。通过垃圾回收,可以有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。

垃圾回收机制(GC)如何判断垃圾可以被回收

通常有引用计数算法根可达性分析算法两种。
引用计数算法:为对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。在两个对象出现循环引用的情况下,此时引用计数器永远不为 0,导致无法对它们进行回收,从而导致了循环引用。正是因为循环引用的存在,因此 Java 虚拟机不使用引用计数算法
根可达性分析算法:通过判断对象的引用链是否可达来决定对象是否可以被回收。
具体来说,根可达性分析算法是根据"GC Roots"对象为根,向下去搜索(去找叶节点),搜索走过的路径叫
引用链
(Reference Chain),当一个对象和"GC Roots"之间没有任何引用链时,这个对象就会判定为可回收的。
在这里插入图片描述
(上图来源:https://blog.csdn.net/twotwo22222/article/details/128183379)

GC Roots的对象有:
● 虚拟机栈(栈帧中的本地变量表)中引用的对象
● 方法区中类静态属性引用的对象
● 方法区中常量引用的对象
● 本地方法栈中JNI(即⼀般说的本地方法)引用的对象

谈谈finalize()的作用

finalize方法是Object类中定义的一个方法,其作用是在对象被垃圾回收器回收之前执行一些清理操作。finalize方法在对象即将被回收时自动调用,可以用来释放对象占用的资源、关闭文件或网络连接、取消注册的事件等清理工作。由于finalize方法的执行会影响垃圾回收器的性能,所以在一些情况下,应该避免使用该方法。

什么是STW?

STW全称为Stop-The-World,是在垃圾回收算法执行过程当中,需要将JVM内存冻结的⼀种状态。在STW状态下,JAVA的所有线程都是停止执行的,GC线程除外,native方法可以执行,但是,不能与JVM交互。GC各种算法优化的重点,就是减少STW,同时这也是JVM调优的重点

说说你知道的垃圾回收算法

我了解的垃圾回收算法有标记清除算法、复制算法、标记压缩算法三种。
1.标记-清除算法:这个算法分为两个阶段,标记阶段:把GC Roots中的垃圾内存标记出来,清除阶段:直接将垃圾内存回收。这种算法虽然实现简单,但是会产生大量的内存碎片,导致内存碎片的空间无法充分利用。
2. 复制算法:复制算法是为了解决标记清除算法的内存碎片问题。复制算法将内存分为大小相等的两半,每次只使用其中⼀半。垃圾回收时,将当前这⼀块的存活对象全部复制到另一半,然后当前这一半内存就可以直接清除。这种算法没有内存碎片,但是但是这种算法代价过高,需要将可用内存缩小一半,对象存活率较高时,需要持续的复制工作,效率比较低。
3. 标记-整理算法:为了解决复制算法的缺陷,就提出了标记-整理算法。这种算法在标记阶段跟标记清除算法是⼀样的,但是在完成标记之后,不是直接清理垃圾内存,而是将存活对象往⼀端移动,以便将它们紧密地排列在一起,从而消除内存中的碎片。这样做的好处是可以更高效地利用内存空间,并且使得后续的内存分配更加迅速。

什么是G1?什么是CMS?它们的区别是什么?

CMS(Concurrent Mark Sweep)和G1(Garbage-First)都是Java虚拟机中的垃圾回收器,它们各有特点并适用于不同的场景。
CMS是一个以最短回收停顿时间为目标的收集器,非常适合注重用户体验的应用。它使用“标记-清除”算法,并且其运作过程相对复杂,分为初始标记、并发标记、重新标记、并发清理四个阶段。其中,初始标记和重新标记阶段需要“Stop The World”,即暂停用户线程,而并发标记和并发清理阶段则可以与用户线程并发运行。然而,CMS的一个缺点是它可能会产生内存碎片,因为它不进行空间整理。
相比之下,G1垃圾回收器的目标是在可预测的停顿时间内完成垃圾回收,适用于大型应用和多核处理器环境。G1将整个堆内存划分为多个小区域(Region),这使得它可以更精细地管理内存,并且可以实现更高的并发性。G1的回收过程包括初始标记、并发标记、最终标记和筛选回收阶段。在筛选回收阶段,G1会根据每个区域的回收成本进行排序,然后按照用户自定义的回收时间制定回收计划。此外,G1使用“标记-整理”算法,这有助于减少内存碎片

CMS和G1的主要区别包括:

1.应用范围:CMS主要针对老年代进行垃圾回收,而G1同时处理老年代和新生代
2.停顿时间:CMS致力于减少停顿时间,而G1提供了可预测的停顿时间模型。
3.垃圾碎片:由于CMS使用“标记-清除”算法,可能会产生内存碎片。而G1使用“标记-整理”算法,降低了内存碎片的产生
4.回收过程:两者的回收阶段有所不同,特别是在最后的阶段,CMS是并发清理,而G1是筛选回收。

Java的四种引用类型?

强引用: 一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,JVM宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。
弱引用: JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。
软引用: 用于描述一些还有用但并非必需的对象。在系统将要发生内存溢出异常之前,将会回收软引用指向的对象。 软引用通常用于实现内存敏感的缓存。
虚引用: 虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响。如果一个对象仅持有虚引用,那么它和没有引用几乎是一样的,随时都可能被垃圾收集器回收。

JVM中的三色标记法是什么?

三色标记法是Java虚拟机里面垃圾回收算法的一种,主要用来标记内存中存活和需要回收的对象,它的好处是可以让JVM不发生或仅短时间发生STW,从而达到JVM内存回收的目的,在JVM中,CMS、G1垃圾回收器都用到了三色标记法。
在三色标记法中,JVM会把内存中的对象分为三种颜色:第一种是白色,表示的是还没有被垃圾回收器扫描的对象;第二种是黑色,表示已经被垃圾回收器扫描过,并且对象及其引用的其他对象都是存活的;第三种是灰色,表示已经被垃圾回收器扫描过,但是对象引用的其他对象还未被扫描
在GC开始的时候,先把所有的对象都标记为白色,然后从根对象开始,去遍历内存中的对象,接着把直接引用的对象标记为灰色,然后再判断灰色集合中的对象是否存在子引用,不存在就将该对象放入黑色对象的集合,如果存在,就需要把子引用的对象放到灰色集合中,按照这样的步骤不断推导,直到灰色集合中的所有对象都变成黑色以后,这一轮的标记就完成了。最后仍处于白色的对象就是不可达对象,可以直接被回收。
三色标记法主要用于在垃圾回收过程中区分已处理、未处理和正在处理的对象,以提高垃圾回收的效率和准确性。

谈谈什么是元数据

元数据指的是描述数据的数据。可以说明数据的元素或属性(名称,大小,数据类型等),或其结构(长度,字段,数据列),或其相关数据(位于何处,如何联系,拥有者)。

了解过元空间吗?

元空间其实是JVM方法区的实现。谈到了元空间,就不得不先说说JVM的持久代。在在jdk7以及jdk7之前,方法区是由持久代来实现的,它是 java堆中的一部分,主要用于存储元数据信息。而在jdk8及jdk8以后元空间取代了持久代的位置,它和持久代最大的区别是:元空间并不在虚拟机中,而是使用本地内存。这样能够避免在JVM中出现OutOfMemoryError(OOM)异常,因为持久代的大小是在JVM启动时固定下来的,而元空间的大小则只受限于本地内存的大小。

说说什么是JVM的分代回收

JVM的分代回收是垃圾回收机制的重要组成部分,基于对象存活周期的不同将内存划分为不同的区域,以提高垃圾回收的效率。

JVM的堆划分为了新生代老年代(1:2的关系),在新生代中的对象大多数很快就会成为垃圾被回收,而老年代中的对象存活时间较长。

对于新生代,又可以细分为伊甸园区(Eden区)幸存者区(Survivor区,里面又分成了from区和to区)(Eden区:from区:to区为8:1:1)。新创建的对象首先会被分配到Eden区。当Eden区空间不足时,会触发新生代垃圾回收机制(Young GC),而对于那些因为存在引用关系而无法被回收的对象,JVM会把它转移到Survivor区,刚刚从Eden区转过来的对象会先分配到Survivor区中的from区中,每当触发依次Young GC,那些没有办法被回收的对象就会在from区和to区之间来回移动,每移动一次,存活的对象年龄会加1(对象的年龄是指其经过垃圾回收的次数),当对象的年龄达到设定的阈值(默认阈值为15)时,会被晋升到老年代。同时,Survivor区中的对象也可能因为空间不足而提前晋升到老年代。

老年代中存放的是长时间存活的对象。当新生代中的对象晋升到老年代时,或者直接在老年代中分配大对象时(如大数组或长字符串),如果老年代空间不足,会触发全局垃圾回收机制(Full GC),清理整个堆空间中的垃圾对象。全局垃圾回收的触发条件除了老年代空间不足外,还包括元空间不足、系统显式调用或者并发模式失败等情况。
在这里插入图片描述
(图片来源:https://blog.csdn.net/weixin_45618869/article/details/132253039)

JVM分代年龄为什么是15次?

JVM的堆划分为了新生代和老年代,新生代中又划分为Eden区和Survivor区(其中Survivor区里面又分成了from区和to区)。新创建的对象首先会被分配到Eden区。当Eden区空间不足时,会触发新生代垃圾回收机制(Young GC),而对于那些因为存在引用关系而无法被回收的对象,JVM会把它转移到Survivor区,刚刚从Eden区转过来的对象会先分配到Survivor区中的from区中,每当触发依次Young GC,那些没有办法被回收的对象就会在from区和to区之间来回移动,每移动一次,存活的对象年龄会加1,当对象年龄达到15时,这些对象如果还没有办法回收,JVM则会将这些对象移动到老年代中。而一个java对象在JVM中的内存布局是由三个部分组成的,分别是对象头,实例数据和对齐填充,一个对象的GC年龄则是保存在对象头中在对象头中有4个bit位来存储GC年龄,而四个bit位所能存储的最大数值是15,即JVM所能存储的最大GC年龄为15。
在这里插入图片描述

JIT是什么?

JIT 代表即时编译器(Just In Time compilation),当代码执行的次数超过一定的阈值时,会将 Java 字节码转换为本地机器代码,以提高程序的执行性能。
在JVM解释执行Java字节码的过程中,JIT编译器会检测那些被频繁调用的“热点”代码区域,并将这些代码编译成本地机器代码。这样,在后续的执行过程中,JVM就可以直接运行这些已经编译好的机器代码,而无需再次进行解释执行,从而提高了执行效率。

OOM是什么?OOM的出现场景有哪些?

OOM是Out of Memory的简称,意思是内存的溢出,通常是当应用程序在申请分配内存时,由于没有足够的内存空间供其使用而引发的异常。
除了程序计数器以外,其它的内存部分都有可能会发生OOM(Out Of Memory),程序计数器是一块很小的区域,但是它只记录当前线程执行到的字节码行号,所以不存在OOM的情况。

1.在堆中,当程序创建大量对象或者对象占用的内存过大时,堆可能会耗尽内存导致OOM错误;
解决策略:调整堆的大小或优化对象的使用。
2.在方法区中,当加载的类过多或者在运行时不停地加载和卸载类时,可能会导致方法区耗尽内存而发生OOM错误。
解决策略:调整方法区的大小或减少类的加载和卸载。
3.在虚拟机栈中,当线程的方法调用层级过深或者栈空间不足时,就会导致栈内存溢出,触发OOM错误。栈内存溢出会抛出StackOverflowError异常,表示栈空间溢出。
解决策略:优化递归算法,避免递归调用层级过深,尽量减少不必要的方法调用层级,避免方法调用链过长。
4.对于本地方法栈,与虚拟栈类似,当本地方法的调用层级过深或者栈空间不足时,就有可能触发本地方法栈溢出,导致OOM错误。
解决策略:避免过深的调用链,也可以调整虚拟机的参数,增加本地方法栈容量大小,以容纳更深层级的本地方法调用。

下一篇传送门:点我

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值