【Java】JVM 知识串讲

1. JVM 是什么

JVM 是 Java 虚拟机的简称,我们在下载 Java 运行环境时(jre),就已经包含了 JVM 了,JVM 是 Java实现跨平台的最核心的部分,所有的 Java 程序会首先被编译为 .class 的类文件,这种类文件可以在虚拟机上执行

2. JVM 的位置

JVM 是 Java 虚拟机的简称,其运行在操作系统之上

在这里插入图片描述

3. JVM 的体系结构

JVM 由类加载器、运行时数据区、执行引擎和本地方法接口组成。它们之间的关系如下图所示:
在这里插入图片描述

4. 类加载器

在这里插入图片描述

通过下列代码测试类加载器,输出结果如下,也证明了 JVM 共含三种类加载器 ,分别是: 应用类加载器 AppClassLoader 、扩展类加载器 ExtClassLoader 、根加载器 BootStrap(因为底层是用 C 写的,所以访问不到)

/**
 * @Author: WanqingLiu
 * @Date: 2023/02/06/9:49
 * 测试 Car 的类加载器
 */
public class Car {
    public static void main(String[] args) {
        Car car = new Car();
        // 通过反射得到 Class 类
        Class Car = car.getClass();
        System.out.println(Car.getClassLoader()); // ” sun.misc.Launcher$AppClassLoader@18b4aac2 “
        System.out.println(Car.getClassLoader().getParent()); // ”sun.misc.Launcher$ExtClassLoader@4554617c“
        System.out.println(Car.getClassLoader().getParent().getParent()); // ”null“ —— ? 为什么输出是null : 因为底层是用 C 写的, Java 代码访问不到 ?
    }
}

5. 双亲委派机制

双亲委派机制是类加载器在工作时(加载类时)所要遵循的工作模型。双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它会把请求委托给父加载器去完成,直至类加载请求被传递到顶层的启动类加载器中。只有当父加载器在它的搜索范围中没有找到所需的类时,子加载器才会尝试自己去加载该类。

通过双亲委派机制 JVM 类加载器工作过程如下:

  1. 当 AppClassLoader 加载一个类时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给它的父类加载器 ExtClassLoader 去完成
  2. 当 ExtClassLoader 加载一个类时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给 BootStrap ClassLoader 去完成。
  3. 如果BootStrap ClassLoader加载失败,会使用 ExtClassLoader 来尝试加载;
  4. 若 ExtClassLoader 也加载失败,则会使用 AppClassLoader 来加载,如果 AppClassLoader 也加载失败,则会报出异常 ClassNotFoundException。

验证双亲委派机制,下面的代码不能正常允许:

package java.lang;
/**
 * @Author: WanqingLiu
 * @Date: 2023/02/06/10:19
 */
public final class String {
    public static void main(String[] args) {
        System.out.println("我是自定义的 String 类");
    }
}

6. 沙箱机制

沙箱机制是 Java 安全模型的核心,沙箱就是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在 JVM 特定运行范围中,并且严格限制代码对本地资源的访问。( 沙箱机制的出现主要是因为 Java 当时想和其他语言竞争,所以得有比其他语言好的地方,因此就提出了沙箱机制)

经过 Java 多代的升级,沙箱机制演化为下图所示的 Demain 域的概念:
在这里插入图片描述

沙箱机制可以保证安全的原因:

  • 字节码校验器: 系统类防止内存中出现多份同样的字节码
  • 类装载器:采用双亲委派机制使得外层同名恶意类不得使用
  • 存储控制器:控制核心 API 对系统的操作权限,用户可自己指定
  • 安全管理器:核心 API 和操作系统间的主要接口,实现权限控制
  • 安全软件包:java.security 包下的类,允许用户自行定义安全特性

7. native 关键字

native 关键字修饰的方法表示本地方法,带了 native 关键字,Java 的作用范围就达不到了,表示需要调用底层 C 语言写的库

调用方式为 JVM 运行时数据区的本地方法栈 调用 JVM 提供的本地方法接口 JNI (Java Native Interface )

