Java gc 概况
Java GC(Garbage Collection,垃圾回收)机制,是Java相对于C/C++来讲我觉得简单暴力的一个地方,Java开发这不需要专门写内存回收,垃圾清理的代码,free和release啥的就不用过多的去注意了,Java虚拟机存在自动的内存管理和垃圾回收机制,对JVM中的内存进行标记,never stop,防止出现内存泄露和溢出问题
但Java中有finalize() 他的作用主要是清理那些并非使用new 来获得内存区域的对象, 垃圾回收器只知道释放那些new出来的内存(理解有限,不足请指正),所以这时候就可以用finalize(),垃圾回收器准备好释放对象占用的内存空间,首先会调用finalize()的方法,然后在下一次垃圾回收动作发生时,才会真正回收对象占用的内存(这种情况发生在使用本地方法上,java中可能调用了非java方法,可能调用了C中的malloc()来分配内存,而且没有free()函数,这时候不就蛋疼了)
只说HotSpot的GC机制
Java GC机制需要做3件事:确定哪些内存需要回收,确定什么时候执行GC,如何执行GC
1 java内存是如何分配的
程序计数器:程序计数器占比较小的内存区域,用来指示当先所执行的字节码到第几行了,通过改变计数器的值来读取下一行语句指令,程序计数器是 线程 私有的
如果现在在执行的是一个本地方法(native c编写的)计数器值为undefined
虚拟机栈:一个线程的每个方法在执行的时候,都会创建一个栈帧,栈帧中有局部变量表,操作数栈,动态链接,方法出口,用于存放此次方法调用过程中的临时变量,参数,和中间结果,每一个方法的执行都完成一次栈帧压栈 到 弹栈的过程
局部变量表存放方法相关局部变量,包括基本数据类型,对象引用,返回地址
虚拟机栈里有两种异常,如果栈深度大于虚拟机允许的最大深度,StatckOverFlowError,不过一般虚拟机都允许动态拓展栈的大小,所以线程一直申请栈,知道内存不足 OutOfMemoryError(都是Error级别)
虚拟机栈是 线程 私有的
本地方法栈:执行本地方法呗 线程 私有
堆区:最牛逼的地方了 在jvm管理中堆区是最大的一块,是GC机制管理的主要区域, 所有 线程 共享 ,在虚拟机启动时创建,为了存放对象实例
堆内存的逻辑上是连续的(物理上不需要),大小可固定,可不固定,如果在GC后依然没有足够的内存分配,会抛出OutOfMemoryError:Java heap sapce异常
方法区:在java虚拟机中,将方法区作为堆的一个逻辑部分来对待,但方法区并不是堆,但我觉得理论上他还是用堆来实现的
方法区是各个线程共享的区域,用于存储已经被虚拟机加载的类信息,final常量,静态变量
方法区可以选择是否执行GC操作,一般这里的执行垃圾回收很少,所以才被称为“永久代”
String s = "abc";
运行时常量池是方法区的一部分,这个“abc”是存放在方法区里的,常量池在方法区中,但理论上方法区用堆区实现,所以好多人说常量池在堆里,纠正过来,常量池在方法区,这里也可以存放运行时产生的常量(比如String的intern()方法,作用就是String维护里一个常量池,如果调用的字符“cde”在池子里,那就返回字符串地址,没有的话新建一个,返回地址)
Java对象访问方式
refernce指向句柄 然后再去……慢
reference直接指向对象在堆中的实际地址,快,hotspot用的就是这个
2 Java内存分配机制
一般对象的内存分配实在堆上进行,Java内存分配和回收的机制就是分代分配,分代回收,根据对象存活时间分为:年轻带,年老代,永久代(方法区)
年轻代:对象创建时,首先发生在年轻代(大对象可以创建在年老代),大部分很快就不会再用了,很快变得不可达,就被年轻代的GC清理掉了
年轻代的内存分为3个区域:Eden区,两个个存活区(Srivivor0,S1)
垃圾回收分类:
Minor GC
Full GC(Major GC)
1大多数对象分配会在Eden上,Eden连续,所以分配速度快
2 Eden区满了,执行Minor GC,将清理后存活的对象复制到一个存活区,两个存活区保证其中一个是空白的
3 当Survivor0已满了,将依然活着的对象复制到S1去,S0清空,以后Eden执行minor GC后将剩余对象添加到S1中
4当连个存活区切换几次后,hotspot默认15次,依然存活的对象复制到年老代
经过一次GC和复制,一个survivor中保存依然活着的对象,其他的都清空,这次GC时S0,S1再角色切换,这就是“停止-复制”清理法,年老代不采用这种方式!!
年老代:年老代的内存空间一般比年轻代要大,所以发生GC的次数也相对较少,当年老代内存不足后,将执行Major GC--Full GC
如果对象较大,年轻代内存不足,大对象可以直接分配到年老区
当年老区存在指向年轻区的引用时,年老区中有一个512byte的“card table” 存放引用记录,不然年老区整个遍历查询 累死
3 Java GC机制
年轻代:Eden区和S0 S1 占80% 10% 10& 采用“停止-复制”算法进行清理,当Survivor+Eden中存活的对象超过10% 则需要将一部分对象分配到年老代
年老代:对象很多,很大,不易采用“停止-复制”,一般用“标记--整理”算法:标记处依然存活的对象(存在引用的),将所有存活的对象 向一端移动,保证内存连续
在发生minor GC时 虚拟机会检查每次进入年老区的对象是否大于剩余空间,如果没地方了,则执行Full GC
永久代:常量池中的常量,无用的类信息, 没有引用了直接就回收了,对于无用的类:
1类的所有实例已经回收了
2加载类的ClassLoader已经回收了
3 类对象的Class对象没被引用(没有通过反射引用该类的地方)
4 垃圾回收如何工作
“引用计数” 每个对象都有一个引用计数器,当有引用连接是,计数器+1,解除连接或引用置为null时,计数器-1;
import java.util.ArrayList;
/**
* Created by albert.bai on 2014/10/31.
*/
public class TestGC {
public static void main(String[] args) {
int i = 0;
ArrayList<Object> list = new ArrayList<Object>();
while(i<10) {
Object s = new Object();
list.add(s);
s=null;
}
}
}
这时候所有Object都没有释放,因为s虽然置null了,但list有对它的引用,依然没法收回
还有一种如果对象间存在循环引用,但这时计数器永远不会是0;也就没法收回! 放弃这种吧
更快的模式:对任何“活”着的对象,一定能最终追溯到其存活在堆栈或静态变量区(方法区)的引用,通常,GC采用有向图的方式记录和管理堆中的所有对象,通过这种方式来确定是否“可达”,这个引用链条可以穿过多个对象层次,从堆栈和静态变量区开始遍历所有引用(我觉得这张有向图开始时是以栈中的引用为起点开始搭建的),对于每一个发现的引用你必须追踪他所有的引用对象,然后是次对象包含的所有引用,一次反复,所以采用的是“有向图”,知道引用的网络全部都被访问到为止,你访问的对象必须是“活的‘,这就解决了存在交互引用的整体对象的问题!