导图社区 第4章 走进JVM
JVM思维导图,介绍了字节码、类加载过程、内存布局、垃圾回收、对象实例化这几个方面的内容,带领我们走进JVM。
编辑于2021-08-02 10:30:42走进JVM
字节码:一个字节(8位)可以存储256不同的指令信息,这样的字节即字节码
主要指令
加载或存储指令
运算指令
类型转换指令
对象创建与访问指令
操作栈管理指令
方法调用与返回指令
同步指令
源文件转字节码过程
.java————词法解析————语法解析————语义分析————生成字节码————字节码
字节码必须通过类加载过程加载到JVM环境后,才能执行。
执行的三种模式
解释执行
JIT编译执行
JIT编译执行与解释混合执行(主流JVM默认执行模式) 优势:解释器在启动时先解释执行,省去编译时间。 JIT作用:将Java字节码动态地编译成可以直接发送给处理器指令执行的机器码。
类加载过程:
是一个将.class字节码文件实例化成Class对象并进行相关初始化的过程。JVM会初始化继承树上还没有被初始化过的所有父类,并会执行这链路上所有未执行的静态代码块、静态变量赋值语句。
Load 加载:读取类文件产生二进制流,并转化为特定的数据结构,进行初步的校验(常量池、文件长度、是否有父类),然后创建对应类的实例
Link 链接
验证:更详细的验证,例类型是否正确、静态变量是否合理
准备;为静态变量分配内存,并设定默认值
解析:解析类和方法确保类之间的相互引用正确性,完成内存结构布局
Init 初始化:执行类构造器方法<clinit>,
new 和newInstance区别:
new 是强类型校验,可以调用任何构造方法,在使用new操作时,这个类可以没有加载过;
Class类下的newInstance是弱类型,只能调用无参构造方法,如果没有默认方法,就抛出InstantiationException(实例化)异常,如果此构造方法没有权限访问,则抛出IllegalAccessException 异常。
类加载器
Bootstrap ClassLoader
Platform ClassLoader (JDK9之前的加载器Extension ClassLoader) 平台类加载器,
Application ClassLoader 应用类加载器,加载用户定义的CLASSPATH路径下的类
自定义类加载器
实现自定义类加载器的步骤:继承 ClassLoader,重写 findClass()方法,调用defineClass()方法
自定义类加载器应用场景:
隔离加载类
修改类加载方式
扩展加载源
防止源码泄露
内存布局
Heap(堆区)
存储着几乎所有的实例对象,堆由垃圾收集器自动回收,堆区由各子线程共享使用。 JVM运行时参数:-Xms1024M -Xmx1024;分别代表最小堆容量和最大对容量
新生代
Eden
当Eden 区装填满的时候 会触发 Young Garbage Collection,即 YGC
-XX:MaxTenuringThreshold 参数能配置计数器的值到达某个阐值的时候 对象从新生代 晋升至老年代
-XX:+HeapDumpOnOutOfMemoryError ,让JVM 遇到 OOM 异常时能输出堆内信息,特别是对相隔数月才出现的 OOM 异常尤为重要。
Servivor0 和 Servivor1
每次YGC时候,它们将存活的对象复制到未使用的那块空间,然后将当前正在使用的空间完全清除,交换两块空间的使用状态
老年代 :
接纳新生代无法容纳的超大对象
对象存活超过阈值,晋升至老年代
-Xmx:最大堆大小 -Xms:初始堆大小 -Xmn: 年轻代大小 -XXSurvivorRatio:年轻代中Eden区与Survivor区的大小比值
Metespace(元空间)
JDK8使用元空间替换永久代(Perm),
元空间在本地内存中分配,在 JDK8 里, Perm 区中的所有内容中字符串常量移至堆内存,其他内容包括类元信息、字段、静态属性、方法、常量等都移动至元空间内
JVM Stack(虚拟机栈)
虚拟机通过压栈和出栈的方式,对每个方法对应的活动栈帧进行运算处理
栈(stack)是一个先进后出的数据结构
每个方法从开始调用到执行完成的过程,就是栈帧从入栈到出栈的过程
当前栈帧:只有位于栈顶的栈帧才是有效的,所有指令都只能针对当前栈帧进行操作
StackOverflowError表示请求的栈溢出,导致内存耗尽,通常出现在递归方法中
栈帧包括局部变量表、操作栈、动态链接、方法返回地址
局部变量表
是存放方法参数和局部变量的区域
操作栈
是一个初始状态为空的桶式结构栈。
在方法执行过程中,会有各种指令往栈中写入和提取信息
JVM的执行引擎是基于栈(操作栈)的执行引擎
动态链接
每个栈帧中包含一个在常量池中对当前方法的引用 ,目的是支持方法调用过程的动态连接。
方法返回地址
正常退出:即正常执行到任何方法的返回字节码指令,如RETURN、IRETURN、ARETURN
异常退出:将返回至方法当前被调用前的位置,方法退出的过程相当于弹出当前栈帧
退出方式
返回值压入上层调用栈帧
异常信息抛给能够处理的栈帧
PC计数器指向方法调用后的下一条指令
Native Method Stacks (本地方法栈)
本地方法可以通过JNI(Java Native Interface)来访问虚拟机运行时的数据区
JNI类本地方法,System.currentTimeMills()
Program Counter Register (程序计数寄存器)
对象实例化
从字节码角度
NEW:类加载成功后,则在堆中分配内存,从Object到本类路径上的所有属性都要进行分配内存。之后进行零值初始化,指令执行完毕后,将指向实例对象的引用变量压入虚拟机栈顶
DUP:在栈顶复制该引用变量,这时会出现两个指向堆内实例对象的引用变量,其中压至底下的引用用于赋值,或保存到局部变量表,另一个栈顶的引用变量作为句柄调用相关方法
INVOKESPECIAL:调用对象实例方法,通过栈顶的引用变量调用<init>方法。<clinit>是类初始化时执行的方法,<init>是对象初始化时执行的方法
从执行步骤
确定类元信息
分配对象内存
设定默认值
设定对象头
执行init方法
垃圾回收
清除不再使用的对象,自动释放内存
GC Roots:通过一个对象与GC Roots之间是否存在直接或间接的引用关系,来判断对象是否存活。比如某个失去任何引用的对象,或者两个相互环岛环引用的对象等,判决这些对象“死缓”,是可以被回收的。
哪些对象可以作为GC Roots?
类静态属性中引用的对象
常量引用的对象
虚拟机栈中引用的对象
本地方法栈中引用的对象
垃圾回收器 (Garbage Collector)
是实现垃圾回收算法并应用在JVM环境中的内存管理模块
相关算法
标记-清除算法:从每个GC Roots 出发,一次标记有引用关系的对象,最后将没有被标记的对象清除。缺点---会带来大量得到空间碎片,导致需要分配一个较大连续空间时容易触发FGC。
1.YGC和FGC是什么 YGC :对新生代堆进行gc。频率比较高,因为大部分对象的存活寿命较短,在新生代里被回收。性能耗费较小。 FGC :全堆范围的gc。默认堆空间使用到达80%(可调整)的时候会触发fgc。以我们生产环境为例,一般比较少会触发fgc,有时10天或一周左右会有一次。 2.什么时候执行YGC和FGC a.edn空间不足,执行 young gc b.old空间不足,perm空间不足,调用方法System.gc() ,ygc时的悲观策略, dump live的内存信息时(jmap –dump:live),都会执行full gc
标记-整理算法:从GC Roots 出发标记存活的对象,然后将存活对象整理到内存空间的一段,形成连续的已使用空间,最后把已使用空间之外的部分全部清理掉,这样旧不会产生空间碎片的问题
Serial 回收器 :主要应用于YGC的垃圾回收器,采用串行单线程的方式完成GC任务。
“Stop The World”简称 STW,即垃圾回收的某个阶段会暂停整个应用程序的执行。 FGC 的时间相对较长,频繁触发会严重影响应用程序的性能。
CMS 回收器:回收停顿时间比较短、目前比较常用的垃圾回收器。通过四个步骤完成垃圾回收工作。
CMS 可以通过配置 XX :+UseCMSCompactAtFullCollection 参数,强制 JVM在 FGC 完成后对老年代进行压缩 执行一次空间碎片整理但是空间碎片整理阶段也会引发 STW 。为了减少 TW 次数, CMS 还可以通过配置- XX +CMSFullGCsBeforeCompaction=n 参数,在执行了n 次 FGC 后, JVM 再在老年代执行空间碎片整理。
初始标记(Initial Mark)
并发标记(Concurrent Mark)
重新标记(Remark)
并发清除(Concurrent Sweep)
GI 回收器:(JDK7推出,11中默认)和CMS相比,具备压缩功能,能避免碎片问题。将Java 堆空间分割成勒若干相同大小的区域。即region
Eden
Survivor
Old
Humongous(特殊的Old类型,专门放置大型对象)