入门
什么是JVM
JVM:Java Virtual Machine,Java虚拟机。
JVM是JRE(Java Runtime Environment)的一部分,安装了JRE就相当于安装了JVM,就可以运行Java程序了。JVM的作用:加载并执行Java字节码(.class)文件。
常见的JVM
Java虚拟机要实现Java虚拟机规范,它定义了Java虚拟机的结构、指令集、类文件格式、类加载器、字节码执行引擎等方面的内容。
JVM结构
执行引擎Execution Engine
JVM执行引擎通常由两个主要组成部分构成:解释器和即时编译器(Just-In-Time Compiler,JIT Compiler)。
解释器:当Java字节码被加载到内存中时,解释器逐条解析并直接执行字节码指令。
即时编译器(JIT Compiler):即时编译器将字节码动态地编译为本地机器码,之后再执行。即时编译器根据运行时的性能数据和优化技术,对经常执行的热点代码进行优化,从而提高程序的性能。即时编译器可以将经过优化的代码缓存起来,以便下次再次执行时直接使用。
本地方法接口Native Interface
本地接口的作用是融合不同的编程语言为 Java 所用。当Java代码调用本地方法 (被native所修饰的方法)时,JVM会将控制权转移到本地方法实现所在的本地库 。本地库 是一个包含本地方法实现的动态链接库(DLL - windows函数库)或共享对象文件(SO - Linux函数库)。它是使用其他编程语言编写的,通常是为了与底层操作系统或硬件进行交互。本地库可以通过JNI加载到JVM中,并提供给Java代码调用。
例如Thread类中有一些标记为native的方法 操作底层操作系统线程
本地方法栈Native Method Stack
本地方法栈(Native Method Stack):本地方法栈存储了从Java代码中调用本地方法时所需的信息。是线程私有 的。
本地方法栈是JVM专门为调用非Java语言方法而设计的,它与操作系统和硬件交互,通过JNI为Java程序提供更强大的功能。每个线程都有自己的本地方法栈,保证本地方法的调用是独立且线程安全的。
PC寄存器(程序计数器PC Register)
PC寄存器(程序计数器,Program Counter Register)是JVM中线程私有 的一块小内存区域。用于记录当前线程 正在执行的字节码指令的地址或行号。它类似于一个指针,指向线程正在执行的字节码指令的下一条指令。
类加载器ClassLoader
类加载器(ClassLoader)是JVM的一个关键组件,用于动态加载、链接和初始化Java类。它的主要职责是在程序运行时将类的字节码加载到JVM中,以便JVM可以执行这些类。类加载器是一个负责加载类的对象,用于实现类加载过程中的加载这一步。每个 Java 类都有一个引用指向加载它的 ClassLoader。数组类不是通过 ClassLoader 创建的(数组类没有对应的二进制字节流),是由 JVM 直接生成的。
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,即双亲委派模型
虚拟机栈stack
栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,每个线程都有自己的栈,它的生命周期是跟随线程的生命周期,线程结束栈内存也就释放,是线程私有 的。线程上正在执行的每个方法都各自对应一个栈帧 (Stack Frame)。
JVM对Java栈的操作只有两个,就是对栈帧的压栈和出栈,遵循"先进后出"或者"后进先出"原则。
一个线程中只能由一个正在执行的方法(当前方法),因此对应只会有一个活动的当前栈帧。
栈溢出(StackOverflowError):通常在递归调用
栈帧
栈帧是一个内存区块,是一个数据集,包含方法执行过程中的各种数据信息。
局部变量表(Local Variables)
也叫本地变量表。存储方法参数和方法体内的局部变量:8种基本类型变量、对象引用(reference)。
操作数栈(Operand Stack)
作用:也是一个栈,在方法执行过程中根据字节码指令记录当前操作的数据,将它们入栈或出栈。用于保存计算过程的中间结果,同时作为计算过程中变量的临时存储空间。
java
public class OperandStackDemo {
public static void main(String[] args) {
int i = 15;
int j = 8;
int k = i + j;
}
}
动态链接(Dynamic Linking)
作用:可以知道当前帧执行的是哪个方法。指向运行时常量池中方法的符号引用。程序真正执行时,类加载到内存中后,符号引用会换成直接引用。
方法返回地址(Return Address)
可以知道调用完当前方法后,上一层方法接着做什么,即"return"到什么位置去。存储当前方法调用完毕后下一条指令的地址
完整一个线程内存结构
java -Xss 可以设置栈大小
方法区Method Area
被所有线程共享,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等
堆heap
堆是线程共享的
堆、栈、方法区的关系