那从上面我们也可以看出,JNI 的作用为 扩展 Java 语言的使用,融合不同的编程语言为 Java 所用。 现在在企业应用中很少见 JNI 的使用,但是驱动硬件的时候很多见(比如: Java 驱动打印机)

以 Thread 类中的 start0 方法为例,理解 native 关键字 :

在这里插入图片描述

8. PC 寄存器

每个线程都有一个私有的程序计数器,其指向方法区中的方法字节码(即指向即将要执行的指令的执行代码),其是一个非常小的内存空间,几乎可以忽略不计

9. 方法区

在这里插入图片描述
方法区是 JVM 内存中的一种,其被所有线程所共享,方法区中保存静态变量、常量和类信息(如构造函数、接口代码)。

10. 栈

栈内存主管程序的运行,其生命周期和线程同步,线程结束,栈内存释放。因此,对于栈来说,不存在垃圾回收问题。
栈内存中存放 8大基本数据类型 、对象引用 和 实例的方法 等

用栈解释为什么 main 方法最后执行

如下图所示:因为 main 方法最先入栈,因此最后执行
在这里插入图片描述

栈运行实例的方法原理:通过 栈桢,如下图所示

在这里插入图片描述

栈溢出 StackOverError :递归时常出现的问题,因为方法一直入栈,不能出栈执行,造成了栈溢出

11. 三种 JVM

  • HotSpot —— 我们都是相对 HotSpot 说
  • JRockit
  • J9VM

12. 堆

堆 Heap,一个 JVM 只有一个堆内存,所有线程共享这一个堆内存。堆中存放所有对象的实例、类、方法、常量、变量等并保存所有引用类型的真实变量。

堆中详细区域分为如下:

  • 新生区: 包括伊甸园区、幸存者 0 区 和 幸存者 1 区
  • 养老区
  • 永久区 —— JDK 1.8 后改名为 元空间,其中含有 方法区,因此方法区也是堆的一部分

堆中内存分配如下图所示:
在这里插入图片描述

元空间(永久代)逻辑上存在,但是物理上不存在 —— 它属于堆,但是真实计算物理区域,只有新生代和老年代

通过如下代码进行测试:

/**
 * @Author: WanqingLiu
 * @Date: 2023/02/05/18:49
 * Xms 初始化内存分配大小
 * xmx 最大分配内存
 * -XX:+HeapDumpOnOutOfMemoryError
 * -XX:+PrintGCDetails
 */
public class Demo01 {
    public static void main(String[] args) {
        // jvm 试图使用的最大内存 —— 默认的总内存是电脑内存的 1/4
        long max = Runtime.getRuntime().maxMemory();
        // jvm 的总内寸 —— 是最大内存的 1/64
        long total = Runtime.getRuntime().totalMemory();
        System.out.println("max = " + max + "字节\t" + max/(double)1024/1024  + "MB");
        System.out.println("total = " + total + "字节\t" + total/(double)1024/1024 + "MB");
    }
}

在这里插入图片描述

通过上述代码可得, 元空间只是逻辑上存在

13. 新生区、老年区

新生区 : 类诞生和成长的地方,甚至死亡

  • 伊甸园区:类诞生的地方,所有对象都是在伊甸园区 new 出来的
  • 幸存者区:分为 from 区 和 to 区,两者可交换,用于垃圾回收

14. 永久区

永久区是常驻内存的,存放 JDK 自带的 Class 对象,Interface 元数据 —— 即 java 运行时的一些环境或者类信息,这个区域不存在垃圾回收,关闭虚拟机时释放该区域内存

  • JDK 6 : 永久代,常量池在方法区中
  • JDK 7 :永久代,但是在退化,常量池在堆中
  • JDK 8 :无永久代,变为元空间,常量池在元空间

15. 堆内存调优

堆内存可能会出现 OOM 故障(即 Out Of Memory 错误,表示堆内存满了),这时我们就需要进行堆内存调优,快速定位出现错误的位置

当一个启动类加载大量第三方 Jar 或者 Tomcat 部署太多应用, 产生大量反射类,就可能会出现 OOM

