Java——JVM(校招准备)
JVM运行时数据区域
1.程序计数器
- 相当于字节码的行号指示器
- 每个线程都会有独立的程序计数器
- 属于线程私有内存
2.Java虚拟机栈
- 线程私有
- 保存一个方法的局部变量、操作数栈、常量池指针
- 局部变量
- 存放编译期可知的数据类型
- 基本数据类型
- 对象引用
- 异常
- StackoverflowError(线程请求的栈深度大于允许的深度)
- outofmemoryError(无法申请到足够内存)
3.本地方法栈
- 与虚拟机栈类似
- 针对Native方法服务
- 异常也一样
- 一些虚拟机会将两个合并
4.Java堆
- 线程共享
- 内存最大的一块
- 存放对象实例
- 垃圾收集器发生的主要地方
- 新生代/老年代
5.方法区
- 线程共享
- Java堆的一部分
- 存储已被加载的类信息、常量、静态变量等数据
- 异常
- outofmemoryError(无法申请到足够内存)
6.运行时常量池
- 方法区的一部分
- 存放编译期生成的字面量和符号引用
- 是为了提升效率和减少内存分配
- 异常
- outofmemoryError(无法申请到足够内存)
- 如何进入
- 使用双引号搞出来的String会在常量池中
- 不是双引号搞出来的,可以使用intern方法
7.直接内存
- 并不是运行时数据区域一部分,但是经常使用
- 异常
- outofmemoryError(无法申请到足够内存)
- 直接内存使用场景:
- 有很大的数据需要存储,它的生命周期很长
- 适合频繁的IO操作,例如网络并发场景
- 直接内存(堆外内存)与堆内存比较:
- 直接内存申请空间耗费更高的性能,当频繁申请到一定量时尤为明显
- 直接内存IO读写的性能要优于普通的堆内存,在多次读写操作的情况下差异明显
类加载机制
1.加载
类的加载就是将class文件中的二进制数据读取到内存中,然后将该字节流所代表的静态数据结构转化为方法区中运行的数据结构,并且在堆内存中生成一个java.lang.Class对象作为访问方法区数据结构的入口
2.连接
2.1验证
确保当前class文件的字节流所包含的内容符合当前JVM的规范要求,并且不会出现危害JVM自身安全的代码,当前字节流不符合规范会抛出VerifyError的异常,或者子异常,验证的信息有:(1)文件格式:验证二进制文件是什么类型,验证是否符合当前JVM规范,(2)元数据验证:检查类是否有父类、接口,验证其父类、接口的合法性, 验证被final修饰的类, 验证是否是抽象类,是否实现了父类的抽象方法或者接口中的方法, 验证方法的重载。(3)字节码验证,主要验证程序的控制流程比如循环、分支等,(4)符号验证,主要验证符号引用转换为直接引用时的合法性
2.2准备
当一个Class文件的字节流通过验证,就开始为该对象的类变量,也就是静态变量,分配内存和初始值。
2.3解析
所谓解析就是指在常量池中找到类、接口、方法、字段的符号引用,并将其替换为直接引用的过程。
3…初始化
执行()方法
判断对象死亡
一、引用计数法
很难解决决对象间循环引用的问题,所以不用
二、可达性分析
1.基本思想
- 通过GC root对象作为起点
- 搜索引用链
- 没有任何向连,即是可以回收的
2.可作为GC root对象包括:
-
虚拟机栈
- 本地变量表
-
方法区
- 静态属性引用
- 常量引用对象
-
本地方法栈中JIN(一般说的Native方法)引用的对象
真正回收过程
1.真正回收需要至少两次回收
2.没有引用链后, 先被标记,再做筛选
- 没有必要执行finalize()方法的条件
- 没有覆盖finalize()方法
- finalize()方法已经被调用过
3.如果被判定需要执行finalize()方法,这个对象会被放进一个队列中等待执行。
引用
- 强引用
- 类型 obj = new Object()
- 强引用存在,不可被回收
- 软引用
- 还有用,但是并非必需
- 即将内存溢出时,会回收 若还不够,再抛异常
- 应用:
- 假如有一个应用需要读取大量的本地图片,如果每次读取图片都从硬盘读取,则会严重影响性能, 但是如果全部加载到内存当中,又有可能造成内存溢出
- 使用软引用可以解决这个问题。
- 弱引用
- 非必需对象
- 只能存活到下一次GC
- 虚引用
- 无法通过虚引用获得实例
- 设虚引用目的是回收时能得到通知
垃圾回收算法
1.复制算法
为了解决效率问题,划分两块,每次只使用其中一块用完了,做一次清理,然后把存活的移动到另一块
-
优点
- 实现简单
- 高效
- 没有碎片
- 适用于对象存活率低的情况
-
缺点
- 只能利用一半内存
- 用于新生代的收集
-
优化办法:
- 划分为一个较大Eden空间
- 两个较小的Survivor空间(1:1:8)
- 每次使用一个Eden和一个survivor空间
- 清理时,将存活的移动到未使用的survivor空间上
2.标记-清除算法
- 简单,基础
- 缺点
- 效率低
- 清除后会有大量不连续的内存碎片
3.标记-整理算法/标记-压缩算法
- 适用于老年代,对象存活率高
- 与标记删除类似,只是后续处理是把对象移动到一端
4.分带收集算法
根据对象存活周期不同,划分不同的代
-
新生代
- 存活率低
- 适用复制算法
-
老年代
- 存活率高
- 标记-清理/标记-整理
垃圾收集器
Minor GC
- 新生代的收集
Full GC
- 老年区的GC
- 发生原因
- 老年代空间不足
- 永久代空间满
- CMS GC 时出现 Promotion Failed 和 Concurrent Mode Failure
- system.gc
jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.9 默认垃圾收集器G1
1.Serial收集器
- 单线程, 串行收集器 ,最基本的收集器
- Client模式下默认新生代收集器
- 新生代 复制算法
- 优点:简单高效
- 缺陷:需要停止所有线程而且停止时间比较长
2.parNew收集器
- Serial 新生代的 多线程版本
3.Parallel Scavenge
- 并行独占的 使用复制算法的新生代收集器, 吞吐量优先
- 多线程
4.Serial Old
- 采用标记整理算法的serial收集器老年版
5.Paralled Old
- Patallel Scavenge老年代版本
- 多线程,标记整理算法
6.CMS
- 一种以获取最短回收停顿时间为目标的并发的老年代算法
- 采用标记——清除算法
初始标记
- 根可以直接关联到的对象
- 速度快
并发标记(并发)
- 要标记过程,标记全部对象
重新标记
- 由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正
并发清除(并发)
- 基于标记结果,直接清理对象
7.G1
-
概述
- 并行回收器,它把堆内存分割为很多不相关的区间
- 每个区间可以属于老年代或者年轻代,并且每个年龄代区间可以是物理上不连续的
-
特点
- 不同于空间整理
- 整体上来看是基于标记-整理
- 局部上Region看是基于复制
- 分配大对象, 不会因为空间碎片进行GC
初始标记(停顿)
标记一下GC Roots能关联到的对象,并且修改TAMS的值,使得下一阶段程序在正确可用的Region中创建。
并发标记
做可达性分析,找出存活对象。耗时长。这段时间内对象变化记录会被记录到Remembered Set Logs里面。
最终标记
修正前一阶段并发阶段发生改变的对象。 把Remembered Set Logs数据合并到Remembered Set中。这段需要停顿,但是并行执行。
筛选回收
对各个Region回收价值和成本进行排序,根据用户设置优先回收一部分。这部分可以并发。