1. JVM 概念
- JVM(Java Virtual Machine)的简称,java虚拟机。
- 虚拟机:指通过软件模拟的具有完整硬件功能、运行在一个完全隔离的环境中的完整计算机系统。
- JVM:被裁剪过的虚拟机,执行字节码指令集的软件。
- Java代码——Java进程——jvm之间的关系
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/4f57ed42ff897b1a70a9ac2b01031645.png)
- 类加载:
如果一个类没有发生类加载,要限制性类加载。
类加载的时机:
1. new关键字实例化对象的时候;
2. 读,写静态变量
3. 调用静态方法的时候;
4. 父类的类加载:子类要初始化类加载,如果子类还没有类加载,则先执行父类的类加载。
5. 主函数的类:java类启动入口类
加载做的事情:
- 把字节码(二进制数据)加载到java进程的方法区(类型的信息,方法的信息等等)
- 在堆中,生成一个Class类对象。作为方法区类信息(代码数据)访问的入口。
- 初始化VM的参数
- 创建一个java虚拟机,并启动
- 入口类类加载,java虚拟机执行main方法
- 启动一些守护线程。(GC线程)等等
- java 进程——线程——申请系统调度cpu执行指令
- 启动线程:申请系统调度CPU执行线程中的代码执行指令。
- Java虚拟机:包含解释器,JIT即时编译器
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/38ec7724b1ff558c5a5cfa246049aa04.png)
关于JVM
- 定制过的裁剪了部分功能的虚拟机,模拟硬件执行(将字节码翻译成机器码)字节码指令;
- Java进程启动时,就会创建一个Java虚拟机,解释执行字节码指令。最终是申请系统调度CPU执行机器码。
- 不同虚拟机实现,可以运行符合字节码规范的代码(kotlin,scala,groovy),基于自己独有的编译器,编译为符合字节码文件,使用符合Java虚拟机规范,自己定制的java虚拟机来运行。(解释翻译为机器码)
2.Java内存区域
2.1 运行时数据区域
- 线程私有区域:程序计数器,Java虚拟机栈,本地方法栈
- 线程共享区域:Java堆,方法区,运行时常量池。
- 内存溢出:创建变量/对象/类加载,需要先在对应的内存区域,分配一块内存空间,如果该区域内存不足,需要执行gc(垃圾回收),如果gc以后,内存还不够用,就会出现内存溢出(OOM)
- 内存溢出导致的结果:严重的情况下,整个java进程都会挂掉;
- 内存溢出的解决方案:
- 优化代码,(空间复杂度):复用变量等等。
- Java进程启动时,加大对应内存区域的空间。
- 如果系统内存不足满足第2个条件,还可以加大系统内存。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/b66a225c90c69bd705173e8a57663dfe.png)
2.2 程序计数器(线程私有)
- 程序计数器是一块比较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。(目的:线程切换出去后要恢复时下个指令从哪个地方开始执行)
- 程序计数器内存区域是唯一一个在JVM规范中没有规定任何OOM的区域。
- 线程私有:由于JVM的多线程是通过线程轮流切换并分配处理器执行时间来实现,因此在任何一个确定的时刻,一个处理器(多核处理器则指的是一个内核)都只会执行一条线程中的指令,因此为了切换线程后能恢复到正确的执行位置,每条线程都需要独立的程序计数器,各条线程之间计数器互补影响,独立存储,我们把类似这类区域称之为“线程私有”的内存。
2.3 Java虚拟机栈(线程私有)
- 虚拟机栈描述的是Java方法执行的内存模型:每个方法执行的同时都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从调用开始直至完成的过程,就对应一个战帧在虚拟机中入栈和出栈的过程,Java虚拟机栈的生命周期和线程相同。(线程启动,虚拟机栈就会创建,线程销毁,虚拟机栈也销毁)。
- 局部变量表:存放了编译器可知的各种基本数据类型(8大基本数据类型),对象引用。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在执行期间不会改变局部变量表的大小。
class Person{
String name;
public Person(String name){
this.name = name;
}
}
public class VMStackTest {
public static void main(String[] args) {
int m = 1;
int n = 2;
swap1(m,n);
System.out.printf("main: m=%s,n=%s%n",m,n);
}
private static void swap1(int m, int n) {
int tmp = m;
m = n;
n = tmp;
Person person1 = new Person("p1");
Person person2 = new Person("p2");
swap2(person1,person2);
System.out.printf("swap1 :p1=%s,p2=%s%n",person1.name, person2.name);
}
private static void swap2(Person p1, Person p2) {
Person tmp = p1;
p1 = p2;
p2 = tmp;
System.out.printf("swap2:p1= %s, p2= %s %n",p1.name,p2.name);
}
}
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/b50d8382ff928db6503cd93789a7ca9b.png)
- 基础数据类型是传值,引用的传引用地址的值。
- 虚拟机栈不需要垃圾回收,出栈自动销毁栈帧,所以不需要栈帧。
- 此区域一般会产生下列两种异常:
- 如果线程请求的栈的深度大于虚拟机所允许的深度,将会抛出StackOverFlowError异常。(线程调用的方法链太深)
- OOM
2.4 本地方法栈(线程私有)
- 本地方法栈与虚拟机栈的作用完全一样,他俩的区别无非是本地方法栈为虚拟机使用的Native方法服务,而虚拟机栈为JVM执行的java方法服务。基于JNI的技术,调用native本地方法(其他语言提供符合JNI规范的接口)。
2.5 堆(线程共享)
- Java堆(Java Heap)是JVM所管理的最大内存区域,Java堆是所有线程共享的一块区域,在JVM启动时创建,此内存区域存放的都是对象实例。JVM规范中说到:“所有的对象实例以及数组都要在堆上分配。”线程共享的区域,都是Java进程启动时就创建的。
- Java堆是垃圾回收管理的主要区域,因此很多时候可以称之为"GC堆",根据JVM规范规定的内容,Java堆可以处于物理上不连续的内存空间。
2.6 方法区(线程共享)
- 方法区用于存储已被虚拟机加载的类信息,常量(static final修饰的常量)、静态变量(等号左边的变量本身,1.7及以后,是存放在堆里面的),即时编译器编译后的代码等数据。
- 存在gc:回收常量和类型(卸载)类型卸载的条件是非常苛刻d额,所以方法区的gc频率比较低。
2.7 运行时常量池
- 运行时常量池是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于编译期生成的各种字面量与符号引用,这部分内容在类加载后存放到方法区的运行池中。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/d1965f3a3087204752c819607ea99a23.png)
- 其他常量池补充:
- class文件常量池,编译为class字节码文件中,存在class文件常量池,用于存放字面量和符号引用。
- 字符串常量池