JVM垃圾回收(GC)模型
垃圾判断算法
引用计数
给对象创建实例时会在堆中为实例分配内存空间并给对象引用设置为1,当有其他引用指向它时+1,当释放时-1,直到引用计数减小到0,该对象的内存就会被释放。
缺点:循环引用无法释放,比如:A引用B,B引用A,但是没有其它引用指向A和B,那么A,B不能使用,应该回收。
根搜索
我们可以把所有引用想象为树结构,脱离树的对象,将视为垃圾。该算法可以很好的解决循环依赖问题。
Java中,GC Roots包括:
在VM栈(帧中的本地变量)中的引用
方法区中的静态引用
JNI(本地方法)中的引用
GC算法
标记-清除算法
效率高,但是容易产生碎片。
为对象分配内存空间是连续的,清除后,不连续的空间造成碎片,内存浪费。
标记-整理算法
不会产生碎片
在标记清除的基础上增加了整理步骤,使得效率变慢,但是空间变得连续。
复制算法
将内存空间划分为三个部分eden,to survivor,from servivor,他们的比例为8:1:1,对象之后存放在eden,from servivor中,会造成10%的内存浪费,每次垃圾回收都会将eden和from servivor中存活的对象复制到to servivor中,然后将to servivor变为from servivor,将from servivor变成to servivor。
分代算法
Java堆分为新生代和老年代,新生代使用复制算法,老年代使用标记清除或者标记整理算法
垃圾回收器的实现和选择
Serial收集器
最早的收集器,单线程进行GC
New和Old Generation都可以使用
在新生代,采用复制算法,老年代采用标记整理算法
因为单线程GC,没有线程的切换的额外开销,简单实用
Hotspot Client模式缺省的收集器
ParNew收集器
ParNew是Serial的多线程版本,其算法,STW,对象分配规则,回收策略等和Serial收集器一摸一样。
在单CPU下ParNew并不比Serial有更好的效果
Parallel Scavenge收集器
采用复制算法的多线程收集器,以吞吐量最大化(GC运行时间占总时间最小)为目标的收集器实现,它允许较长时间的STW换取总吞吐量最大化。
Serial Old收集器
单线程收集器,使用标记整理算法,是老年代收集器
Parallel Old收集器
老年代版本吞吐量优先收集器,使用多线程和标记整理算法,jvm1.6提供;新生代使用PS收集器的话,老年代除Serial Old外别无选择,因为PS无法使用CMS收集器配合工作
CMS收集器
CMS收集器,是以获得最短回收停顿时间为目的,多数应用于互联网站或B/S系统的服务端上
CMS基于标记清除算法实现的;
Initial Mark
该阶段标记直接被GC root应用或被新生代存活对象所引用的对象
Concurrent Mark
根据上阶段找到的root遍历查找。并发标记,与用户应用并发运行
Concurrent Preclean
与应用并发运行,在并发运行中,一些对象的引用可能发生变化,但这种情况发生时,JVM会将这个对象区域(card)标记为Dirty,
这就是Card Marking
在pri-clean阶段,哪些能够从Dirty对象到达的对象也会被标记,这个标记做完,dirty card标记就会被清除
Concurrent Abortable Preclean
与应用并发运行,为了尽量承担STW中最终标记阶段的工作。这 这个阶段持续时间依赖很多的因素,由于这个阶段重复做很多工 知道满足一些条件(重复迭代次数、完成的工作量或者时钟时间 等)
Final Remark
最终标记,属于STW的
Concurrent Sweep
清除不再使用对象,回收它们占用的空间
Concurrent Reset
重置CMS内部的数据结构,为下次GC做准备
GC代码演示
package com.jvm.gc; /** * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 * -verbose:gc打印每次GC信息 * -Xms20M 堆最小20M * -Xmx20M堆最大20M * -Xmn10M新生代10M * -XX:+PrintGCDetails: * -XX:SurvivorRatio=8表示eden:survivor=8:1 */ public class GcTest1 { public static void main(String[] args) { int size = 1024*1024; byte[] bytes1 = new byte[2 * size]; byte[] bytes2 = new byte[2 * size]; byte[] bytes3 = new byte[2 * size]; System.out.println("end"); } }
package com.jvm.gc; /** * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 * 当eden空间不足以存放新对象时,新创建的对象直接进入老年代 */ public class GcTest2 { public static void main(String[] args) { int size = 1024*1024; byte[] bytes1 = new byte[2 * size]; byte[] bytes2 = new byte[2 * size]; byte[] bytes3 = new byte[5 * size]; System.out.println("end"); } }
package com.jvm.gc; /** * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:PretenureSizeThreshold=4194304 -XX:SurvivorRatio=8 -XX:+UseSerialGC * -XX:PretenureSizeThreshold=4194304超过4M的对象直接进入老年区不会出现GC(必须配合UseSerialGC垃圾收集器使用) */ public class GcTest3 { public static void main(String[] args) { int size = 1024*1024; byte[] bytes1 = new byte[2 * size]; byte[] bytes2 = new byte[2 * size]; byte[] bytes3 = new byte[5 * size]; System.out.println("end"); } }
package com.jvm.gc; /** * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails * -XX:+PrintCommandLineFlags -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:+PrintTenuringDistribution * -XX:+PrintCommandLineFlags参数信息 * -XX:MaxTenuringThreshold=5年龄阈值,在可以自动调节晋升到老年代阈值GC中,设置该阈值的最大值 * 该值默认15 * -XX:+PrintTenuringDistribution * */ public class GcTest4 { public static void main(String[] args) { int size = 1024*1024; byte[] bytes1 = new byte[2 * size]; byte[] bytes2 = new byte[2 * size]; byte[] bytes3 = new byte[2 * size]; byte[] bytes4 = new byte[2 * size]; byte[] bytes5 = new byte[2 * size]; byte[] bytes6 = new byte[2 * size]; System.out.println("end"); } }
package com.jvm.gc; /** * -verbose:gc -Xmx200M -Xmn50M -XX:TargetSurvivorRatio=60 -XX:+PrintTenuringDistribution * -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+UseConcMarkSweepGC -XX:+UseParNewGC * -XX:MaxTenuringThreshold=3 * -XX:TargetSurvivorRatio=60当某年龄段数据占用survivor空间60%时,将从小计算threshold值, * threshold值为年龄值到最大值之间 * Desired survivor size 3145728 bytes, new threshold 1 (max 3) * - age 1: 3145776 bytes, 3145776 total * -XX:+PrintTenuringDistribution 打印年龄详细信息 */ public class GcTest5 { public static void main(String[] args) throws InterruptedException { byte[] bytes_1 = new byte[512 * 1024]; byte[] bytes_2 = new byte[512 * 1024]; myGc(); Thread.sleep(1000); System.out.println(11111); myGc(); Thread.sleep(1000); System.out.println(22222); myGc(); Thread.sleep(1000); System.out.println(33333); myGc(); Thread.sleep(1000); System.out.println(44444); byte[] bytes_4 = new byte[1024 * 1024]; byte[] bytes_5 = new byte[1024 * 1024]; byte[] bytes_6 = new byte[1024 * 1024]; myGc(); Thread.sleep(1000); System.out.println(55555); myGc(); Thread.sleep(1000); System.out.println(66666); System.out.println("end"); } private static void myGc() { for (int i = 0; i < 40; i++){ byte[] byteArray = new byte[1024 * 1024]; } } }
package com.jvm.gc; /** * CMS垃圾收集器 * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC */ public class GcTest6 { public static void main(String[] args) throws InterruptedException { int size = 1024 * 1024; byte[] bytes1 = new byte[4 * size]; System.out.println(11111); byte[] bytes2 = new byte[4 * size]; System.out.println(22222); byte[] bytes3 = new byte[4 * size]; System.out.println(33333); byte[] bytes4 = new byte[2 * size]; System.out.println(44444); Thread.sleep(1000); } }