垃圾回收和内存分配策略
三问
- 那些内存需要回收
- 什么时候回收
- 如何回收
当高并发习引起错误是需要堆监控和调节
那些该回收
- 活着
- 死去
两种方法
引用计数器
- 虽然需要一些额外的控件计数
- 但是效率高
- java没有使用
- 缺点
- 很难解决对象的循环引用问题
- 比如对象内相互指向但是外部实例置为空了
可达性分析算法
使用GC root作为根节点,如果节点不可达及为死亡
虚拟机栈 引用的对象
方法去京台面两引用的对象
方法去常量引用的对象
本地方法栈中国JNI应用的对象
java虚拟机内部的应用
- 基本数据对应的Class对象,常驻异常对象
所有被同步持有的对象
反映java虚拟机内部情况的JM XBean,JVM TI中注册的回调,本地代码。
支持局部回收的特征,需要将关联的都添加到GC Roots 避免过多做过优化
四种引用
- 强引用
- 只要有就绝不回收
- 当内存不够就报错
- 软引用
- 有用但是非必须
- 在内存溢出之前会回收
- 如果还不够就抛出异常
- 弱引用
- 垃圾回收就会被回收
- 虚引用
- 在被回收之后有一个系统通知
- 完成一个实例,并且在java中找一下应用的实例。为什么这么设计?
对象的自我拯救
public static void main(String[] args) throws Throwable { SAVE_HOOK = new Main(); SAVE_HOOK = null; System.gc(); Thread.sleep(500); if(SAVE_HOOK != null){ SAVE_HOOK.isAlive(); }else{ System.out.println("no,i am dead"); } SAVE_HOOK = null; System.gc(); Thread.sleep(500); if(SAVE_HOOK != null){ SAVE_HOOK.isAlive(); }else{ System.out.println("i not dead once , but now,i am dead"); } } public static Main SAVE_HOOK = null; public void isAlive(){ System.out.println("yes,i am stall alive"); } @Override protected void finalize() throws Throwable{ super.finalize(); System.out.println("finalize if executed"); Main.SAVE_HOOK = this; }- 如上 对象第一次拯救成功,因为重写了finalize方法,在第一次回收会执行他自己的finalize方法。第二次在回收就必定会被回收。所有对象的fianlize方法之后被执行一次。
垃圾回收不是立即执行的,想队列添加一个,优先级很低
回收方法区
常量回收
类型
- 所有实例都被回收
- 类加载器都被回收了
- java。lang Class对象没有在任何地方被引用,无法通过反射访问该类的方法
Java虚拟机被允许对满足上述三个条件的无用类进行回收,这里说的仅仅是“被允许”,而并不是
和对象一样,没有引用了就必然会回收。关于是否要对类型进行回收,HotSpot虚拟机提供了-
Xnoclassgc参数进行控制,还可以使用-verbose:class以及-XX:+TraceClass-Loading、-XX:
+TraceClassUnLoading查看类加载和卸载信息,其中-verbose:class和-XX:+TraceClassLoading可以在
Product版的虚拟机中使用,-XX:+TraceClassUnLoading参数需要FastDebug版[1]的虚拟机支持。
垃圾收集算法
两种收集算法【从对象消亡的角度出发】
- 引用计数垃圾收集
- 追踪式垃圾收集
分带收集理论
java堆划分出不同的区域
弱分代
- 朝生夕死
强分代
- 熬过很多次的垃圾回收
新生代
老年代
跨代引用假说(新生代被老年代指向,反着来)
- 记忆集
- 将可能发生
- 发生新生代收集时只需要将老年代的一小部分进行扫描
标记-清除算法【老年代多】
首先标记所有需要回收的对象
缺点
- 执行效率不够稳定
- 内存空间碎片化
标记-复制算法【新生代多】
- 先标记需要清除的,把不清除的复制到另一块。然后把原来的一半清空
- 缺点
- 产生大量内存间赋值的开销
- 可用内存变为一半
- 优化,大部分新生代熬不过第一轮,所有不用1:1划分空间
标记-整理算法【老年代多】
- 先标记
- 后整理,存活对象往一边移动
- 然后清除后面的
- 缺点
- 需要移动对象
- 优点
- 没有碎片化
优化,容忍空间碎片存在,知道影响对象创建执行一次标记整理算法
根节点枚举
会STOP the world的
在HotSpot
的解决方案里,是使用一组称为OopMap的数据结构来达到这个目的。一旦类加载动作完成的时候,
HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来,在即时编译(见第11章)过程中,也
会在特定的位置记录下栈里和寄存器里哪些位置是引用。这样收集器在扫描时就可以直接得知这些信
息了,并不需要真正一个不漏地从方法区等GC Roots开始查找。
安全点
不是每个指令都生成
只是在“特定的位置”记录
了这些信息,这些位置被称为安全点(Safepoint)。
安全点位置的选取基本上是以“是否具有让程序长时间执行的特征”为标准
进行选定的
“长时间执行”的最明显特征就是指令序列的复用,
抢先式中断
(Preemptive Suspension)和主动式中断(Voluntary Suspension),
主动一条汇编指令就完成了轮询操作
安全区域
- 线程睡眠或者blocked
记忆集和卡表
- 从非收集区域指向收集区域的指针集合的抽象数据
- 字长精度
- 对象精度
- 卡精度
- 使用卡表
- 具体实现 记忆集
- 包含多个,只要有一个就变脏,没有就时干净的,垃圾收集时就筛选脏的元素
写屏障
卡表变脏
- 但出现跨代引用
- 时间,当应用类型字段赋值的那一刻
写屏障
- aop切面
伪共享问题
- 现在是在缓存行中进行单位存储的,如果两个线程互相独立的变成,恰好在一个缓存行 就是彼此影响
并发的可达性分析
因为标记阶段是共有特性,如果随这堆变大等待时间变大,开销会很大
三色标记
- 白色 便是为访问过,
- 黑色 扫描过,安全存货
- 灰色 北方问过,对象至少有一个引用为扫描过
产生对象消失的问题
插入一条或者多条从黑色到白泽的新引用
·赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。
增量更新要破坏的是第一个条件,当黑色对象插入新的指向白色对象的引用关系时,就将这个新
插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫
描一次。这可以简化理解为,黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象
了。
原始快照要破坏的是第二个条件,当灰色对象要删除指向白色对象的引用关系时,就将这个要删
除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描
一次。这也可以简化理解为,无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来
进行搜索。
经典的垃圾回收器
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kHMiChGM-1619316157182)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210416172015463.png)]
Serial收集器
CMS收集器
- 缺点
- 占用资源 cpu
- 浮动垃圾
- 内存碎片
jvm内存分配策略
根据使用那种垃圾分类工具
新生对象放在新生代,少数可能放在老年代
对象优先在EDEN分配
- Eden区空间不够发起一次Minor GC
- 使用参数 +PrubtGCDetails打印参数
- 代码实现代码清单
大对象进入老年代
- 消耗性能 所以直接放在老年代
- 尽量避免大量的朝生夕死的大对象
长期存活的对象进入老年代
- 默认15次
- 可以通过参数设定
动态对象年龄判断
- 同龄对象之和大于空间的一半,大于或者等于就进入老年代
空间分配担保
- 流程如何
画一张流程图表示对象创建之后的新生代和老年代变化
jvm基础工具
jps 检查虚拟机进程的工具
- jps -l
- -q
- -m
- -v
jstat 监控虚拟机统计信息
- 本地和远程不同
- 远程需要加入LVMID
jinfo: Java配置信息工具
jmap: Java内存映像工具
jhat: 虚拟机堆转储快照分析工具
jstack: Java堆栈跟踪工具
面试时说使用可视化工具
需要知道基础的和每个参数代表了什么
jvm调优
筛选可以使用到自己的代码之后的
记录取来