java虚拟机---JVM

JVM

JVM,也就是 Java 虚拟机,它最主要的作用就是对编译后的 Java 字节码文件逐行解释,翻译成机器码指令,并交给对应的操作系统去执行。

JVM 的其他特性有:

  • JVM 可以自动管理内存,通过垃圾回收器回收不再使用的对象并释放内存空间。
  • JVM 包含一个即时编译器 JIT ,它可以在运行时将热点代码缓存到 codeCache 中,下次执行的时候不用再一行一行的解释,而是直接执行缓存后的机器码,执行效率会大幅提高。
bash 复制代码
注:JIT 和 JNI 的区别:
JIT 是"即时编译"(Just-In-Time Compilation)的缩写。
它是一种在程序运行时将字节码(bytecode)编译成机器码的技术,主要用于提高程序的执行效率。

JNI 是Java 提供的一种编程框架,允许 Java 代码与用其他语言(如 C、C++)编写的本地代码交互。
通过 JNI,Java 程序可以调用本地方法(native methods),这些方法通常是用 C 或 C++ 实现的,
并编译为动态链接库(DLL 或 SO 文件)。反过来,本地代码也可以调用 Java 方法
  • 任何可以通过 Java 编译的语言,比如说 Groovy、Kotlin、Scala 等,都可以在 JVM 上运行。
bash 复制代码
"通过 Java 编译"在问题中的含义是指"编译成 Java 字节码",每种语言都有自己的专用编译器。

JVM的组织架构

JVM 要解释执行需要进行三个步骤:加载 .class 文件 -> 准备数据 -> 执行

因此 JVM 大致可以划分为三个部分:类加载器、运行时数据区和执行引擎

JVM内存模型

Java 虚拟机(JVM)在运行 Java 程序时,会将内存划分为若干区域,每个区域有其特定的功能。

  1. 程序计数器(Program Counter Register)

    • 作用:记录当前线程正在执行的字节码指令的地址,用于控制程序的执行流程。
    • 特点:每个线程都有独立的程序计数器,线程之间互不干扰。
  2. Java 虚拟机(Java Virtual Machine Stacks)

    • 作用:为 Java 方法(非 native 方法)的执行提供内存空间。
    • 特点:每个线程拥有自己的虚拟机栈,栈中包含多个栈帧(Stack Frame)。每个栈帧对应一次方法调用,存储局部变量表、操作数栈、动态链接和方法出口等信息。
  3. 本地方法栈(Native Method Stacks)

    • 作用:为 native 方法(用 C/C++ 等语言编写的方法)提供内存支持。
    • 特点:与 Java 虚拟机栈类似,但专用于 native 方法的执行。
  4. Java (Java Heap)

    • 作用:存储对象实例和数组。
    • 特点:JVM 中最大的内存区域,所有对象实例都在堆上分配内存。堆是垃圾收集器(GC)的主要管理区域。
  5. 方法区(Method Area)

    • 作用:存储类的元数据信息(如类结构)、静态变量、常量以及即时编译器编译后的代码。
    • 特点:在 JDK 8 及之后,方法区被元空间(Metaspace)取代,元空间使用本地内存而非 JVM 堆内存。
  6. 运行时常量池(Runtime Constant Pool)

    • 作用:存储编译期生成的字面量(如字符串常量)和符号引用。
    • 特点:是方法区的一部分,包含每个类的常量池表。JDK 8 方法区变元空间,运行时常量池就放在堆上。
  7. 直接内存(Direct Memory)

    • 作用:不属于 JVM 运行时数据区,但常用于 NIO(如 ByteBuffer)等场景。
    • 特点:使用本地内存,不受 JVM 堆大小限制。

内存区域变化


主要是方法区到元空间,以及常量池的变化

字符串常量池,类常量池,运行时常量池存储的都是什么啊?

1.字符串常量池
  • 字符串常量池主要存储 字符串字面量,也就是在 Java 代码中用双引号括起来的字符串,例如 "Hello"、"World" 等。
  • 它的设计目的是为了复用这些字符串对象,确保 JVM 中每个唯一的字符串字面量只有一份,从而节省内存。
  • String s1 = "Hello"; String s2 = "Hello";用 s1 == s2 验证,结果为 true
  • 用 new String("Hello") 创建字符串,这个对象会分配在堆上,可以通过 String.intern() 方法将它放入字符串常量池。
2.类常量池
  • javac 将源文件编译成 .class 文件,类常量池指的是这个文件的一部分,是在磁盘上的
  • 类常量池存储了在编译时生成的 字面量符号引用
  • 类加载阶段JVM 加载 .class 文件 时,会把类常量池的内容 拷贝到方法区(JDK 8+ 在元空间)
  • 在解析阶段,逻辑地址会被替换为 实际的内存地址,部分数据进入 运行时常量池。

例如对于一个类:

java 复制代码
package example;

import utils.Helper;  // 🔹 导入外部类

public class Main {
    // 🔹 静态常量
    static final double PI = 3.14159;