解决:

  1. 调整 堆 内存空间,看结果

使用 IDEA 调整 堆内存大小方法如下:

加入 VM option 选项,填入 -Xms1024m -Xmx1024m -XX:+PrintGCDetails ,其中 -XX:+PrintGCDetails 表示打印垃圾回收过程

在这里插入图片描述

  1. 分析内存,看一下那些地方出现了问题

在项目中突然出现了 OOM 故障 , 调错方式:

  • 使用内存快照分析工具 —— MAT、JProfiler

使用内存快照分析工具可以让我们直接看到代码第几行出错 ——

IDEA 整合 JProfiler 方式为:

  1. IDEA 下载 JProfiler 插件 —— 下载好要重启
  2. 本机下载 -Xms1m -Xmx1m -XX:+HeapDumpOnOutOfMemoryError 客户端:https://www.ej-technologies.com/products/jprofiler/overview.html
  3. VM Options 加入如下选项 -Xms1m -Xmx1m -XX:+HeapDumpOnOutOfMemoryError, 表示拍摄内存快照
  4. 使用 JProfiler 客户端打开拍摄的快照,进行分析
  • Dedug , 一行行分析代码

16. GC —— 分代收集算法

垃圾回收在堆中,即 GC 的作用区域在堆中

JVM 进行 GC 的堆内存位置:

  • 新生代
  • 幸存区 (from to)—— from to 是交换的
  • 老年区

按照 GC 程度划分的 GC 两种类型

  • 轻 GC :即普通 GC
  • 重 GC:即全局 GC

16.1 引用计数法

在这里插入图片描述

16.2 复制算法

将 幸存者区,分为 from 区 和 to 区,遵循谁空谁是 to 区的原则,两者间可进行交换,复制算法执行过程为:

  1. 每次 GC 将 Eden 区存活的对象移动到幸存区中,一旦伊甸园区被 GC , 伊甸园区为空的
  2. 复制算法保证 to 区永远是空的 (复制方式为:每次将 GC 后存活的对象复制到 to 区,然后 to 区变为 from 区,将原 from 区清空,变为 to 区)
  3. 当一个对象经历了15次(可以调整)GC,就进入养老区

好处:没有内存碎片
缺点:浪费内存空间(多了一半 to 区永远是 null)

16.3 标记清除算法

在这里插入图片描述

缺点:两次扫描,严重浪费时间,而且会产生内存碎片(非连续的空间)
优点:不使用额外空间

16.4 标记压缩算法

在这里插入图片描述

在标记清除算法基础上再加一次扫描,向一端移动存活的对象,从而清除内存碎片
—— 优化:清除 n 次,压缩 1 次

总结 GC 算法:

内存效率 : 复制 > 标记清除 > 标志压缩
内存整齐度:复制算法 = 标记压缩 > 标记清除
内存利用率: 标记压缩 = 标记清除 > 复制算法

没有最好的算法,但是有针对与每个常见场景的最合适的算法

年轻代:存活率低, 使用复制算法
老年代:存活率高,区域大,使用标记清除加标记压缩混合实现 —— JVM 调优

17. JMM

Java Memory Medol

  1. 什么是 JMM: JMM 是一个抽象的概念,一个缓存一致性协议,其定义了读写的规则,
    JMM 定义了线程工作内存和主内存中的抽象关系,解决共享对象的可见性问题(volatile)
  2. JMM 的功能: 官方 博客 视频
  3. JMM 如何学习

最后:JVM 面试题

  1. JVM 的内存模型和分区 - 详细到每个区放什么
  2. 堆里面的分区有那些, Eden、 from 、to 、老年区的特点
  3. GC 算法 用法和原理
  4. 轻 GC 和 重 GC 分别在什么情况下发生
  5. 谈谈你对 JVM 的理解 ? Java 8 虚拟机和之前的变化
  6. 什么是 OOM,什么是栈溢出,怎么分析
  7. JVM 的常用调优参数
  8. 内存快照如何抓取,怎么分析 Dump 文件
  9. 堆 JVM 类加载器的认识
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值