认识JVM
1.Java文件编译的过程
- 程序员编写的.java文件
- 由javac编译成字节码文件.class:(为什么编译成class文件,因为JVM只认识.class文件)
- 在由JVM编译成电脑认识的文件 (对于电脑系统来说 文件代表一切)
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=%2FUsers%2Fwuzerui%2FDesktop%2Fpictrue%2Fimage-20240706162931054.png&pos_id=img-ZmvUie80-1722097969174
2 为什么说java是跨平台语言
这个夸平台是中间语言(JVM)实现的夸平台
java有JVM从软件层面屏蔽了底层硬件、指令层面的细节让他兼容各种系统
3.解析JVM运行时数据区
1.方法区(Method Area)
方法区是所有线程共享的内存区域,它用于存储已被Java虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
它有个别命叫Non-Heap(非堆)。当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。
2.Java堆(Java Heap)
java堆是java虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。
在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
java堆是垃圾收集器管理的主要区域,因此也被成为“GC堆”。
从内存回收角度来看java堆可分为:新生代和老生代。
从内存分配的角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区。
无论怎么划分,都与存放内容无关,无论哪个区域,存储的都是对象实例,进一步的划分都是为了更好的回收内存,或者更快的分配内存。
根据Java虚拟机规范的规定,java堆可以处于物理上不连续的内存空间中。当前主流的虚拟机都是可扩展的(通过 -Xmx 和 -Xms 控制)。如果堆中没有内存可以完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
3 程序计数器(Program Counter Register)
程序计数器是一块较小的内存空间,它可以看作是:保存当前线程所正在执行的字节码指令的地址(行号)
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。称之为“线程私有”的内存。程序计数器内存区域是虚拟机中唯一没有规定OutOfMemoryError情况的区域。
总结:也可以把它叫做线程计数器
例子:在java中最小的执行单位是线程,线程是要执行指令的,执行的指令最终操作的就是我们的电脑,就是 CPU。在CPU上面去运行,有个非常不稳定的因素,叫做调度策略,这个调度策略是时基于时间片的,也就是当前的这一纳秒是分配给那个指令的。
假如:线程A在看直播突然,线程B来了一个视频电话,就会抢夺线程A的时间片,就会打断了线程A,线程A就会挂起然后,视频电话结束,这时线程A究竟该干什么?
(线程是最小的执行单位,他不具备记忆功能,他只负责去干,那这个记忆就由:程序计数器来记录)
4.Java虚拟机栈(Java Virtual Machine Stacks)
java虚拟机是线程私有的,它的生命周期和线程相同。
虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
解释:每虚拟机栈中是有单位的,单位就是栈帧,一个方法一个栈帧。一个栈帧中他又要存储,局部变量,操作数栈,动态链接,出口等。
解析栈帧:
局部变量表:是用来存储我们临时8个基本数据类型、对象引用地址、returnAddress类型。(returnAddress中保存的是return后要执行的字节码的指令地址。)
操作数栈:操作数栈就是用来操作的,例如代码中有个 i = 6*6,他在一开始的时候就会进行操作,读取我们的代码,进行计算后再放入局部变量表中去
动态链接:假如我方法中,有个 service.add()方法,要链接到别的方法中去,这就是动态链接,存储链接的地方。
出口:出口是什呢,出口正常的话就是return 不正常的话就是抛出异常落
思考:
一个方法调用另一个方法,会创建很多栈帧吗?
答:会创建。如果一个栈中有动态链接调用别的方法,就会去创建新的栈帧,栈中是由顺序的,一个栈帧调用另一个栈帧,另一个栈帧就会排在调用者下面
栈指向堆是什么意思?
栈指向堆是什么意思,就是栈中要使用成员变量怎么办,栈中不会存储成员变量,只会存储一个应用地址,堆中的数据等下讲
递归的调用自己会创建很多栈帧吗?
递归的话也会创建多个栈帧,就是一直排下去
5 本地方法栈(Native Method Stack)
本地方法栈很好理解,他很栈很像,只不过方法上带了 native 关键字的栈字
它是虚拟机栈为虚拟机执行Java方法(也就是字节码)的服务
native关键字的方法是看不到的,必须要去oracle官网去下载才可以看的到,而且native关键字修饰的大部分源码都是C和C++的代码。
同理可得,本地方法栈中就是C和C++的代码
JVM(Java Virtual Machine,Java 虚拟机)不仅仅是类装载器和解释器。JVM 是一种用于执行 Java 字节码的虚拟机,提供了一种计算环境,使得 Java 程序可以在不同平台上运行而无需修改。JVM 的主要组件包括类装载器、解释器、即时编译器(JIT 编译器)、垃圾收集器等。
JVM 的主要组件
- 类装载器(Class Loader)
- 负责加载 .class 文件到 JVM 中,并将其转化为 JVM 可以使用的类对象。
- 类装载器的过程包括:加载(Loading)、连接(Linking,包含验证、准备和解析三个阶段)、初始化(Initialization)。
- 字节码解释器(Interpreter)
- 负责解释执行 Java 字节码,将字节码逐条转换为相应的机器指令并执行。
- 解释器的执行速度相对较慢,但其跨平台性和启动速度较快。
- 即时编译器(JIT Compiler)
- 为了提高性能,JVM 采用了即时编译技术。JIT 编译器会将热点代码(即执行频率高的代码)编译为本地机器码,从而提高执行效率。
- 常见的 JIT 编译器包括 C1 和 C2 编译器。
- 垃圾收集器(Garbage Collector)
- 负责自动管理内存,回收不再使用的对象所占用的内存空间,避免内存泄漏。
- JVM 提供了多种垃圾收集算法,如串行收集器、并行收集器、CMS 收集器和 G1 收集器等。
- 运行时数据区(Runtime Data Area)
- JVM 在执行 Java 程序时会划分出若干内存区域,包括方法区、堆区、栈区、本地方法栈和程序计数器。
- 方法区(Method Area):存储类信息、常量、静态变量、JIT 编译后的代码等。
- 堆区(Heap Area):存储对象实例和数组。
- 栈区(Stack Area):每个线程都有自己的栈区,存储局部变量、操作数栈等。
- 本地方法栈(Native Method Stack):为本地方法调用服务。
- 程序计数器(Program Counter Register):指示当前线程正在执行的字节码指令的地址。
JVM 的工作流程
- 类加载:
- 当 JVM 启动时,类装载器会加载必要的类文件,将其转换为内存中的类对象。
- 类装载过程包括加载、验证、准备、解析和初始化五个阶段。
- 字节码解释和执行:
- JVM 使用解释器逐条解释执行字节码指令。
- 对于执行频率高的代码,JIT 编译器会将其编译为本地机器码,提高执行效率。
- 内存管理:
- JVM 自动管理内存,通过垃圾收集机制回收不再使用的对象。
- 线程管理:
- JVM 提供线程管理功能,可以并发执行多个线程。
JVM 如何处理字节码文件
- 类加载:
- JVM 使用类加载器(ClassLoader)将字节码文件加载到内存中。
- 字节码验证:
- JVM 验证字节码的正确性,以确保其不包含非法指令,防止恶意代码破坏 JVM 的安全性。
- 字节码解释:
- JVM 的解释器将字节码逐条解释执行。解释器会读取字节码指令,并将其转换为相应的机器码执行。
- 即时编译(JIT):
- 为了提高执行效率,JVM 还可以使用即时编译器(JIT Compiler)将字节码动态编译为本地机器码,从而减少解释执行的开销。
字节码文件的结构
字节码文件的结构是严格定义的,以下是其主要组成部分:
- 魔数(Magic Number):
- 标识该文件是一个 Java 字节码文件。其值固定为 0xCAFEBABE。
- 版本信息:
- 包括次版本号和主版本号,标识编译器版本。
- 常量池(Constant Pool):
- 包含字面量和符号引用等信息。
- 访问标志(Access Flags):
- 描述类或接口的访问权限及属性(如是否为接口、抽象类、public 类等)。
- 类索引、超类索引和接口索引集合:
- 指向常量池中的类名、超类名和接口名。
- 字段表(Fields):
- 包含类中字段的描述信息。
- 方法表(Methods):
- 包含类中方法的描述信息及其字节码。
- 属性表(Attributes):
- 包含源文件名、调试信息、注解等其他信息。
示例
以下是一个简单的 Java 类及其对应的字节码示例:
Java 源代码(HelloWorld.java)
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
javac HelloWorld.java
生成的字节码文件(HelloWorld.class)
通过 javap
工具可以查看字节码文件的内容:
javap -c HelloWorld
输出内容(部分):
public class HelloWorld {
public HelloWorld();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello, World!
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
上述输出显示了 HelloWorld
类的构造方法和 main
方法的字节码指令。
字节码文件是 Java 编译器生成的中间文件,包含了 JVM 可以理解和执行的指令集。它是 Java 跨平台能力的基础,因为相同的字节码文件可以在任何实现了 JVM 的平台上运行。JVM 通过类加载、字节码验证、解释执行和即时编译等步骤来处理和执行字节码文件。
JVM中的新生代和老生代
在 Java 虚拟机(JVM)中,堆内存划分为几个不同的区域,以便更有效地管理内存和垃圾回收。堆内存主要分为两个区域:新生代(Young Generation)和老生代(Old Generation)。每个区域有不同的内存管理策略和垃圾回收机制。
新生代(Young Generation)
特点:
-
对象生命周期短: 新生代主要存放新创建的对象。大多数对象在这里被快速创建和销毁,生命周期短。
-
-
分区
- 新生代进一步分为三个区域:Eden 区、Survivor 区(包括 From 和 To 两个 Survivor 区)。
- Eden 区: 大部分新对象在这里分配内存。
- Survivor 区: 当对象在 Eden 区存活过一次垃圾回收后,会被移动到 Survivor 区。两个 Survivor 区交替使用。
-
垃圾回收: 新生代使用一种叫做 Minor GC(小型垃圾回收)的机制。由于对象生命周期短,Minor GC 频率较高,但速度快,因为大部分对象是短命的,很快就被回收。
老生代(Old Generation)
特点:
- 对象生命周期长: 老生代存放生命周期较长的对象。经过多次 Minor GC 仍然存活的对象会被移动到老生代。
- 垃圾回收: 老生代使用 Major GC 或 Full GC 进行垃圾回收。这种垃圾回收通常较少发生,但每次回收涉及的对象数量多,回收时间长。
新生代和老生代的区别
- 对象生命周期:
- 新生代: 存放生命周期短的对象。
- 老生代: 存放生命周期长的对象。
- 垃圾回收频率和时间:
- 新生代: Minor GC 频繁发生,但每次回收速度快。
- 老生代: Major GC 或 Full GC 发生频率低,但每次回收时间较长。
- 内存分配策略:
- 新生代: 采用复制算法,Eden 区和两个 Survivor 区交替使用。
- 老生代: 采用标记-清除或标记-整理算法。
详细描述内存分配和垃圾回收过程
新生代内存分配和回收过程:
- 对象创建:
- 新对象首先在 Eden 区分配内存。如果 Eden 区满了,则触发一次 Minor GC。
- Minor GC:
- 检查 Eden 区的所有对象,将存活的对象移动到一个 Survivor 区(假设为 To 区)。
- 清空 Eden 区。
- 将 From 区的存活对象移动到 To 区,交换 From 和 To 的角色。
- 若对象在 Survivor 区多次存活,则晋升到老生代。
老生代内存分配和回收过程:
- 对象晋升:
- 在新生代经过多次 Minor GC 仍然存活的对象会被晋升到老生代。
- Major GC 或 Full GC:
- 当老生代空间不足时,会触发 Major GC 或 Full GC。
- Major GC 采用标记-清除或标记-整理算法,回收老生代中不再使用的对象。
- Full GC 不仅回收老生代,还包括新生代和其他内存区域。
总结
- 新生代: 存放新创建且生命周期短的对象,使用 Minor GC 进行频繁且快速的垃圾回收。
- 老生代: 存放生命周期长的对象,使用 Major GC 或 Full GC 进行较少但耗时较长的垃圾回收。
这种分代回收策略有效地提高了垃圾回收的效率,优化了内存管理。
总结
JVM 是一个复杂的执行环境,包含类装载器、解释器、JIT 编译器和垃圾收集器等多个组件。类装载器负责加载类文件,解释器和 JIT 编译器负责执行字节码,垃圾收集器负责内存管理。通过这些组件的协同工作,JVM 提供了一个高效的跨平台执行环境。