深入解析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. 统一类加载规则:保证全环境中基础类版本一致

相关推荐
风曦Kisaki1 小时前
Nginx代理与LVS(NAT/DR)全方位对比
运维·nginx·lvs
maosheng11461 小时前
NFS服务器的搭建有多种类型linux-linux
linux·运维·服务器
普通young man1 小时前
Linux基础开发工具集合
linux·运维·服务器
李少兄1 小时前
深入理解 Web 服务器、Servlet 容器与现代 Java Web 架构
java·服务器·servlet
运维行者_1 小时前
使用Applications Manager监控的关键MongoDB指标
服务器·开发语言·网络·数据库·mongodb·机器学习·云计算
lxllzwj52013141 小时前
Mac如何像shell一样丝滑的使用item2连接服务器.
服务器·macos·github
Elsius.1 小时前
网络运维与网络安全 阶段一 基础篇二十一
运维
weixin_468466851 小时前
Jellyfin 家庭媒体中心从零搭建指南
服务器·docker·容器·自动化·jellyfin·媒体中心
随便做点啥2 小时前
4090 8卡服务器 - Qwen3.6-27B-AWQ 完整压测报告 (V2.0)
服务器·经验分享