    // 🔹 实例变量
    private String name;

    // 🔹 构造方法
    public Main(String name) {
        this.name = name;
    }

    // 🔹 普通方法
    public void greet() {
        System.out.println("Hello, " + name);
    }

    public static void main(String[] args) {
        // 🔹 创建对象
        Main obj = new Main("Alice");
        obj.greet();

        // 🔹 调用外部类方法
        Helper.sayHello();
    }
}

类常量池为:

java 复制代码
CONSTANT_Class      example/Main
CONSTANT_Class      utils/Helper  // 🔹 外部类
CONSTANT_Fieldref   example/Main.PI
CONSTANT_Fieldref   example/Main.name
CONSTANT_Methodref  example/Main.<init>  // 🔹 构造方法
CONSTANT_Methodref  example/Main.greet   // 🔹 方法
CONSTANT_Methodref  utils/Helper.sayHello  // 🔹 外部类方法
CONSTANT_String     "Hello, "
CONSTANT_String     "Alice"
CONSTANT_Double     3.14159
3.运行时常量池
  • 运行时常量池是 JVM 在运行时为每个类或接口维护的常量池
  • 运行时常量池支持动态链接和运行时解析,例如将对 System.out.println 的符号引用解析为具体的对象和方法地址
  • 它还能在程序运行时扩展,例如添加新的字符串常量
  • 运行时常量池 = 类常量池内容 + 直接引用 + 动态常量

堆内存

堆 是Java虚拟机(JVM)中内存管理的一个重要区域,主要用于存放对象实例和数组。随着JVM的发展和不同垃圾收集器的实现,堆的具体划分可能会有所不同,但通常可以分为以下几个部分:

  • 新生代 :新生代又被划分为 Eden 空间和两个 Survivor 空间(From 和 To)
    • 新创建的对象会被分配到 Eden 空间。
    • Eden 区填满时,会触发一次 Minor GC,清除不再使用的对象。存活下来的对象会从 Eden 区移动到 Survivor 区
  • 老年代:对象在新生代中经历多次 GC 后,如果仍然存活,会被移动到老年代。当老年代内存不足时,会触发 Major GC,对整个堆进行垃圾回收。
  • 大对象区:在某些JVM实现中(如G1垃圾收集器),为大对象分配了专门的区域,这部分区域在老年代。

对象的内存布局

对象的内存布局是由 Java 虚拟机规范定义的,但具体的实现细节各有不同,如 HotSpot 和 OpenJ9 就不一样。HotSpot:

对象四种引用

四种分别是"强、软、弱、虚"

  • 强引用:Object obj = new Object(); 只要存在就不回收;
  • 软引用:SoftReference softRef = new SoftReference<>(new Object()); 内存不足回收,常用于实现内存敏感的缓存(如图片缓存),在内存压力大时自动清理;
  • 弱引用:WeakReference weakRef = new WeakReference<>(new Object()); 一定回收,临时引用,避免内存占用,比如 threadlocal 里的 key;
  • 虚引用:PhantomReference phantomRef = new PhantomReference<>(new Object(), queue); 通常与 ReferenceQueue 结合使用,当对象被回收时,虚引用会被放入关联的 ReferenceQueue,在对象被回收时收到通知。虚引用不可达;
  • Java.lang.ref 包下的类

内存泄漏和内存溢出

用一个比较有味道的比喻来形容就是,内存溢出是排队去蹲坑,发现没坑了;内存泄漏,就是有人占着茅坑不拉屎,导致坑位不够用。

  • 内存泄漏举例:1、静态属性导致内存泄露 2、 未关闭的资源 3、 使用ThreadLocal
  • 静态属性(用 static 修饰的字段)属于类级别,其生命周期与类的加载和卸载绑定,其超长的生命周期和全局可见性导致更容易内存泄漏
    ThreadLocal---java
相关推荐
跟着珅聪学java18 分钟前
spring boot +Elment UI 上传文件教程
java·spring boot·后端·ui·elementui·vue
我命由我1234523 分钟前
Spring Boot 自定义日志打印(日志级别、logback-spring.xml 文件、自定义日志打印解读)
java·开发语言·jvm·spring boot·spring·java-ee·logback
lilye6624 分钟前
程序化广告行业(55/89):DMP与DSP对接及数据统计原理剖析
java·服务器·前端
战族狼魂4 小时前
CSGO 皮肤交易平台后端 (Spring Boot) 代码结构与示例
java·spring boot·后端
xyliiiiiL5 小时前
ZGC初步了解
java·jvm·算法
杉之5 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
hycccccch6 小时前
Canal+RabbitMQ实现MySQL数据增量同步
java·数据库·后端·rabbitmq
天天向上杰7 小时前
面基JavaEE银行金融业务逻辑层处理金融数据类型BigDecimal
java·bigdecimal
请来次降维打击!!!7 小时前
优选算法系列(5.位运算)
java·前端·c++·算法
用键盘当武器的秋刀鱼7 小时前
springBoot统一响应类型3.5.1版本
java·spring boot·后端