JVM
工作流程
运行时数据区
- 程序计数器
- 通过改变计数器的值来选取下一条需要执行的字节码指令
- 在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令
- 多线程是通过线程轮流切换并分配处理器执行时间的方式实现的
- 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址
- 如果线程正在执行的是一个Native方法,这个计数器值则为空
- 唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域
- 虚拟机栈
- 描述的是Java方法执行的内存模型
- 每个方法在执行的同时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口灯
- 每个方法从调用到执行完成的过程,对应着一个栈帧在虚拟机栈中入栈到出栈的过程
- 局部变量表存放基本数据类型、对象引用
- 局部变量表所需的内存空间在编译期间完成分配,方法运行期间不会改变
- 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常
- 如果虚拟机栈可以动态扩展,但扩展时无法申请到足够内存,将抛出OutOfMemoryError异常
- 本地方法栈
- 与虚拟机栈功能类似,区别于本地方法栈是为Native方法服务,而虚拟机栈是为Java方法服务
- 堆
- Java虚拟机所管理的内存中最大的一块
- 主要作用是分配对象内存,存放对象实例
- 堆是垃圾收集器管理的主要区域
- 方法区
- 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
- 运行时常量池
- 用于存放编译器生成的各种字面量和符号引用
- 字面量:相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等
- 符号引用:属于编译原理方面的概念,包括三种类型的变量
- 类和接口的全限定名
- 字段名称和描述符
- 方法名称和描述符
- 用于存放编译器生成的各种字面量和符号引用
类加载
- 类加载过程
- 加载:通过类的完全限定查找此类字节码文件并创建一个Class对象
- 验证:确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全,主要包含
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
- 准备:为静态变量分配内存并设置初始默认值,不包含final修饰的静态变量,因为final修饰的静态变量在编译阶段已经分配
- 解析:将常量池中的符号引用替换成直接引用的过程,主要包含
- 类或接口的解析
- 字段解析
- 类方法解析
- 接口方法解析
- 初始化:若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量
- 类加载器
- BootstrapClassLoader:初始类加载器,是所有类加载器的父类加载器,主要负责加载JDK类文件
- ExtClassLoader:扩展类加载器,从jre/lib/ext目录下或者java.ext.dirs系统数学定义的目录下加载类
- AppClassLoader:Application类加载器,负责从classpath环境变量中加载某些应用相关的类
- 工作原理
- 双亲委任机制:加载任务委托给父类加载器,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载,保证了java核心库的安全性
- 可见性机制:子类加载器可以看到父类加载器加载的类,而反之则不行
- 单一性机制:父类加载器加载过的类不能被子类加载器再次加载
垃圾回收GC
-
对象存活判断方法
- 引用计数法
- 每个对象有一个引用计数属性,新增一个对象引用计数加1,对象引用释放时减1,计数为0时可以回收
- 无法解决对象互相循环引用的问题
- 可达性分析
- 从GC Roots开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,是不可达对象
- GC Roots包括
- 虚拟机栈中引用的对象
- 方法区中类静态属性实体引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
- 引用计数法
-
垃圾收集算法
- 标记-清除算法
- 标记:根据对象存活判断的方法,标记所有需要回收的对象
- 清除:标记完成后统一回收所有被标记的对象
- 缺点
- 标记和清除的效率不高
- 标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致后续需要分配较大对象内存的时候无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作
- 复制算法
- 将可用内存按容量划分为两份大小相等的区域,每次只使用其中一块。当这一块内存用完了,就将还存活的对象复制到另外一块上面,然后把已使用过的内存区域一次性清理掉
- 优点:不用考虑内存碎片的问题,实现简单,运行高效
- 缺点:
- 内存缩小为原来的一半
- 持续复制长生存期的对象则导致效率降低
- 标记-整理算法
- 标记过程和“标记-清除”算法一样,但后续步骤不是直接对回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
- 分代收集算法
- 基本假设:绝大部分对象的生命周期都非常短暂,存活时间短
- 把Java堆分为新生代和老年代,根据各个年代的特点采用最适当的收集算法
- 新生代频繁创建销毁,只有少量存活,则选用复制算法
- 老年代对象存活率高,使用“标记-整理”算法
- 标记-清除算法
-
垃圾收集器
-
CMS收集器
- 基于“标记-清除”算法实现
- 以获取最短回收停顿时间为目标的收集器,重视服务的响应速度
- 四个步骤
- 初始标记:标记一下GC Roots能直接关联到的对象
- 并发标记:和用户线程一起并发执行关联路径搜索过程
- 重新标记:修正并发标记期间,因用户操作导致标记产生变动的那一部分对象的标记记录
- 并发清除:和用户线程一起并发执行对象清除
-
G1收集器
-
采用“标记-整理”算法
-
可预测停顿,让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒
-
内存模型与回收策略
-
- 年轻代
- Eden区:大多数情况对象在此区分配内存
- Survivor区:相当于Eden区和Old区的一个缓冲,分为两个区,From区和To区,Eden区没被回收的对象,会进入到Survivor的From区,执行GC后进入To区
- 使用“复制”算法
- 老年代
- Old区:经历了16次Minor GC还能在新生代中存活的对象,才会被送到老年区,老年区采用“标记-整理”算法
-