一、JVM类加载机制
1、类加载过程
当我们运行某个类时,首先需要通过类加载器把主类加载到JVM
加载类过程
加载、验证、准备、解析、初始化、使用、卸载
加载: 在磁盘上查找并通过IO读入字节码文件,使用到类时才会加载
验证: 验证字节码文件的正确性
准备: 给静态变量分配内存,并赋予默认值
解析: 将符号引用替换成直接引用
静态链接:当一个字节码文件被装载进 JVM 内部时,如果被调用的目标方法在编译期可知,且运行期保持不变时,这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接。
动态链接:如果被调用的方法在编译期无法被确定下来,也就是说,只能够在程序运行期将调用的方法的符号转换为直接引用,由于这种引用转换过程具备动态性,因此也被称之为动态链接。
初始化: 给静态变量赋值,并执行静态代码块
使用:
卸载:
注意:主类在运行过程中如果使用到其它类,会逐步加载这些类
public class TestLoad {
static {
System.out.println("======load TestLoad======");
}
public static void main(String[] args) {
new A();
System.out.println("======A已执行完======");
//B不会加载,除非这里执行 new B()
B b = null;
}
}
class A {
static {
System.out.println("======load A======");
}
public A() {
System.out.println("======init A======");
}
}
class B {
static {
System.out.println("======load B======");
}
public B() {
System.out.println("======init B======");
}
}
输出:
======load TestLoad======
======load A======
======init A======
======A已执行完======
从上面的代码运行结果可以看出来,我们只用用到了 class TestLoad 和 class A, 所以只有这个两个类被加载。而即使我们声明了变量 B b = null 但是我们没有 new B()实例,所以B类没有被使用,因此也不会被加载。
2、类加载器
-
引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等
-
扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包
-
应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类
-
自定义加载器:负责加载用户自定义路径下的类包
类加载器示例
public class TestClassLoader {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
System.out.println(TestClassLoader.class.getClassLoader().getClass().getName());
System.out.println();
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
ClassLoader extClassloader = appClassLoader.getParent();
ClassLoader bootstrapLoader = extClassloader.getParent();
System.out.println("the bootstrapLoader : " + bootstrapLoader);
System.out.println("the extClassloader : " + extClassloader);
System.out.println("the appClassLoader : " + appClassLoader);
System.out.println();
System.out.println("bootstrapLoader加载以下文件:");
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i]);
}
System.out.println();
System.out.println("extClassloader加载以下文件:");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println();
System.out.println("appClassLoader加载以下文件:");
System.out.println(System.getProperty("java.class.path"));
}
}
输出
null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
the bootstrapLoader : null
the extClassloader : sun.misc.Launcher$ExtClassLoader@29453f44
the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2
bootstrapLoader加载以下文件:
file:/D:/WorkSoft/JDK1.8/jre/lib/resources.jar
file:/D:/WorkSoft/JDK1.8/jre/lib/rt.jar
file:/D:/WorkSoft/JDK1.8/jre/lib/sunrsasign.jar
file:/D:/WorkSoft/JDK1.8/jre/lib/jsse.jar
file:/D:/WorkSoft/JDK1.8/jre/lib/jce.jar
file:/D:/WorkSoft/JDK1.8/jre/lib/charsets.jar
file:/D:/WorkSoft/JDK1.8/jre/lib/jfr.jar
file:/D:/WorkSoft/JDK1.8/jre/classes
extClassloader加载以下文件:
D:\WorkSoft\JDK1.8\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
appClassLoader加载以下文件:
D:\WorkSoft\JDK1.8\jre\lib\charsets.jar;D:\WorkSoft\JDK1.8\jre\lib\deploy.jar;D:\WorkSoft\JDK1.8\jre\lib\ext\access-bridge-64.jar;D:\WorkSoft\JDK1.8\jre\lib\ext\cldrdata.jar;D:\WorkSoft\JDK1.8\jre\lib\ext\dnsns.jar;D:\WorkSoft\JDK1.8\jre\lib\ext\jaccess.jar;D:\WorkSoft\JDK1.8\jre\lib\ext\jfxrt.jar;D:\WorkSoft\JDK1.8\jre\lib\ext\localedata.jar;D:\WorkSoft\JDK1.8\jre\lib\ext\nashorn.jar;D:\WorkSoft\JDK1.8\jre\lib\ext\sunec.jar;D:\WorkSoft\JDK1.8\jre\lib\ext\sunjce_provider.jar;D:\WorkSoft\JDK1.8\jre\lib\ext\sunmscapi.jar;D:\WorkSoft\JDK1.8\jre\lib\ext\sunpkcs11.jar;D:\WorkSoft\JDK1.8\jre\lib\ext\zipfs.jar;D:\WorkSoft\JDK1.8\jre\lib\javaws.jar;D:\WorkSoft\JDK1.8\jre\lib\jce.jar;D:\WorkSoft\JDK1.8\jre\lib\jfr.jar;D:\WorkSoft\JDK1.8\jre\lib\jfxswt.jar;D:\WorkSoft\JDK1.8\jre\lib\jsse.jar;D:\WorkSoft\JDK1.8\jre\lib\management-agent.jar;D:\WorkSoft\JDK1.8\jre\lib\plugin.jar;D:\WorkSoft\JDK1.8\jre\lib\resources.jar;D:\WorkSoft\JDK1.8\jre\lib\rt.jar;E:\projects\jvm\target\classes;D:\WorkSoft\Idea\IntelliJ IDEA 2020.3\lib\idea_rt.jar
3、双亲委派机制
4、打破双亲委派机制
二、JVM内存模型
堆: 主要用于存放对象和数组,是线程共享的。堆还可以分为新生代(YoungGeneration)和老年代(OldGeneration),新生代还可以分为 Eden、From Survivor、To Survivor
线程栈: 每个线程包含一个栈区,栈区中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法。每个栈帧中含有局部变量、操作数栈、动态链接、方法出口。当一个线程执行一个方法时,就会创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完之后,便会将栈帧出栈
操作数栈:在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)和 出栈(pop);某些字节码指令将值压入操作数栈,其余的字节码指令将操作数取出栈,使用它们后再把结果压入栈,比如:执行复制、交换、求和等操作;
操作数栈,主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间
通过标准的入栈和出栈操作来完成一次数据访问
方法出口:例如调用方法执行完,返回主方法,保存该调用方法的执行位置等
方法区: 存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。是线程共享的。
程序计数器: 程序计数器是当前线程正在执行的字节码的地址。程序计数器是线程隔离的,每一个线程在工作的时候都有一个独立的计数器。
本地方法栈: 本地方法栈执行的是本地方法(Native Method)
三、JVM对象创建与内存分配机制
1、对象的创建
四、脑图地址
https://www.processon.com/view/link/633af20507912921d8fd68e5