JVM垃圾回收
一、Java对象分代管理
分为两代:新生代与老生代。新生代与老年代的内存大小比例为1:2。
1.新生代
新生代分为两大区:伊甸区和幸存区。幸存区又分为From区和To区。
伊甸区、From区和To区的比例为:8:1:1。
新生代存放着所有新生成的对象。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
新生代回收的频率很高,每次回收的时间都很短。
2.老年代
老年代没有分具体分区,整个区就是老年代本身。
在新生代中经历了多次垃圾回收仍然存活的对象,将放入老年代, 放的都是一些生命周期较长的对象 。因为对象存活率高,没有额外空间对它进行分配担保(如果有空间,通过分配担保机制进入老年代),就必须使用“标记一清理”或者“标记一整理”算法来进行回收。
老年代回收的频率低,但是需要消耗很多的时间。
3.Minor GC(新生代GC)
指的是发生在新生代的垃圾收集。因为Java对象大多都具备朝生夕灭的特性,因此Minor GC(采⽤用复制算法)非常频繁,一般回收速度也比较快。
4.Full GC(老年代GC、Major GC)
指发生在老年代的垃圾收集。出现了Full GC,经常会伴随至少一次的Minor GC(并非绝对,在Parallel Scavenge收集器器中就有直接进行Full GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。
为什么要分新生代、老年代呢?
在java中,有的对象需要长时间引用,长时间引用的对象放在老年代区。用完了就可以丢弃的对象就放在新生代区。这样能针对对象不同的生命周期特点,进行不同的垃圾回收策略
二、如何判断一个对象可以被回收?
1.引用计数法
对于某个对象而言,只要应用程序中持有该对象的引用,就说明该对象不是垃圾,如果一个对象没有任何指针对其引用,它就是垃圾。
弊端: 如果AB相互持有引用,导致永远不能被回收。图中这种情况,会导致垃圾回收器无法回收,因为A、B对象引用计数都为1,不具备回收条件。
2.可达性分析
首先要确定一系列根对象(GC Root:肯定不能被当作垃圾回收的对象),在垃圾回收之前,我们会在堆内存中扫描一遍,判断里面的对象是不是被根对象直接或者间接的引用,如果是,那么这个对象就不能被回收,反之就可以作为垃圾被回收掉。
Java中可以被作为GC Root中的对象有:
(1)虚拟机栈中的引用的对象。
(2)方法区中的类静态属性引用的对象。
(3)方法区中的常量引用的对象。
(4)本地方法栈(jni)即一般说的Native的引用对象。
三、垃圾回收算法
1.标记清除算法(Mark Sweep)
算法过程为:标记–>清除
步骤:
蓝色区域:不能被回收的对象区域;
灰色区域:需要被回收的对象区域;
(1)标记出图中蓝色区域;
(2)将没被标记的区域(灰色区域)记录它们的起始与结束地址,清除里面的要被回收的垃圾对象,并标记为闲置空间;
优点:算法相对简单,对标记可回收对象少的区域GC效率高。
缺点:会产生内存碎片、若可回收对象多则效率低。
这种算法存活下来的对象较多。
2.标记整理算法(Mark Compact)
算法过程为:标记–>清除–>整理
步骤:
蓝色区域:不能被回收的对象区域;
灰色区域:需要被回收的对象区域;
(1)标记出图中蓝色区域;
(2)将没被标记的区域(灰色区域)记录它们的起始与结束地址,清除里面的要被回收的垃圾对象,并标记为闲置空间;
(3)由于蓝色区域与蓝色区域之间存在内存缝隙(不连续),所以会进行内存整理操作,将蓝色区域整理为连续的内存块。
优点:没有产生内存碎片 、对存活对象少的区域GC效率高。
缺点:内存空间浪费、需要占用双倍内存空间,若内存对象多的区域GC效率低。
这种算法存活下来的对象较多。
3.复制算法(Copy)
算法过程为:标记–>复制–>清空–>转换
步骤:
蓝色区域:不能被回收的对象区域;
灰色区域:需要被回收的对象区域
(1)标记出图中蓝色区域;
(2)将蓝色区域从From区转移到To区,连接成连续的存储块;
(3)清理From区里的灰色区域(清除需要被回收的垃圾对象);
(4)再将To区中的蓝色区域复制到From区,等待下一次回收;
优点:没有产生内存碎片 、对存活对象少的区域GC效率高。
缺点:内存空间浪费、需要占用双倍内存空间,若内存对象多的区域GC效率低。
这种算法存活下来的对象较少。