YWW
YWW
发布于 2025-08-20 / 20 阅读
0
0

JVM内存原理理解

1.JVM通俗理解

JVM是java虚拟机,可以在任何拥有jvm的平台运行java的字节码文件【.class文件】,JVM实现了java跨平台特性。

Java源代码首先需要通过Java编译器生成字节码文件,然后由JVM负责解释执行这些字节码。

2.重点理解:JVM内存机制

  1. JVM的内存组成 ?

(1)堆:一块内存,被所有线程共享。存放【生成的对象】

所以为什么会有堆内存占满的问题出现,和对象内存回收有很大关联

(2)栈:虚拟机栈和本地方法栈

虚拟机栈:线程私有,存放【基本类型变量+对象引用变量

包装类型(Integer,Character)都是普通对象,放堆内存

创建完对象,这个对象名作为变量存在这里 栈中数据是共享的

本地方法栈:为虚拟机中使用的Native方法服务

  • 虚拟机栈为虚拟机执行Java方法(字节码)服务

  • 栈的深度如果超过虚拟机允许的深度,抛出StackOverflowError异常

  • 扩展虚拟机栈时,扩展无法申请到足够的内存 抛出OutOfMemoryError
    【这里OOM依旧和内存是否充足有关】

(3)方法区:存放.class字节码文件,包含所有虚拟机加载的类、常量、静态变量等,存的都是整个程序中唯一的元素(static变量、class)

(4)程序计数器:线程私有的,理解为:维护当前这个线程执行的字节码的行号


2. JVM内存模型


jdk1.8之后JVM内存模型将永久代替换--元空间,常量池依旧在方法区

垃圾回收机制是这样的:

(1) 新产生的对象放在Eden区里

(2) eden区满了,存活的对象复制到from区中

【如果存活对象from放不下,这些存活对象全部进入老年代,之后的eden区内存回收掉】

(3) 继续分配到eden,eden再次满了 eden+from 存活对象复制到to区

【to满则进入老年代,eden/from 内存都回收】

(4) 默认情况,一个对象复制15次,就进入老年代

(5) 老年代满了或放不下,进行一次full GC

垃圾回收

1.Minor GC /young GC

新生代回收:不影响老年代,需要速度快:复制算法

2.Full GC

老年代回收:整个堆的内存回收,标记-清除,标记-压缩

垃圾回收算法常见的几种:

(1) 复制算法:

整个内存空间被分两部分相同大小,每次使用一块空间,一半空间的内存占满后,将所有被引用的存活对象复制到另一块空间,然后情况当前空间;缺点是可用空间变小,且复制大对象效率低,不适合老年代

从根集合节点扫描,标记所有存活对象,将这些存活对象复制到一块新的内存,然后把原来的内存回收

eden:from: to 一般是8:1:1,eden满了则把对象复制到from ,然后回收eden内存,eden/from 满了则复制到to,然后回收eden/from

(2) 标记-清理 算法:

先从根节点开始 标记所有对象,没标记的就是未被引用的对象,清除阶段,清除所有的未标记对象
【适合清除老年代,但容易产生内存碎片,扫描空间两次(标记/清除)】

(3) 标记-压缩 算法:

先从根节点做一次标记,然后把存活对象压缩到内存的一段

清除阶段:清理边界外的所有空间,减少了碎片内存产生

(4) 分代收集算法:

Java 堆分为新生代和老年代,根据各个代的特点选择合适的垃圾收集算法。新生代-复制算法;老年代-标记压缩

新生代中,每次收集都会有大量对象死去,所以可以选择“复制”算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。

老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集

垃圾回收器 CMS和G1
(1) CMS:

基于标记-清除法的垃圾回收器

在启动 JVM 的参数加上“-XX: + UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。缺点是容易产生内存碎片,且需要更多CPU资源和更大堆空间

步骤分为四步:

1.初始标记:虚拟机停顿执行的应用线程,扫描对象然后标记

2.并发标记:用户线程和GC线程并发执行,继续向下标记对象

3.重新标记:再次暂停执行的线程【STW】,重新从根对象开始查找并标记遗漏的对象

4.并发清理:应用线程和GC清除线程并发回收,随后进入并发重置,重置CMS收集器的数据

(2) G1回收器

局部的标记-清理 根据哪快内存垃圾数量最多,回收收益最大作为衡量标准。

初始标记:标记所有直接关联对象

并发标记:堆中的对象进行标记,标记线程与应用程序线程并行执行,并且收集各个region的存活对象信息

最终标记:标记那些在并发标记阶段发生变化的对象,最终将被回收

清除:清除空region,即无存活对象的region

区别:

CMS是获取最短回收停顿时间为目标的收集器,减少老年代回收的停顿时间

一边运行一边并发标记,最后暂停清理,标记-清理易产生内存碎片

G1是把内存分成块,在可控时间内(比如最多停200ms),优先回收​​垃圾最多的小方块(Region)
G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region】
根据标记结果,选择回收价值最高的(最大的Region)复制存活对象到新区域,然后回收旧区域内存

G1采用标记-复制算法 且可预测清理时间

3.类加载机制

(1)类加载机制:

依据.class文件,首先将class文件转换为二进制字节码加载到内存中,对其进行校验,准备,解析,初始化,最后转换成JVM可用的类型

(2)类加载过程

加载:加载类的二进制字节码(.class)到内存,结构化静态存储结构,生成class对象

连接:分为 验证-准备-解析

  • 验证:检查类文件格式是否符合 JVM 规范【文件格式解析;元数据解析;字节码解析;符号引用解析】

  • 准备:为类的静态变量分配内存并设置默认值。

  • 解析:将符号引用替换为直接引用。

初始化:静态方法块和静态变量初始化【执行类的构造方法】

(3)双亲委派机制:

类加载器收到加载一个类请求,先将请求委派到类的父 类加载器,无法找到并加载则继续委派到上层 父 类加载器 ,直到顶层加载器

父类加载器无法加载才尝试子类加载器


打破双亲委派:重写classloader中的loadclass方法 ,或热部署

如 SPI 是 Java 的一种扩展机制,用于加载和注册第三方类库,常见于 JDBC、JNDI 等框架。

双亲委派模型会优先让父类加载器加载类,而 SPI 需要动态加载子类加载器中的实现。使用 SPI 机制通过 META-INF/services 文件指 定服务提供者的实现类。


评论