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

相关推荐
大树883 小时前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠3 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质4 小时前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
小宇宙Zz4 小时前
Maven依赖冲突
java·服务器·maven
源分享4 小时前
Java线程同步的多种实现方法(非常详细)
java·开发语言·jvm
Inhand陈工5 小时前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智5 小时前
ARP代理--工作原理
运维·网络·arp·arp代理
shushangyun_5 小时前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
JAVA9655 小时前
JAVA面试-JVM篇 03-JVM运行时数据区哪些是线程私有的哪些是共享的
java·jvm·面试
古城小栈5 小时前
Unix 与 Linux 异同小叙
linux·服务器·unix