深入解析JVM核心原理与运行机制

目录

[一、JVM 基础认知](#一、JVM 基础认知)

[1.1 JVM 核心概念](#1.1 JVM 核心概念)

[1.2 JDK、JRE、JVM 三者区别](#1.2 JDK、JRE、JVM 三者区别)

[1.3 JVM 运行流程](#1.3 JVM 运行流程)

[二、JVM 内存运行模型](#二、JVM 内存运行模型)

[2.1 程序计数器(线程私有)](#2.1 程序计数器(线程私有))

[2.2 Java 虚拟机栈(线程私有)](#2.2 Java 虚拟机栈(线程私有))

[2.3 本地方法栈(线程私有)](#2.3 本地方法栈(线程私有))

[2.4 堆(线程共享)](#2.4 堆(线程共享))

[2.5 元数据区(方法区,线程共享)](#2.5 元数据区(方法区,线程共享))

[2.6 常见内容溢出问题](#2.6 常见内容溢出问题)

[三、JVM 类加载机制](#三、JVM 类加载机制)

[3.1 类加载过程](#3.1 类加载过程)

[3.2 类加载器与双亲委派模型](#3.2 类加载器与双亲委派模型)


一、JVM 基础认知

1.1 JVM 核心概念

**JVM(Java虚拟机):**运行 Java 字节码的虚拟计算机,是实现 Java 一次编写,到处运行的核心。它屏蔽了底层操作系统、硬件差异,Java程序并非直接运行在系统上,而是依托 JVM 执行。

虚拟机分为两大类:系统虚拟机、程序虚拟机

  • **系统虚拟机(如 VMware):**完整模拟一套硬件电脑,可独立安装操作系统
  • **程序虚拟机(如 JVM):**只执行特定语言的字节码,不模拟完整硬件,用于跨语言、跨平台使用

1.2 JDK、JRE、JVM 三者区别

  • **JDK:**Java开发工具包 = JRE+编译、调试等开发工具,用于开发+运行
  • **JRE:**Java运行时环境 = JVM+核心类库,仅能运行Java程序
  • **JVM:**虚拟机,只负责运行字节码,是最底层运行容器

它们三者关系如下图所示:


1.3 JVM 运行流程

程序在执行前先要把 .java 源代码编译成字节码(.class字节码文件),JVM 首先需要把字节码通过一定的方式类加载器(ClassLoader)把文件加载到运行时数据区(内存),

由于字节码文件是JVM的一套指令集规范,并不能直接交给底层操作系统执行,因此需要特定的命令解析器(执行引擎) 将字节码翻译成底层系统指令再交给CPU执行,这个过程需调用其他语言的接口**(本地库接口)** 来实现整个程序的功能,最后垃圾回收(GC),程序运行结束,资源释放。


二、JVM 内存运行模型


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

**程序计数器是用来记录当前线程执行到的字节码行号。**如果当前线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行一个Native方法,这个计数器为空。

程序计数器内存区域是唯一一个在Java规范中无OOM的内存区域


2.2 Java 虚拟机栈(线程私有)

Java 虚拟机栈的生命周期与线程相同,描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息

Java虚拟机栈中包含:

按照后进先出的出栈规律

  • **局部变量表:**存放方法中的局部变量和方法参数,局部变量表的内存空间在编译期间就完成分配,在执行期间不会改变局部变量表的大小
  • **操作数栈:**方法执行的 " 临时计算器 ",先进后出
  • **动态链接:**把符号引用变成真正的内存地址,帮助找到方法到底在内存的哪个位置
  • **方法返回地址:**方法执行完之后回到哪里继续执行

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

本地方法栈是给C++代码使用的(也就是我们所说的本地方法native)

线程私有:

  1. 每个线程独占一份,线程之间互不干扰、数据隔离
  2. 线程创建→自动开辟对应内存;线程销毁→内存立即释放

2.4 堆(线程共享)

程序中创建的所有对象都保存在堆中(new),最大内存区域

垃圾回收主要区域,分新生代、老年代,最常发生OOM(内存溢出)


2.5 元数据区(方法区,线程共享)

用来存储类信息、常量、静态变量、即时编译器编译的代码


2.6 常见内容溢出问题

  • **OOM(OutOfMemoryError):**统称:内存溢出,指 JVM 没有内存可以分配
  • 栈溢出(StackOverflowError): 线程私有区域溢出,栈帧太多,方法调用太深(例如无限递归)
  • **堆溢出(Heap OOM):**OOM的一种最常见形式,new的对象太多了

三、JVM 类加载机制

下面是类加载的生命周期:


3.1 类加载过程

  1. **加载:**将 .class 文件找到,打开文件并且读取文件的数据到内存中,根据代码中写的 " 全限定类名 "(import...),找到对应的class文件
  2. **验证:**检查字节码是否合法、安全
  3. **准备:**给创建的类对象分配内存,赋默认值
  4. **解析:**将常量池的符号引用替换为直接引用,也就是初始化常量
  5. **初始化:**针对类对象进行初始化,初始化类的静态成员,执行静态代码块,对父类的加载

类加载的时机:

  1. 在new这个类的实例的时候
  2. 在调用这个类的静态方法或访问类的静态成员
  3. 在对其子类进行加载时,也会触发父类的加载

类加载只加载一次(单例)


3.2 类加载器与双亲委派模型

双亲委派模型出现在类加载的第一步 (加载),因此就有了类加载器的出现
概念:

双亲委派模型是指如果一个类加载器收到了类加载的请求,它首先不会去加载这个类,而是交给父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求都会到达最顶层的启动类加载器中,只有当父加载器无法完成这个请求时,子类加载器才会尝试去加载完成,如果都未加载成功会抛出 ClassNotFoundException异常

JVM中默认包含3个类加载器:

  1. **BootstrapClassLoader :**负责加载标准库中的类
  2. **ExtensionClassLoader:**负责加载Java扩展库中的类(JDK厂商进行的扩展)
  3. **ApplicationClassLoader:**负责加载第三方库,以及当前项目中的类

核心作用:

  1. 安全防护:防止核心类被篡改,会优先加载原生类
  2. 避免重复加载:同一个类只会被加载一次
  3. 统一类加载规则:保证全环境中基础类版本一致

相关推荐
jarreyer15 分钟前
【AI Agent】大模型自动化载体
运维·自动化
xixingzhe228 分钟前
SpringBoot + Nginx 免鉴权接口安全防护方案
运维·nginx·安全
翼龙云_cloud1 小时前
阿里云国际代理商:阿里云 ECS 全维度监控配置教程
运维·阿里云·云计算·监控
笑锝没心没肺2 小时前
fail2ban工具安装配置及使用
linux·运维·服务器
zt1985q3 小时前
本地部署开源智能家居集成平台 ioBroker 并实现外部访问( Windows 版本)
运维·服务器·智能家居
大卡片3 小时前
linux内核驱动开发
linux·运维·驱动开发
心心喵4 小时前
[linux] nohup和pm2的区别 进程保活
linux·运维·服务器
NGINX开源社区5 小时前
F5 NGINX Ingress Controller 5.3.0 新增功能
运维
SelectDB7 小时前
Apache Doris 在 AgentLogsBench 中领先,支撑 Agent 可观测性生产负载
运维·数据库·agent
荣-7 小时前
从两天到十几分钟:一套 YT Crash 自动化分析工具完整工程复盘
大数据·运维·自动化