1.Jvm是什么
1.png全称是Java Virtual Machine, 是运行在物理计算机上的运行Java代码的虚拟计算机;
它主要由两个子系统, 两个组件组成;
1.1 两个子系统
- 类加载子系统(class loader): 将java全限定名称的class文件加载到Jvm
- 执行引擎(execution engine): 执行class中的java指令, 收集jvm运行时垃圾对象
类加载器
java文件被编译后生成的class文件中会包含两个方法<cinit>, <init>
<cinit>是jvm加载class生成Class对象调用方法
<init>是创建对象时调用的方法
加载class文件
对class文件做连接
初始化class: 对该类的静态变量赋正确的初始值
使用class
卸载class
第2个做连接的阶段可以分为3个步骤
- 检查阶段: 主要做java魔术, 版本, 语义, 符号量等的检查
- 准备阶段: 为类中静态变量分配内存空间, 并赋默认值
- 解析阶段: 字符引用解析为直接引用
类卸载阶段
- 该类的所有对象全部被回收
- 该类的classloader被回收
- 该类的class对象没有被引用
class被加载的时机
- new一个class的对象
- 调用类的静态变量和静态方法
- 反射一个类
- 主动加载一个类
class.forName("xxx.class")
- 初始化一个子类
- main启动类函数
1.2 两个组件
- Jvm运行时数据区(runtime data area): jvm内存
- 本地方法接口(native interface): 调用本地方法的交互接口
jvm运行时数据内存
QQ截图20200509173935.png根据《Java 虚拟机规范(Java SE 7 版)》规定, Java 虚拟机所管理的内存如上图所示
分为方法区, 堆, 虚拟机栈, 程序计算器, 本地方法栈
方法区: 存储类加载后生成的类信息, 运行时常量池和及时编译后的代码
虚拟机栈: 存储线程运行时的方法数据, jvm为每个运行的线程分配一个栈内存, 线程中每执行一个方法会向栈中压入一个栈帧, 栈帧信息包括局部变量, 操作数栈, 动态连接和方法返回
本地方法栈: 存储jvm调用本地方法运行时数据
堆: 存储java运行时生成的对象
程序计算器: 存储线程当前运行到位置信息, 为每个线程分配一个计算器
常量池
/* java中的常量池可以分为静态常量池, 运行时常量池和String常量池 */
静态常量池: java编译成class文件后class中的一段数据代码, 该区域主要存储字面量和符号引用
运行时常量池: class被加载到jvm后, 存储静态常量池的内存
String常量池: String在java中被定为为final的, 为了加速String的数据访问, jvm在方法区开辟出一块内存区域用户存放运行时产生的String数据, 类似缓存的概念; 池中的String对象会被一个表维护着引用, 保持在GC时不会被回收
基本数据类型的缓存池: ByteCache, CharacterCache, ShortCache, IntegerCache, LongCache缓存1KB([-128, 127])的数据
方法区
方法区是虚拟机规范中的一块内存区域, 该区域主要是存放jvm加载的类的信息, 每个jvm对方法区的实现都有差异
方法区存放类的数据结构, String常量池和运行时编译后的代码, 类的数据结构主要为类全限定名, 符号量和字面量的常量池, 静态变量, 方法描述
// java8将永久代改为元空间主要做以下的考虑
String常量池可能引起OOM
永久代的GC效率低, 浪费较多时间
永久代内存需在jvm启动之前定义, 不能随着物理服务的内存做动态调整
// java8之后的方法区的调整
类元信息被存放到了metaspapce
常量池和静态变量被存放到了heap中(常量池和静态变量都是对象需要被GC)
方法区 | 详细信息 | 源码中的示例 |
---|---|---|
类的信息 | 类的全限定名, 父类的全限定名, 类的类型标识(接口, 类, 注解), 类的访问限制描述(public, protect, private, default) | abstract class com.inus.Test extends com.inus.TestSuper minor version: 0 major version: 52 flags: ACC_SUPER, ACC_ABSTRACT |
常量池 | 类的所有常量的有序集合, 符号量(类型, 字段, 方法的描述符)和字面量(基本数据类型和String的常量), 栈帧中的动态链接主要就是对这部分内存的引用 | Constant pool: #1 = Class #2 // com/inus/Test #2 = Utf8 com/inus/Test #3 = Class #4 // com/inus/TestSuper #4 = Utf8 com/inus/TestSuper |
字段信息 | 字段全限定名, 字段类型, 访问修饰符 | public static final com.inus.TestSuper CONST; descriptor: Lcom/inus/TestSuper; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL |
方法信息 | 包括够着方法, 静态块和类中定义的方法方法<br />全限定名, 方法修饰符, 方法参数, 方法执行指令 | public com.inus.Test(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #18 // Method com/inus/TestSuper."<init>":()V 4: return LineNumberTable: line 21: 0 line 24: 4 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/inus/Test; |
类加载器的引用 | 当类加载后, 方法区中会保留一份对该类加载器的引用 | |
Class对象的引用 | 类加载完成后jvm会创建一个Class的对象, 方法区会保留一份对该类对象的引用 this.getClass().getName() 就是通过这个引用获取Class的信息的 |
// 方法区的实现: metaspace
jdk8为每个classloader分配一个本地内存区域, 这些内存区域合起来就是metaspace
确保类和类元数据和加载该类的classloader的生命周期是保持一致的
2.Jvm GC
Jvm的基本算法
3.png
- 标记-清除: 先标记heap中无用类, 然后执行回收操作, heap内存中会出现大量内存碎片
- 复制: 开辟两块内存区域, 每次使用一块, 来回复制存活的对象, 存在内存浪费(新生代算法)
- 标记-整理: 先标记heap无用对象, 然后将存活对象向内存一端整理, 该算法存在对象移动(老年代算法)
- 分代收集: 将heap内存分为几个特征比较明显的区域, 对不同特征区域的heap进行不同的回收算法
年轻代收集器
- Serial: 串行化垃圾回收器, 复制算法
- ParNew: Serial的多线程版本, 也是复制算法
- Parallel Scavenge: 优化后的ParNew收集器, 优先吞吐量
老年代垃圾收集器
- Serial Old: Serial的老年代版本, 标记整理算法
- Parallel Old: Parallel Scavenge的老年代算法, 也是标记整理算法
- CMS(Concurrent Mark Sweep): 并行标记整理算法
G1垃圾回收器
标记整理算法, 针对jvm整个堆做GC;
将heap划分为n个指定的大小的region, eden年轻代, Survivor区和老年代各占n个region
网友评论