为什么GC
1.什么是垃圾
面试题
垃圾:运行程序中没有任何指针指向的对象;
为什么需要GC
内存迟早会被消耗完,JVM将整理出的内存分配给新的对象;没有GC就不能保证营养程序的正常运行;
内存泄露:没有指针指向该对象;但也无法回收
JAVA垃圾回收机制
应该关心那些区域
主要是:堆和方法区
垃圾回收器:对年轻代,老年代回收;甚至对全堆和方法区回收;
频繁收集Young区,较少收集Old区,基本不动Perm区(或元空间)
throw是生成异常的过程;
throw是语句抛出一个异常,一般是在代码块的内部,当程序出现某种逻辑错误时由程序员主动抛出某种特定类型的异常。
if(s.equals("abc")) { throw new NumberFormatException(); } else { System.out.println(s); }
throws 是方法可能抛出异常的声明;
throws:用在声明方法时,表示该方法可能要抛出异常,交给上层调用它的方法程序处理。
public void function() throws Exception{......}
用try{…}catch{…}捕捉了异常之后一定要对在catch{…}中对其进行处理。
如果在函数体内用throw抛出了某种异常,最好要在函数名中加throws抛异常声明,然后交给调用它的上层函数进行处理。
垃圾回收的相关算法
1.标记阶段:引用计数算法
垃圾标记阶段:判断对象是否存活
垃圾:一个对象已经不再被任何的存活对象继续引用;
判断对象存活两种方式:引用计数算法和可达性分析算法
JAVA不使用引用计数器法:因为无法处理循环引用的情况;
循环引用
结论:
2.标记阶段:可达性分析算法(根搜索算法、追踪性垃圾收集)
GC Roots:根集合就是一组必须活跃的引用
GC Roots包含以下几类元素
1.虚拟机栈、本地方法栈或JNI引用的对象;
2.方法区中类静态属性引用的对象
3.方法区中常量引用的对象
4.所有被同步锁synchronized持有的对象
5.JVM内部引用:基本数据类型对应的Class对象,常驻异常对象,系统类加载器
本地代码缓存;
GC ROOTs:采用栈方式存放变量和指针;若一个指针,它保存了堆内存里面的对象,但自己又不存放在堆内存里,那它就是一个Root
STW的原因
3.对象的finalization机制
finalize:完成对象被销毁之前的自定义处理逻辑;
允许在子类被重写,用于在对象被回收时进行资源释放;
永远不要主动调用finalize方法;
1.在finalize时会导致对象复活;
2.finalize方法执行时间无法保证,由GC线程决定,可能不会执行;
3.糟糕的finalize会影响GC的性能;
由于finalize方法的存在:JVM的对象存在三种状态
1.可触及的
2.可复活的
3.不可触及的
finalize只会被调用一次
判断一个对象ObjA是否可以回收,至少要经历两次标记过程
例子:
总结
4.MAT与JProfiler的GC Roots溯源
获取Dump文件
方式1:命令行产生
方式2:JVisualVM导出
JVisualVM导出
1.选中当前进程
2.选择堆Dump
3.生成快照
4.存储Dump文件
5.用MAT查看Dump文件
5.清除阶段:标记-清除算法
5.1标记-清除(Mark-Sweep)算法
标记:非垃圾对象,标记可达对象;记录在对象的header中;
清除:对堆内存循环变量,发现非标记的对象,即不可达对象,则回收掉;
缺点:效率不高,要循环遍历;会产生内存碎片
清除:只是把清除的对象空间地址,存放在空闲列表中;下次使用时,可以直接覆盖;
6.清除阶段:复制清除算法
核心思想
把内存空间分为两块;
年轻代的S0和S1区就是使用的这种算法;
优点:不会产生碎片问题,创建对象时可以指针碰撞直接分配;
缺点:栈指针的引用改变为另一个区域,所以需要从新计算改变这些引用;
如图,对象地址改变,引用也需要改变;
注意:若内存中存活的对象比较多,则复制算法性能会下降,因为大部分对象都被复制移动了。
应用场景:
新生代的Survivor区,因为这个区域的对象都是朝生即死的;
不使用老年代,因为老年代的对象大部分都是存活的;
7.清除阶段:标记-压缩(整理)(Mark-Compact)算法
执行过程两个阶段:
1.标记阶段:标记所有存活的对象,即可达对象;
2.压缩阶段:压缩到内存一端,按顺序排放;
无需把内存分成两个区域;
标记-压缩算法:实际上是标记-清除-压缩算法;
只是它是非移动的回收算法(无需把内存分成两份),未解决压缩(移动时)仍要重新修改内存中引用地址的问题;wux
创建对象分配堆空间:
1.复制算法和标记-压缩算法,都可以使用指针碰撞;
2.标记-清除算法,可以使用空闲列表分配;
指针碰撞
8.总结
9.分代收集算法
具体分代算法
年轻代:使用复制算法;S0,S1;
老年代:是由标记-清除和标记-压缩混合实现
10.增量收集算法、分区算法
这两种算法主要是解决STW(Stop World)
10.1增量收集算法
增量收集、分区与其他传统算法交替执行;需要解决线程间冲突的问题;
低延时并行处理;
缺点:
10.2分区算法
说明
分区算法把内存分成一个一个小region;每次回收有时间限制,时间长多回收几个区域;时间短少回收几个区域。
最后: