Java虚拟机(JVM): 是Java程序运行的环境,它负责解释执行 Java 字节码(Bytecode),并提供了内存管理、垃圾回收等功能。JVM 是 Java 程序在不同平台上实现跨平台的关键。JVM 可以独立于 JDK 存在,例如,你可以使用 JRE(Java Runtime Environment)来运行 Java 程序,而无需 JDK。
Java虚拟机的存在原因
-
跨平台性: Java 的跨平台性是其最重要的特性之一。Java 程序可以在不同的操作系统和硬件平台上运行,这得益于 Java 虚拟机的存在。Java 源代码编译成字节码(Bytecode)后,由不同平台上的 Java 虚拟机负责解释执行,因此实现了跨平台的能力。
-
安全性: Java 虚拟机通过字节码校验器(Bytecode Verifier)等机制来确保代码的安全性。字节码校验器会检查字节码文件,防止恶意代码通过各种漏洞进入系统。
-
垃圾回收: Java 虚拟机提供了垃圾回收(Garbage Collection)机制,自动管理程序的内存,避免了手动内存管理可能带来的内存泄漏和空指针异常等问题。
-
性能优化: Java 虚拟机的即时编译器(JIT Compiler)可以将字节码实时编译成本地机器码,从而提高程序的执行效率。
Java内存模型的设计
Java 内存模型(Java Memory Model,JMM)规定了 Java 程序中的线程如何与内存交互。它主要解决了多线程并发访问共享变量可能引发的可见性、有序性和原子性问题。
-
可见性: JMM 保证一个线程修改的共享变量对其他线程是可见的。在多核处理器和多级缓存的环境中,线程对共享变量的修改可能存在延迟或不可见的情况,JMM 通过内存屏障等机制来确保线程之间的数据可见性。
-
有序性: JMM 保证程序的执行顺序符合预期。在多线程环境中,线程的指令可能会乱序执行,导致程序出现异常的行为。JMM 使用内存屏障来确保指令的有序性。
-
原子性: JMM 保证基本数据类型的读取和赋值操作是原子性的。对于复合操作,JMM 使用锁和 volatile 变量等机制来确保原子性。
Java 内存模型的设计旨在解决多线程并发访问共享数据可能导致的各种问题,保证了 Java 程序的正确性和稳定性。
JVM加载类的流程
JVM加载类的流程通常包括以下几个步骤:
-
加载(Loading):加载是指查找并加载类的二进制数据(class文件),并将其转换为 JVM 内部的数据结构(Class对象)。类的加载通常由类加载器(ClassLoader)来完成。在加载阶段,类加载器会根据类的全限定名(Fully Qualified Name)来定位类文件,然后读取类文件的字节码数据,并创建对应的 Class 对象。
-
链接(Linking):链接阶段分为三个子阶段:验证(Verification)、准备(Preparation)、解析(Resolution)。
- 验证:验证阶段用于确保被加载的类符合 JVM 规范,例如检查类文件格式、语义是否合法等。
- 准备:准备阶段为类的静态变量分配内存空间,并设置默认初始值(零值)。
- 解析:解析阶段将类中的符号引用转换为直接引用,即将类、方法、字段等符号引用解析为内存地址。
-
初始化(Initialization):初始化阶段是类加载过程中的最后一步,负责对类进行初始化操作,包括执行类的静态变量赋值和静态代码块等初始化代码。在初始化阶段,JVM 会按照定义顺序依次执行类的静态变量赋值和静态代码块,并保证初始化的线程安全性。
需要注意的是,类加载过程是懒加载的,即只有当程序首次使用到某个类时,JVM 才会触发该类的加载过程。此外,类加载过程是线程安全的,JVM 会确保同一个类只被加载一次,避免重复加载和初始化。