JVM专题
JVM 简介
java virtual machine java虚拟机
问题: JDK JRE JVM 三者之间的关系
问题: JVM模式
Server模式(默认)
针对服务器应用,加载速度慢,运行速度快
Client模式
针对桌面应用,加载速度快,运行速度慢
JVM 体系结构(四个部分)
类加载器-运行时数据区-执行引擎-本地接口
类加载器
概念,作用:将Java代码编译成的.class文件(字节码文件)加载到JVM内存中
分类:
- BootStrapClassloader 启动类加载器
- ExtensionClassloader 扩展类加载器
- ApplicationClassloader 应用类加载器
- 自定义类加载器
- 继承ClassLoader类,复写loadClass方法
类装载过程
- 加载
- 通过类的权限定名,查找此类的二进制字节码文件,通过该字节码文件创建Class对象
- 链接
- 验证
确保Class文件符合虚拟机规定的CLass文件格式,检查class文件的准确性 - 准备
为不含final的类的静态变量分配内存空间并设置初始化值 - 解析
将常量池的间接引用转换为直接引用的一个过程
- 验证
- 初始化
- 对静态变量和静态代码块执行初始化工作
- 使用
- 程序代码执行时的使用,new出对象程序中的使用
- 卸载
- 程序代码退出、异常、结束等,执行垃圾回收
双亲委派机制
当要加载某个类(.class文件)到内存的时候,类加载器不会自己去加载,首先会去询问上一级的类加载器(父加载器)有没有加载这个类,如果父加载器加载了,自己的类加载器就不会加载,否则再去询问上一级加载器有没有加载,直到顶层的启动类加载器都没有加载的时候,此时自己才会去加载。
一句话:即每个儿子都不愿意干活,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成。
运行时数据区
程序计数器
作用:记录程序执行到哪一条指令【通过指针】记录程序执行到的位置
特点:整个JVM虚拟机唯一一个不会产生内存溢出的地方 原因:是有硬件加载的
本地方法栈
作用:执行被native关键字修饰的方法
虚拟机栈
也称作方法栈,主要进行方法调用,方法在栈里面的表现为栈帧(Frames)
特点:线程私有,没有线程安全问题
缺点:会出现栈内存溢出
1、方法进行递归调用过深
2、方法内部操作大对象
堆
堆是Java程序中最大的一块内存区域,用于存储创建的对象实例和数组
- 堆划分成两个部分
- 新生代 1/3堆占比
- Eden 伊甸区
- Survive 幸存区
- rom 区 (S0)
to 区 (S1)
- rom 区 (S0)
- 老年代 2/3堆占比
- 新生代 1/3堆占比
方法区
方法区用于存储类的信息和运行时常量池。
它是一个理论的概念,不同的虚拟机产品实现是不一样的,比如java8所使用的Hotspot虚拟机,它基于元数 据空间(直接内存)实现了方法区。
特点:被各个线程共享,逻辑上属于堆的一个部分、随着虚拟机的创建而创建
缺点:会出现内存溢出
执行引擎-----GC 垃圾回收
如何判断对象是垃圾?
-
引用计数法
-
给对象添加一个引用计数器,每当一个地方引用它时计数器加1,引用失效计数器减1,计数器为0说明不引用,就为垃圾
- 优点 实现简单、判断效率高
- 缺点 无法解决对象互相循环引用问题
因为引用计数法无法解决对象互相循环引用问题,所以我们常用的是可达性分析算法
-
-
可达性分析算法
- 当对象到达GC Root没有引用链相连的时候,就证明这个对象是不可用的了,就为垃圾
- GC Root的种类
- 虚拟机栈中引用的对象
- 方法区中的常量引用的对象
- 方法区中的静态变量引用的对象
- 本地方法栈中native引用的对象
- GC Root的种类
- 当对象到达GC Root没有引用链相连的时候,就证明这个对象是不可用的了,就为垃圾
垃圾收集算法
- 标记-清除算法
- 先标记出存活的对象,然后再清除所有未被标记的对象。(通过可达性分析算法来判断存活的对象)
- 缺点:会产生大量不连续的内存碎片
- 先标记出存活的对象,然后再清除所有未被标记的对象。(通过可达性分析算法来判断存活的对象)
- 标记-复制算法(新生代)
- 先将堆内存一分为二,每次只使用一个区域,然后标记出存活的对象,将被标记的对象复制到另一个区域,再将之前的区域全部清除。
- 缺点:内存的使用率不高,每次只使用一个内存
- 先将堆内存一分为二,每次只使用一个区域,然后标记出存活的对象,将被标记的对象复制到另一个区域,再将之前的区域全部清除。
- 标记-整理(压缩)算法(老年代)
- 先标记所有存活的对象,将所有存活的对象移动到内存的一边,然后清除掉边界以外的其他内存区域
垃圾回收器
- 新生代
- Serial
- 回收算法:标记-复制算法
- 简单高效的单核机器,Client 模式下默认新生代收集器;
- Parallel ParNew
- 回收算法:标记-复制算法
- GC 线程并行版本,在单 CPU 场景效果不突出。常用于 Client 模式下的 JVM
- Parallel Scavenge
- 回收算法:标记-复制算法
- 目标在于达到可控吞吐量(吞吐量=用户代码运行时间/(用户代码运行时间+垃圾回收时间))
- Serial
- 老年代
- Serial Old
- 算法:标记-压缩算法
- 性能一般,单线程版本。1.5 之前与 Parallel Scavenge 配合使用;作为 CMS 的后备预案
- Parallel Old
- 算法:标记-压缩算法
- GC 多线程并行,为了替代 Serial Old 与 Parallel Scavenge 配合使用
- CMS
- 算法:标记-清除算法
- 对 CPU 资源敏感、停顿时间长。标记-清除算法会产生内存碎片,可以通过参数开启碎片的合并整理。 基本已被 G1 取代
- Serial Old
- G1(重点)
- 特点:将整个Java堆分为多个大小相等的独立区域 ,跟踪各个区域的垃圾堆积的价值大小, 然后在后台维护一个优先队列,每次根据允许的收集时间,优先回收价值最大的区域
- 判断垃圾算法:可达性分析算法
- 判断回收算法:标记-整理算法
- 步骤:
- 初始标记:G1会标记出GC Roots 直接关联的对象
- 并发标记:对堆中对象进行可达性分析,找出存活对象,耗时长,与用户进程并发工作
- 最终标记:修正并发标记期间用户进行继续运行所产生变化的标记
- 筛选标记:对各个Region 的回收价值排序,然后根据期望的GC停顿时间指定回收计划
- 场景: 适用于多核大内存机器、GC 多线程并行执行,低停顿、高回收效率;
- G1垃圾回收器适用于需要处理大堆内存、具有高并发需求、对延迟要求较低以及对分代回收支持的场景。它在这些场景下可以提供更好的性能和更低的停顿时间,帮助优化应用程序的运行效率。
垃圾回收过程
- 大多数情况下对象在伊甸区分配,当伊甸区没有足够的空间时将发起一次MinorGC 当伊甸区执行MinorGC 后还不足以为对象分配空间时。大对象直接进入老年代,可以用参数设置大对象直接进入老年代,避免频繁MinorGC
- 如果对象在伊甸区出生,发生MinorGC 后依然存活,且能被Survive(幸存区)容纳,年龄+1 ,达到一定年龄进入老年代,默认是15
- 占 Survive 区空间一半以上而且年龄相等的对象,大于等于该年龄以上的对象直接进入老年代
- 发生MinorGC 之前先检查老年代最大可用连续空间是否大于新生代所有对象空间,如果大于说明MinorGC 安全,否则会判断是否允许担保失败,如果允许担保失败,判断老年代最大连续空间是否大于历次晋升到老年代的平均大小 ,如果大于则执行MinorGC 否则FullGC
- Minor GC
- 触发条件:
- 伊甸区 满了
- 存入对象超过伊甸区剩余空间
- 触发条件:
- Major GC
- 触发条件
- 老年代空间不足时,会先尝试触发Minor GC。Minor GC之后空间还不足,则会触发Major GC。
- 触发条件
- Full GC
- 触发条件
- 显式调用System.gc()方法时法可以请求垃圾回收器执行一次Full GC,可通过-XX:+ DisableExplicitGC 参数来禁止调用System.gc()
- 当方法区空间不足时
- 当Minor GC后存活的对象大小超过了老年代剩余空间
- Minor GC时中Survivor幸存区空间不足时,会判断是否允许担保失败,不允许则触发Full GC。运行则会尝试进行一次Minor GC,并将存活对象直接晋升到老年代中。
- 如果每次晋升到老年代的对象平均大小>老年代最大可用连续内存空间时,也会触发Full GC
- 触发条件
本地接口
本地接口(Native Interface)是一种使Java代码能够调用本地(或原生)方法的机制。本地接口允许Java程序与底层操作系统、硬件设备或其他编程语言进行交互。本地接口常常是通过C或C++编写的,这里就不概述了。