很给力:
Java GC
---- relive
1.Jvm内存结构
java虚拟机在解析执行java程序的时候会把其管理的内存主要分配成5块数据区域。
1.程序计数器
i.占用较小内存空间,可看作是当前线程所执行的字节码的行号指示器(学过汇编语言的同学应该了解);
ii.工作原理类似通过改变计数器的值来选取下一条执行的指令的字节码;
iii.线程私有:即为了让各个线程之间的计数器互不影响,独立存储,所以每条线程都需要一个独立的程序计数器,不能共享,否则易造成数据不一致;
2.java虚拟机栈
i.俗称栈区,描述java方法执行的内存模型,即每个方法在执行时都会创建一个栈帧用于存放变量、局部变量表、操作数栈、动态链接、方法出口等信息,一个方法从调用至完成,对应着一个栈帧在虚拟机中的出入栈过程;
ii.线程私有,与线程的生命周期一致;
3.本地方法栈
i.和java虚拟机栈类似,二者的区别即java虚拟机栈存放的对象是java中的字节码,而本地方法栈存放的对象是本地方法,例如c/c++写的程序等;
ii.线程私有,生命周期与线程一致;
4.堆区
i.java虚拟机管理的内存中最大的一块,也是线程共享的内存区;
ii.其作用主要是存放对象实例,java虚拟机规范中声明“所有的对象实例以及数组都需要在对上分配内存。”;
iii.Java GC的主要区域,jvm基本采用分代手机算法,可以把堆区细分为新生代和老年代;
5.方法区
i.线程共享;
ii.用于存放已经被虚拟机加载的类信息、常量、运行时常量、静态变量、即时编译器编译后的代码等;
iii.又叫做non-heap;
2.Java GC机制
i.GC面临的问题
i.什么样的对象会被回收?
参看“什么样的对象会被回收”
2.何时回收?
有jvm决定,对程序、程序员透明,何时回收不可预知;
iii.怎么回收?
参看“c,d,e,f,g”
b.什么样的对象将会被回收
当一个对象在对内存中完成初始化之后,其所经历的状态有三种,其中,当对象的状态变为不可达之后,将会被java GC回收内存资源。
i.可达状态:存在直接引用,例如Object oo = new Object();
ii.可恢复状态:如果不再有任何引用,则进入可恢复状态,在GC回收内存前,会调用其finalize方法来清理资源,例如oo=null;
iii.不可达状态:不再有任何引用,且调用其finalize方法后对象没有变成回达状体,则该对象将会永久的失去引用,变成不可达状态,将会真正的被GC回收内存资源;
iii.强制垃圾回收
当一个对象失去引用后,会进入可恢复状态,此时如果调用其finalize方法没有使其重回可达状态,则该对象会永久失去引用等待java GC回收内存,系统什么时候回收内存,对程序、程序员是透明的,由jvm来选择何时进行回收;但是程序中可以调用以下两种方式来进行强制内存回收,但是这种“强制”对jvm来说其实是一种“建议”,即便是写了轻质回收的代码,jvm也不一定会立即回收内存资源。
i.System.gc();
ii.Runtime.getRuntime().gc();
iii.例证
1.Java_bin
2.
3.Main
iv.执行结果
5.结论
程序new了10个无引用的匿名对象,每new一个无引用的对象都会“强制”系统回收内存,但是从程序多次执行的表现观察,调用finalize方法的次数基本上每次都不相同而且不一定等于10次或0次,所以得出结论:java强制回收不是真的强制,而是对jvm的一种“建议”,“建议”其现在进行GC操作,但是jvm什么时候真正开始回收内存,对程序、程序员而言是透明的,不可预测。
d.finalize方法
i.解释:finalize方法源自Object中,protected修饰,即允许其被子类继承、重写;在java真正执行GC回收一个对象占用的内存之前会调用该方法来清理资源,如果在该方法中,对象没有变变回达状态,则finalize方法执行完成返回后对象消失,垃圾回收开始真正执行;
ii.建议:程序员不要主动调用对象的finalize方法,应该交由垃圾回收机制来调用;
iii.备注:finalize方法何时被调用、是否被调用具有不确定性;当jvm首先执行finalize的时候,有可能使对象重可恢复状态变成可达状态,从而不被GC掉;当JVM执行finalize方法的时候出现异常,垃圾回收机制不会报告一场,程序继续运行,但是finalize异常之后的代码不会继续执行(如下,修改Main.class的finalize方法,使其抛出异常,再重新执行Main.java文件的时候,并无任何异常抛出,但是再也不会输出System.out的字符);
e.java中的引用
i.强引用:对象有一个及其以上的引用,例如Object oo = new Object();
ii.软引用:java中通过SoftReference类来实现引用级别低于强引用,当系统内存足够时,起不会被系统回收;当系统内存不够使,其可能被回收;
iii.弱引用:通过WeakReference类实现,引用级别低于软引用,无论系统内存是否足够,系统进行垃圾回收的时候总是会回收其占用的内存;
iv.虚引用:通过PhantomReference类实现,引用级别最低,类似没有引用,主要用于跟踪对象被垃圾回收的状态;
v.用法示例:
String str = new String("我是xx");
WeakReference wr = new WeakReference(str);
//软引用和虚引用类似
f.常用对象判死算法
i.引用计数法
通过引用计数器来判断对象是否被引用,例如有一个引用则引用计数器+1。
a.缺点:对象的相互引用会导致无法回收内存;
b.代码:
Java_bin_a:
Java_bin_b:
c.图示:
ii.可达性分析
类似树状结构,从“GC Root”节点开始通过引用链搜索,如果一个对象无路可达,则表示该对象处于可恢复状态,等待被GC回收内存资源;
g.垃圾收集算法
i.标记-清除
先标记“不可达”状态的对象,然后统一回收内存资源;效率不高,同时会导致内存碎片化;
b.复制算法
为了提高“标记-清楚”算法的效率,将可用内存划分成大小相等的两块,每次只是用其中的一块,当这一块内存用完了(包含存活对象、等待回收的对象和碎片占用的空间),则一次性拷贝到另一块,此时将会释放等待回收对象的内存空间,同时解决内存碎片问题,存活对象连续占用一片内存,再一次性清空原先的内存块;
iii.标记-整理
相比较“复制算法”,不会浪费50%的内存,算法不直接回收不可达对象占用的内存,而是让存活对象都向一端移动,然后清理释放边界的内存空间;
iv.分代收集
根据对象的存活周期在堆中将对象分为新生代和老年代,根据不同代特点不同选择不同的算法:
新生代——绝大部分对象很快不可达需要回收内存,采用复制算法,每次只需要付出极少的复制代价;
老年代——绝大部分对象长期保持可达状态,适合采用“标记-清理”或“标记-整理”算法;
3.其他
a.随着操作系统的发展,32bit系统必然会逐渐被64bit系统取代。虽然java虚拟机在很早以前就已经推出了64bit的版本,但是直到目前,任旧存在问题——java程序跑在64bit系统上系能消耗比32bit系统的多,多消耗20%左右内存,存在10%左右的性能差距;
b.首先要注意,java GC只针对内存的回收,对类似数据库连接、io等物力资源的释放不会自动回收,均需要手动close掉;
c.程序员无法精确的控制何时回收内存,只能给jvm提供“建议”或在代码上做一定的优化;
|