java 回收机制 garbage collection gc 这个是JVM来处理的
回收分2个步骤:
1. 如果确认某个对象是“垃圾”?
1) 引用计数法 如果一个对象被创建但是没有任何地方被使用到,那么这个对象就成为可被回收的对象。 Test test=new Test();
这种方法 简单,效率高,但是无法解决循环引用的问题,所以java没有采用这种方法,python采用的是引用计数法
2)什么是循环引用?
MyObject object1 = new MyObject();
MyObject object2 = new MyObject();
// 两个对象相互引用
object1.object = object2;
object2.object = object1;
//两个对象都设置为null object1和object2指向的对象已经不可能再被访问 由于它们互相引用对方,导致它们的引用计数都不为0,那么垃圾收集器就永远不会回收它们。
object1 = null;
object2 = null;
3) 所以java采用的是可达性分析法:通过"GC Roots"对象作为起点搜索,如果在"GC Roots"和一个对象之间没有可达路径,那么该对象是不可达的,这个时候不会立即回收,而是最少要被标记两次
那么对象才会由不可达对象变为可回收对象。
java 平时如何将常见的对象判定为可回收对象:
1) 显示地将某个引用赋值为null或者将已经指向某个对象的引用指向新的对象
Object obj = new Object();
//赋值为null
obj = null;
Object obj1 = new Object();
Object obj2 = new Object();
//指向新的对象 那么原来的对象就没有了 因为指向新对象了
obj1 = obj2;
2) 局部引用的对象:
for(int i=0;i<10;i++) {
Object obj = new Object();
System.out.println(obj.getClass());
}
object对象每循环执行完一次,都会成为可回收的对象
3)弱引用与其关联的对象,
WeakReference<String> wr = new WeakReference<String>(new String("world"));
2 如何回收
1) 典型的垃圾收集算法
确认了垃圾后,开始回收,那么如何高效的进行垃圾回收呢? 下面几个办法
1. Mark-Sweep(标记-清除)算法
标记-清除算法分为两个阶段:标记阶段和清除阶段。
标记阶段:标记出所以需要被回收的对象, 可达性分析法
清除阶段:回收被标记的对象所占用的空间
这种方法容易产生内存碎片,碎片太多可能会导致后续过程中为大对象分配空间时内存不够而提前出发新的一次垃圾收集动作
2. Copying(复制)算法
将内存容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。
但是这种方法会将内存控件减半,
3. Mark-Compact(标记-整理)算法
算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存
4 Generational Collection(分代收集)算法
分代收集算法是目前大部分JVM的垃圾收集器采用的算法
根据对象存活的生命周期将内存分为若干个不同区域,一般是老年代和新生代
老年代: 每次垃圾收集只有少量对象需要回收 一般使用Mark-Compact
新生代: 每次垃圾回收时都有大量的对象需要被回收,那么可以根据不同代的特点采取最合适的收集算法
使用copying算法,因为新生代中每次垃圾回收都要回收大部分,但是不是按照1:1的比例来划分新生代的空间的,一般是新生代划分为一块较大的Eden空间和两块较小的Survivor控件,每次
使用Eden空间和一块Survivor空间,回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。
2) 典型的垃圾收集器
1. Serial/Serial Old:它是一个单线程收集器,并且在它进行垃圾收集时,必须暂停所有用户线程
Serial收集器是针对新生代的收集器,采用的是Copying算法,Serial Old收集器是针对老年代的收集器,采用的是Mark-Compact算法。它的优点是实现简单高效,但是缺点是会给用户带来停顿。
2. ParNew是Serial收集器的多线程版本,使用多个线程进行垃圾收集
3. Parallel Scavenge收集器是一个新生代的多线程收集器(并行收集器)
它在回收期间不需要暂停其他用户线程,其采用的是Copying算法,该收集器与前两个收集器有所不同,它主要是为了达到一个可控的吞吐量。
4. Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),使用多线程和Mark-Compact算法。 因为是老年代么
5. CMS(Current Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是Mark-Sweep算法。
6. G1收集器是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。
内存分配:
一个对象的内存主要分配到新生代的Eden和From Space,少数情况下才会到老生代,如果新生代的Eden Space和From Space的空间不足,则会发起一次GC
GC的过程中,1.会将Eden Space和From Space中的存活对象移动到To Space, From和To就是两块Survivor
2.然后将Eden Space和From Space进行清理
3.如果To Space控件无法存储某个对象,就会将该对象移动到老年代中
gc后,使用的便是Eden space和To Space了(因为将From Space清理了 将存活的对象放到了To Space了)
下次GC时会将存活对象复制到From Space,如此反复循环。当对象在Survivor区躲过一次GC的话,其对象年龄便会加1,默认情况下,如果对象年龄达到15岁,就会移动到老年代中。
一般来说,大对象会被直接分配到老年代,所谓的大对象是指需要大量连续存储空间的对象,最常见的一种大对象就是大数组
byte[] data = new byte[4*1024*1024]
这种一般会直接在老年代分配存储空间。
当然分配的规则并不是百分之百固定的,这要取决于当前使用的是哪种垃圾收集器组合和JVM的相关参数。 |
|