经常有人问:为什么老年代垃圾回收的比新生代要“慢”。
分析的思路是这样的:
首先要分析新生代和老年代垃圾回收具体做哪些事情,粗略的说,做两件事:1、标记存活对象 2、使用垃圾回收算法清除垃圾。
然后分析这两件事情的执行速度上存在差异么?
写了一个程序模拟GCRoots追踪和垃圾收集过程。这样可以更明确的GCRoots追踪的大概过程 。声明:我主要工作使用的语言java,暂时没有能力看JVM的C程序,所以这里的算法不是官方的算法,但我相信思路应该是一样的。
先看执行流程图:
代码实现思路是:从栈帧找到直接关联对象地址,到追踪表中找出直接关联对象引用的对象地址列表,这样就通过两级关系找到了所有存活对象。采用了这种数据结构,追踪的代价与存活对象的多少是成正比的。
package com.yh.stu.jvm.garbage; import java.util.*; /** * 实现一个GCRoots算法 * * @author DSH * @create 2019-08-05-15:17 */ public class GCRootsTest { private static TraceTable traceTable = new TraceTable(); private static StackFrame stackFrame = new StackFrame(); private static Generation newGeneration = new Generation(); private static Generation oldGeneration = new Generation(); public static void main(String[] args) { /** * 在年轻代放入10000个对象 ,存活100个 */ final int totalNewObjAmt = 10_000; for (int i = 0; i < totalNewObjAmt; i++) { StorageStructure objSt = new StorageStructure("#A000-"+i, "A000"+i); newGeneration.add(objSt); } /** * 在老年代代放入10000个对象 ,存活9900个 */ final int totalOldObjAmt = 10_000; for (int i = 0; i < totalOldObjAmt; i++) { StorageStructure objSt = new StorageStructure("#B000-"+i, "A000"+i); oldGeneration.add(objSt); } createRealation(newGeneration, 10); createRealation(oldGeneration, 99); System.out.print("Minor gc cost:"); newGeneration.gc(); System.out.println("=================================================="); System.out.print("Major gc cost:"); oldGeneration.gc(); } /** * 随机建立对象的引用关系 */ private static void createRealation(Generation generation, int childrenPerObj) { for (int i = 0; i < 10; i++) { StorageStructure rootObj = getRandomObjFromGen(generation); stackFrame.addAddr(rootObj.addr); for (int j = 0; j < childrenPerObj; j++) { StorageStructure childObj = getRandomObjFromGen(generation); rootObj.addRefObj(childObj); addTraceTable(rootObj, childObj); } } } private static void addTraceTable(StorageStructure objStRoot, StorageStructure objStChild) { List<String> headList = traceTable.headMap.get(objStRoot.addr); if (headList == null) { headList = new ArrayList<>(); } headList.add(objStChild.addr); traceTable.headMap.put(objStRoot.addr, headList); } private static StorageStructure getRandomObjFromGen(Generation generation) { Random random = new Random(); List<StorageStructure> objStList = generation.getObjStList(); int randomIndex = random.nextInt(objStList.size()); StorageStructure objSt = objStList.get(randomIndex); return objSt; } static class Generation { private List<StorageStructure> objStList = new ArrayList<>(); private Map<String, StorageStructure> map = new HashMap<>(); public void gc() { TimerTools timerTools = new TimerTools(); timerTools.start(); //复制对象,这里用sleep(1)表示复制的时间消耗 int i = 1; for (String rootAddr : stackFrame.getVarAddrList()) { List<String> addrList = traceTable.headMap.get(rootAddr); for (String childAddr : addrList) { if (map.get(childAddr) != null) { try { // Thread.sleep(0,1); Thread.sleep(10); // } catch (InterruptedException e) { e.printStackTrace(); } // System.out.println((i++) + "-" + map.get(childAddr)); } } } System.out.println(timerTools.spent()); } public void add(StorageStructure objSt) { this.objStList.add(objSt); this.map.put(objSt.addr, objSt); } public List<StorageStructure> getObjStList() { return objStList; } } /** * 追踪表 * headMap中存放的key是 stackFrame中的对象地址,value里是存放的是key指定的对象锁关联对象的地址列表 */ static class TraceTable { private Map<String, List<String>> headMap = new HashMap(); } static class StackFrame { private List<String> varAddrList = new ArrayList<>();//存放对象引用地址 /** * 在栈帧中放入对象引用地址 * * @param addr */ public void addAddr(String addr) { this.varAddrList.add(addr); } public List<String> getVarAddrList() { return varAddrList; } } static class StorageStructure { private String addr; private Object obj; private List<StorageStructure> refObjList = new ArrayList<>();//引用对象 public StorageStructure(String addr, Object obj) { this.addr = addr; this.obj = obj; } public void addRefObj(StorageStructure objSt) { refObjList.add(objSt); } @Override public String toString() { return "ObjectStorageStructure{" + "addr='" + addr + '\'' + ", obj=" + obj + '}'; } } }
具体看代码注释,程序其实还有很多效率上可以优化的地方。大家看思路吧!
测试结果:
Minor gc cost:1011 ================================================== Major gc cost:10233
其实不是老年代回收慢,而是老年代存活对象多,追踪和操作更多的对象肯定比追踪和操作相对少的对象花费跟多的时间!!
本文是《从 0 开始带你成为JVM实战高手》内容总结,版权问题,特此声明。详细内容:
如果购买,成功后加QQ群找群主返现10元