堆内存空间
- 新生代(Young Generation):Eden区、两个Survivor(Survivor0、Survivor1)区:
1、新生代内存划分默认为8:1:1,新生代大小可由-Xmn来控制,Eden和Survivor比例可以用-XX:SurvivorRatio来控制;
2、大部分对象在Eden区生成。当新对象生成时,Eden空间申请失败(空间不足),则会发起一次GC(Scavenge GC)。回收时先将Eden区存活的对象复制到一个Survivor0区,然后清空Eden区。当某次回收时Survivor0区存放满时,则将Eden区和Survivor0区存活的对象存放到Survivor1区,然后清空Eden和Survivor0区,清空后将Survivor0区和Survivor1区交换,即保持Survivor1区为空(有争议),如此反复。当Survivor1区不足以存放Eden和Survivor0区的存活对象时,就将存活的对象直接存到老年代。另外,当对象在Survivor区躲过一次GC的话,其年龄就会加1,默认情况下,如果对象的年龄达到15岁,就会直接移动到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收。 - 老年代(Old Generation):
在新生代中经历了N次垃圾回收后仍然存活的对象,就会被放到老年代中。因此,可以人为老年代中存放的都是一些生命周期较长的对象,内存也比新生代大很多(默认时1:2)。老年代满时触发Major即Full GC,一般来说,大对象会被直接分配到老年代,所谓大对象是指需要大量连续存储空间的对象,最常见的就是大数组。 - 永久代(Permanent Generation):
用于存放静态文件(class类、方法)和常量等,永久代对垃圾回收没有显著影响,在JavaSE8特性中已经被移除了,取而代之的是元空间(MetSpace)。
什么样的对象会被当作垃圾回收
- 当一个对象的引用(地址)没有变量去记录的时候,该对象就会成为垃圾对象,并在垃圾回收器空闲的时候对其进行清扫
如何校验对象是否被回收
- 可以重写Object类中的finalize方法,该方法在垃圾回收器执行的时候,被回收器自动调用执行
怎样通知垃圾回收
- 可以调用System类的静态方法gc(),通知垃圾回收器去清理垃圾。(不建议使用,因为JVM有垃圾回收机制自动清理垃圾
代码实现
public class GarbageCollection {
public static void main(String[] args) {
/**
* 问题一:什么样的对象是垃圾对象
* 没有被变量记录的对象
*/
new Demo(); //垃圾对象
Demo d = new Demo(); //不是垃圾对象,有被变量记录
d = null; //这样就变成垃圾对象了
/**
* 问题二:怎么确认垃圾对象被回收了
* Object类中有一个finalize方法,在垃圾处理器工作的时候会被调用,
* 可以重写该方法,当回收垃圾时执行
*/
// 创建一些垃圾对象看是否被清理了没,注意当垃圾量过少时 垃圾处理器会懒得处理
for (int i = 0; i < 10000000; i++) {
new Demo();
}
/**
* 问题三:怎样通知垃圾回收器主动清理垃圾对象
* System类中有静态gc()方法,通知垃圾回收器来干活
*/
for (int i = 0; i < 100; i++) {
new Demo();
System.gc(); //通知垃圾回收器干活
}
}
}
class Demo{
@Override
protected void finalize() throws Throwable {
System.out.println("我是垃圾对象 被清理了!!(~﹃~)~zZ");
}
}
问题扩展
- 怎么判断某个对象是垃圾,即没有变量记录
1、引用计数算法(java中不是使用此方法)
每个对象中添加一个引用计数器,当有别人引用它的时候,计数器就会加1,当变量不引用它时计数器就会减1,当计数器为0的时候对象就可以当成垃圾。算法简单,但是最大问题就是在循环引用的时候不能够正确把对象当成垃圾。示例:
public class GarbageCollection_Math {
public static void main(String[] args) {
Demo_Gc d1 = new Demo_Gc(); //有变量引用计数器加1
Demo_Gc d2 = new Demo_Gc();
d1.demo = d2;
d2.demo = d1; //加1
d1 = null; //减1,但实际d1已经是垃圾对象
d2 = null;
}
}
class Demo_Gc{
public static Object demo;
}
2、根搜索方法
这是JVM一般使用的算法,根搜索算法是从离散数学中的图论导入的。程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点成为了垃圾对象。
3、Java可以作为GC ROOT的对象有:1.虚拟机栈中引用的变量(本地变量表);2.方法去中静态属性引用的对象;3.方法去中常量引用的对象;4.本地方法去中引用的对象(Native对象)
注意事项
- 尽量不要创建很大的对象
原因:GC回收算法从来不对大对象(>=85000字节)堆进行内存压缩整理,在堆中大的内存块会浪费太多CPU时间 - 不要频繁的new生命周期很短的对象
这样频繁垃圾回收频繁压缩有可能会导致很多内存碎片