虽然Java大部分内存由垃圾回收器(GC)管理着,但是对内存的释放还是有必要知道一点的,毕竟对我们编程也是有一定的帮助的。例如,解决内存泄露问题等等!!
GC回收哪个区域的垃圾
这里所说的区域指的是运行时数据区,有5种:程序计数器,虚拟机栈,本地方法栈,堆,方法区。
我们一一分析哪个区域由GC管理:
程序计数器:这里只保存当前线程锁执行的字节行号,也就是执行到了哪里,只是一块很小的内存,不在GC管理范围内。
虚拟机栈:这里保存的是方法执行时创建的栈帧,方法执行完后,自动从虚拟机栈弹出。也就是说,栈内数据,超过作用域后会被自动释放(创建的对象在堆内,栈只是保存对象的引用),所以也不在GC管理范围内。
本地方法栈:和虚拟机栈类似,只不过是Native方法,所以也不在GC管理范围内。
堆:储存对象实例和数组,大部分垃圾都在这里产生,这里是GC重点管理的区域。
方法区:储存类信息,常量,静态变量等信息,也会储存对象信息,也在GC管理范围内。
所以GC主要回收是的 堆 和 方法区 的垃圾。
GC回收哪些对象(所谓的“垃圾”)
有2种方法判断对象是“垃圾”,可以被GC回收:
引用计数算法
给对象添加一个引用计数器,当一个地方引用该对象,计数器值+1;当引用失效时,计数器值-1;如果引用计数器的值为0,说明该对象不能再次使用,也就成了“垃圾”,可以被GC回收。
引用计数法不能解决循环引用问题,也就是A对象持有B,B对象持有A,那么A和B的计数器值永远不会为0。故Java没有采用这种算法管理内存。
根搜索算法
这种算法的思路是通过一系列名为“GC ROOT”的对象为起始点,从这个节点像下搜索,搜索走过的路径为引用链,当一个对象没有任何引用链相连,即从GC Root到该对象不可达,则证明该对象不可用,也就成了“垃圾”,可以被GC回收。
哪些可以作为“GC ROOT”:
虚拟机栈(栈帧中的本地变量表)中的引用的对象
方法区中的类静态属性引用的对象
方法区中的常量引用的对象
本地方法栈中JNI(Native方法)的引用对象
GC什么时候执行
新生代eden空间满了,触发minor(小) gc;
升到老年代的对象大于老年代剩余空间,触发full(满) gc;
或者没满,但是被HandlePromotionFailure参数强制了,触发full(满) gc;
GC做了什么
我们先了解分代的垃圾回收机制:
新生代(Young generation)
用于保存第一次创建的对象,分为3个区域:1个伊甸园空间和2个幸存者空间,默认的比例是8:1:1
回收原理:
绝大多数刚刚被创建的对象会存放在伊甸园空间(Eden)。
在伊甸园空间执行第一次GC(Minor GC)之后,存活的对象被移动到其中一个幸存者空间(如S1,S1为From区域),伊甸园空间会被清空。
再次伊甸园空间执行GC后(Minor GC 是对整个新生代检查,不仅仅是伊甸园空间),如果幸存者空间S1的对象依旧还存活,那么这些存活的对象年龄增加。然后所有还存活的对象会被移动到另一个幸存者空间(如S2,S2是下次Minor GC的From区域)中,伊甸园空间和幸存者空间S1会被清空。
步骤3执行N(N = MaxTenuringThreshold(年龄阀值设定,默认15))次之后,依然存活的对象,就会被移动到老年代。
一般来说,新生代对象要经历N次Minor GC之后才会被移动到老年代。不过也有例外,对于一些比较大的对象(需要分配一块比较大的连续内存空间),则直接进入到老年代。一般在Survivor 空间不足的情况下发生。
老年代(Old generation)
老年代只有一个区域,但是这块区域很大,默认大小是新生代的2倍,正因如此,发生在老年代的GC(发生在老年代的GC叫Full GC)次数要比新生代少得多,而且做一次 Full GC 的时间比 Minor GC 要更长(约10倍)。
回收老年代的算法有几种,待会再分析。
持久代(Permanent generation)
也称之为 方法区(Method area):用于保存类常量以及字符串常量。注意,这个区域不是用于存储那些从老年代存活下来的对象,这个区域也可能发生GC。发生在这个区域的GC事件也被算为 Full GC 。
垃圾回收算法
标志-清除算法
从根集合(GC ROOT)开始扫描,第一次扫描,标志存活的对象,第二次扫描,直接回收未被标志的对象。
因为未对存活的对象做移动处理,仅对不存活的对象进行处理,所以效率较高,也因为如此,会残留很多内存碎片。
复制算法
复制算法把内存分为2个区间,一个为活动区间,一个为空闲区间。
从根集合(GC ROOT)开始扫描,把存活的对象拷贝到空闲区间,扫描完活动区间后,一次性回收整个区间的内存。此时,空闲区间就成了活动区间,如此反复。
复制算法以牺牲一半的内存为代价,换来高速率的回收,典型的以空间换时间!因此,复制算法适用于对象存活率较低的内存中。
到这里,你应该会想到新生代的垃圾回收原理吧?!正是,新生代垃圾回收采用的就是复制算法。
标志-整理算法
标志-整理算法采用了标志-清除算法去回收不存活的对象,但在回收后,会将存活的对象往左端空闲空间移动,并更新对应的指针。所以相对于标志-清除算法,解决了标志-清除算法残留的内存碎片的问题,但因此成本会更高,比较适用回收不那么频繁的内存中。
由于年老代的Full GC不容易触发,所以年老代的垃圾回收采用了标志-整理算法。
垃圾回收器
关于有哪些垃圾回收器,请参考 Java性能优化之JVM GC(垃圾回收机制) 的 “垃圾回收器简介”一节。
配置参数
-Xms JVM启动的时候设置初始堆的大小
-Xmx 设置最大堆的大小
-Xmn 设置年轻代的大小
-XX:PermSize 设置持久代的初始的大小
-XX:MaxPermSize 设置持久代的最大值
内存优化
内存优化可以参考5R做法:Reckon(计算),Reduce(减少) ,Reuse(重用),Recycle(回收),Review(检查)。
请参考:
参考资料
网友评论