HotSpot是使用指针的方式来访问对象:
- 堆 内存用于存放对象和数组
- 堆 中会存放指向对象类型数据的地址
- 栈 中会存放指向堆中的对象的地址
堆空间
一个Java程序运行起来对应一个进程,一个进程对应一个JVM实例,一个JVM实例中有一个运行时数据区。
在 Java 虚拟机(JVM)中,堆空间是管理 Java 对象的内存区域。堆空间的主要作用是动态分配和管理 Java 对象的内存,在JVM启动的时候被创建,并且一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的。
堆空间划分
堆内存逻辑上分为三部分:
- Young Generation Space 新生代/年轻代 Young/New
- Tenured generation space 老年代/养老代 Old/Tenured
- Permanent Space/Meta Space 永久代/元空间 Permanent/Meta
新生代又划分为:
- 伊甸园区(Eden space)
- 和幸存者区(Survivor space) 。
幸存者区有两个: - S0区(Survivor 0 space)
- S1区(Survivor 1 space)
堆内存内部空间所占比例:
- 新生代与老年代的默认比例: 1:2
- 伊甸园区与幸存者区的默认比例是:8:1:1
配置堆大小
-Xms600m -Xmx600m -Xmn200m
-
Xms表示堆的起始内存,等价于-XX:InitialHeapSize,默认是物理电脑内存的1/64。
-
Xmx表示堆的最大内存,等价于-XX:MaxHeapSize,默认是物理电脑内存的1/4。
通常会将-Xms和-Xmx配置相同的值,目的是为了在Java垃圾回收机制清理完堆区后,不需要重新分隔计算堆区的大小,从而提高性能。
JDK1.8及之后堆空间

堆空间工作流程
学不明白的了解即可
垃圾回收GC
在C/C++这类没有自动垃圾回收机制的语言中,一个对象如果不再使用,需要手动释放,Java中为了简化对象的释放,引入了自动的垃圾回收(Garbage Collection简称GC)机制。通过垃圾回收器来对不再使用的对象完成自动的回收,垃圾回收器主要负责对堆上的内存进行回收。其他很多现代语言比如C#、Python、Go都拥有自己的垃圾回收器
线程不共享的部分,都是伴随着线程的创建而创建,线程的销毁而销毁。因此线程不共享的程序计数器、虚拟机栈、本地方法栈中没有垃圾回收。
方法区的垃圾回收
方法区中能回收的内容主要就是不再使用的类。判定一个类可以被卸载。需要同时满足下面三个条件:
1、此类所有实例对象没有在任何地方被引用,在堆中不存在任何该类的实例对象以及子类对象。
2、该类对应的 java.lang.Class 对象没有在任何地方被引用。
3、加载该类的类加载器没有在任何地方被引用。
堆的垃圾回收
Java堆中的对象是否能被回收,是根据对象是否被引用来决定的。如果对象被引用了,说明该对象还在使用,不允许被回收。
垃圾判断
判断堆上的对象是否被引用方法:引用计数法(已摒弃)和可达性分析法。
可达分析法
将一系列GC Root的集合作为起始点,按照从上至下的方式搜索所有能够被该合集引用到的对象(是否可达),并将其加入到该和集中,这个过程称之为标记(mark),被标记的对象是存活对象。 最终,未被探索到的对象便是死亡的,可以回收的。
垃圾回收算法
当成功区分出内存中存活对象和死亡对象后,GC接下来的任务就是执行垃圾回收。有以下算法:
标记-清除算法(Mark-Sweep)
复制算法(Copying)
标记压缩算法(Mark-Compact)
分代收集算法(Generational-Collection)
垃圾收集器
如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现
七款经典垃圾收集器:
算法和收集器了解即可