JVM包含哪几个部分?
- 类加载子系统
- 运行时数据区(程序计数器、虚拟机栈、本地方法栈、堆、方法区)
- 执行引擎
类加载子系统
类的加载过程
加载 -> 连接(验证、准备、解析) -> 初始化
- 加载:{
2、在堆中生成一个代表该类的class对象,作为方法区这个类的各种数据的入口;
}
- 验证:保证class文件的字节流中包含的信息是否符合当前虚拟机的要求,包含:文件格式验证、元数据验证、字节码验证、符号引用验证;
- 准备:{
2、增添final之后会将其赋值为所给的数(完成了类构造器<clinit>方法之中);
}
- 解析:将常量池内的符号引用替换为直接引用的过程。主要针对(接口、字段、类方法、接口方法、方法类型、方法句柄、调用限定符);
- 初始化:执行类构造器<clinit>()方法;
类加载器
四种类加载器:
类加载器之间的父子关系一般不会以继承的关系来实现,而是使用组合关系来复用父加载器的代码。
- Bootstrap ClassLoader 启动类加载器;
- Extention ClassLoader 标准拓展类加载器;
- Application ClassLoader 应用类加载器;
- User ClassLoader 用户自定义类加载器。
双亲委派加载机制:
- 保证了java程序的稳定运行,可以避免类的重复加载(JVM区分不同类的方式不仅仅根据类型,相同的类文件被不同的类加载器加载产生的是两个不同的类);
- 保证了java的核心API不被篡改。
破坏双亲委派加载机制:
- SPI机制:获取 META/INF 路径下的所有实现类,这些类通过第三方提供的,根据双亲委派原则,由JDK内部加载的 Class 默认应该归属于 BootStrap 类加载器;但是原生SPI机制通过ServiceLoader.load方法由外部指定类加载器,或者默认使用ThreadContextClassLoader()线程上下文加载器,从而避免了Class 被载入BootStrap加载器,导致破坏了双亲委派加载机制;
- Tomcat容器:会优先加载Web应用自己定义的类,通过WebAPPClassLoader加载本身目录下的class文件,加载不到再给CommonClassLoader加载。为了实现不同应用依赖不同版本使用同一个类名的问题,需要加载多个相同的、不同版本的类;
- 参考:https://github.com/HSshuo/SPI
Java内存区域/运行时数据区
大体分为:堆、栈、本地方法栈、方法区、程序计数器
- 线程私有:程序计数器、虚拟机栈、本地方法栈;
- 线程共享:堆、方法区(元空间)
介绍:
- 程序计数器:是一块内存区域、用来记录线程当前要执行的指令地址。确保线程切换后能恢复到正确的执行位置;
- 虚拟机栈:包含局部变量表(基本数据类型+对象的引用)、方法出口信息、操作数栈、动态连接;为虚拟机执行java方法(字节码)服务;
- 本地方法栈:使用本地方法会在本地方法栈也创建栈帧;为虚拟机使用到的native方法服务;
- 堆:
- 字符串常量池(原本在方法区的运行时常量池中);
- 存放对象实例;并不是绝对的,通过逃逸分析,如果某些方法中的对象引用没有被返回或者被外面使用,对象可以直接在栈上分配内存;
- Java堆是垃圾收集器管理的主要区域;(引申垃圾回收算法、常见的垃圾回收器、参考链接:https://blog.nowcoder.net/n/a16d0a274890498891aee25aea4ba3a7)
- 方法区:运行时常量池,用于存储以被虚拟机加载的类信息(类的版本、字段、方法、接口等信息)、静态变量等数据;
- 方法区也被称为永久代,是HotSpot虚拟机对虚拟机规范中方法区的一种实现方式;
- 元空间(替代永久代):使用直接内存;通过NIO类使用native函数库直接分配堆外内存,通过存储在java堆中的DirectByteBuffer对象对这个内存进行引用操作;
元空间、永久代可以被回收嘛:可以回收
- 当该类的实例都被回收
- 加载该类的ClassLoader已经被回收
- 该类不能通过反射访问到其他方法、同时该类没有被引用