一、JVM启动流程
Java虚拟机(JVM)的启动流程是Java程序运行的第一步,它涉及多个阶段,从初始化到最终执行Java代码。下面详细阐述JVM的启动流程:
1. 加载JVM库
- 查找并加载JVM库:JVM的启动首先依赖于JVM库(通常是
libjvm.so
在Linux上,或jvm.dll
在Windows上)。这些库包含了JVM执行所需的所有底层代码。 - 设置JVM参数:JVM启动时可以接受来自命令行或配置文件(如
java.ini
或jvm.config
)的参数,这些参数用于配置JVM的行为,如堆内存大小、垃圾收集器类型等。
2. 初始化JVM
- 创建JVM实例:JVM库被加载后,JVM实例被创建。这个实例将负责执行Java程序。
- 初始化内部数据结构:JVM实例创建后,会初始化其内部的数据结构,如类加载器、运行时数据区(堆、栈、方法区等)、垃圾收集器等。
3. 加载并初始化类加载器
- 引导类加载器(Bootstrap ClassLoader):这是JVM自带的类加载器,负责加载Java核心库(如
java.lang.*
、java.util.*
等)。 - 扩展类加载器(Extension ClassLoader):负责加载JDK扩展目录(如
jre/lib/ext
)中的类库。 - 系统类加载器(System ClassLoader):也称为应用程序类加载器,负责加载用户路径(
CLASSPATH
)上的类库。
4. 加载并初始化Java应用
- 加载主类:根据命令行指定的主类(使用
-cp
或-classpath
参数指定类路径,-jar
参数指定JAR包,或直接在命令行中指定类名),JVM使用系统类加载器加载主类。 - 初始化主类:加载主类后,JVM会执行主类的静态初始化块和静态变量初始化。
- 执行主方法:最后,JVM会查找并执行主类的
main
方法,这是Java程序的入口点。
二、JVM基本结构
JVM的基本结构主要包括几个关键部分:PC寄存器、Java栈、方法区、Java堆、本地方法栈等。下面详细介绍这些部分。
2.1 PC寄存器
- 定义:PC寄存器(Program Counter Register)是每个线程私有的,用于存储当前线程执行的Java方法中的指令地址(即下一条要执行的指令的地址)。
- 作用:在Java虚拟机的概念模型中,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
- 特殊情况:当线程执行的是Java方法时,PC寄存器中保存的是正在执行的Java字节码指令的地址;当线程执行的是本地方法(Native Method)时,PC寄存器的值则是undefined,因为本地方法是通过本地方法栈来执行的,不依赖PC寄存器。
2.2 方法区
- 定义:方法区(Method Area)是所有线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 内容:
- 类型信息:包括类的全限定名、类的直接超类的全限定名、类的访问修饰符等。
- 常量池:用于存放编译期生成的各种字面量和符号引用。
- 字段信息:包括字段名、字段类型、字段修饰符等。
- 方法信息:包括方法名、方法的返回类型、方法的参数类型、方法的修饰符、方法的字节码等。
- JDK版本差异:
- JDK 6及之前:字符串常量池存放在方法区。
- JDK 7及之后:字符串常量池被移至Java堆中。
2.3 Java堆
- 定义:Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,几乎所有的对象实例都在这里分配内存。
- 特点:
- 垃圾收集的主要区域:Java堆是垃圾收集器管理的主要区域,因此也被称为“GC堆”。
- 分代收集:Java堆通常被分为新生代和老年代,新生代又分为Eden区、两个Survivor区(From/To),以支持分代收集算法。
2.4 Java栈
- 定义:Java栈(Java Stack)是线程私有的,它的生命周期与线程相同。每个Java方法被执行的时候,都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
- 组成:
- 局部变量表:用于存放方法参数和方法内部定义的局部变量。
- 操作数栈:作为计算过程中变量临时的存储空间。
- 动态链接:指向运行时常量池的方法引用或常量引用。
- 方法出口:包含方法返回地址和异常处理等信息。
2.5 本地方法栈
- 定义:本地方法栈(Native Method Stack)与Java栈的作用非常相似,其区别不过是Java栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。
- 特点:本地方法栈也会抛出
StackOverflowError
和OutOfMemoryError
异常。
三、 内存模型
Java内存模型(Java Memory Model, JMM)定义了线程和主内存之间的抽象关系,以及线程之间共享变量的可见性和有序性规则。
3.1 主内存与工作内存
- 主内存:所有变量都存储在主内存中,这是所有线程共享的内存区域。
- 工作内存:每个线程都有自己的工作内存,是线程私有的,用于存储主内存中变量的副本。
3.2 变量的读写操作
- 读操作:当线程读取一个变量时,它会从主内存中读取变量的值,然后将其复制到自己的工作内存中。
- 写操作:当线程修改一个变量的值时,它会先在自己的工作内存中修改这个变量的副本,然后再将修改后的值写回主内存。
3.3 可见性与有序性
- 可见性:一个线程修改了某个变量的值,这个新值对其他线程来说是立即可见的,称为变量的可见性。Java提供了
volatile
关键字来保证变量的可见性。 - 有序性:Java虚拟机在执行Java程序时,为了提高性能,可能会对指令进行重排序。但是,这种重排序不会影响单线程程序的执行结果,但在多线程环境下可能会导致问题。Java提供了
synchronized
和volatile
等关键字来保证指令的有序性。
四、 JVM类加载机制
Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析、初始化,最终形成可以被虚拟机直接使用的Java类型。类加载机制主要包括以下几个步骤:
4.1 加载
- 定义:通过类的全限定名获取类的二进制字节流,将其存储在方法区的内存中,并在Java堆中创建一个
java.lang.Class
对象来表示这个类。 - 双亲委派模型:类加载器采用双亲委派模型来加载类,即一个类加载器在加载类之前,会先让它的父类加载器尝试加载这个类,如果父类加载器无法加载,才由当前类加载器来加载。
4.2 链接
- 验证:确保被加载的类的正确性,包括文件格式验证、元数据验证、字节码验证和符号引用验证。
- 准备:为类的静态变量分配内存,并设置默认的初始值(注意,这里只是分配内存和设置初始值,不会执行初始化代码)。
- 解析:将类、接口、字段和方法的符号引用转换为直接引用。
4.3 初始化
- 定义:为类的静态变量赋予正确的初始值(如果类中有静态代码块,也会在这个阶段执行)。
- 初始化时机:只有当类被真正使用时,才会被初始化。这里的“使用”包括主动使用和被动使用两种情况。主动使用包括创建类的实例、访问类的静态变量或静态方法、反射调用等;被动使用则不会导致类的初始化,如通过子类引用父类的静态字段、通过数组定义类引用等。
总结
JVM的启动流程、基本结构、内存模型和类加载机制是Java虚拟机运行Java程序的基础。了解这些概念和机制对于深入理解Java程序的行为、优化程序性能以及解决多线程和并发问题都具有重要意义。通过本文的详细阐述,希望读者能够对JVM有更深入的认识和理解。
个人网站:www.rebootvip.com
更多SEO优化内容 网站学习 google adsense
资源免费分享下载:电子书,项目源码,项目实战
** ** Python 从入门到精通 ** **
** ** Java 从入门到精通 ** **
** ** Android从入门到精通 ** **