
目录一、内存分配原则1.对象优先在Eden区分配2.需要大量连续内存空间的对象直接分配到Tenured区3.长期存活的对象转移到Tenured区4.内存分配过程4.1 JDK9 G14.2 JDK8 CMS二、内存回收原则1.GC类型2.死亡对象判断方法2.1 引用计数法2.2 可达性分析算法3.垃圾收集算法3.1 标记-清除算法3.2 标记-复制算法4.内存回收过程4.1 三色标计法4.2 CMS4.3 G1一、内存分配原则1.对象优先在Eden区分配对象首先在Eden区分配内存空间。当Eden区没有足够空间进行分配时JVM触发Minor GC/Young GC将Eden区中所有存活对象的对象头部记录年龄的4bit位1并移动到S1区。将S0区的存活对象年龄1如果年龄达到1111将对象移动到Tenured区否则移动到S1区。如果S0区中相同年龄的所有对象大小的总和超过了S0空间的一半那么年龄大于或等于该年龄的对象会移动到Tenured区。此时Eden和S0被清空将S0和S1身份互换下次GC移动到S0。为新对象在Eden区分配内存空间初始化年龄为0000。如果S1区在Minor GC过程中空间不足JVM会采用分配担保机制将超出S1容量的对象直接移动到Tenured区。如果Tenured区空间也不足就会触发Full GC。这里可能会问为什么要有Eden、S0、S1直接用Eden不行吗首先这是一种垃圾回收算法分区机制是考虑到将存活的对象转移到新的连续内存空间中保证死亡对象回收后不会有内存碎片问题导致空间不足。其次这种转移方式是为了统一更新存活对象的年龄在转移时更新避免遗漏。2.需要大量连续内存空间的对象直接分配到Tenured区大对象直接进入Tenured区是为了避免大对象在S0\S1区之间来回移动的成本、避免占用太大的S1区内存、大对象需要连续空间会导致S1区内存碎片降低存储效率。3.长期存活的对象转移到Tenured区对象在S0\S1区每熬过一次Minor GC/Young GC对象的年龄就增加1岁当它的年龄增加到一定程度默认为 15 岁就会被移动到Tenured区中。4.内存分配过程4.1 JDK9 G1类加载后G1采用页式离散管理可变分区连续管理基于-XX:G1HeapRegionSize将堆内存划分为大小相同的Page。JVM按照地址升序页号页内地址[TLAB号内地址]维护一个全局空闲分区链表。为了解决多线程并发的效率问题JVM的思路是为每个线程预分配一块堆空间TLAB线程实例化对象时直接在TLAB低地址划分一块连续内存空间给当前对象当前线程的后续对象依次在该TLAB中分配直到该TLAB满了。当该线程的TLAB空间不足时从最近之前的肯定都分配满了状态为Eden的Page中CAS修改分配指针每个Page页内地址从0x0000开始取一块连续的TLAB给当前线程。当Eden的Page空间不足时使用首次适应算法低地址开始分配从空闲分区链表获取一个状态Free的Page使用CAS标记为Eden所以各代由一组非连续的Page构成且各代空间占用动态变化。如果空闲分区链表为空那么触发Young GC。对于内存占用超过Page/2的大对象直接分配到Tenured区遍历空闲分区链表获取一个或多个连续的状态为Free的Page标记为Humongous不同的是Page内即使有空闲空间也不会再被使用。4.2 JDK8 CMSCMS的Eden区和Tenured区都是一片连续的存储空间各代的空间占用固定-XX:SurvivorRatio。Eden区维护一大片连续的存储空间每个线程还是私有TLAB当TLAB不足时在Eden区低地址部分CAS修改分配指针分配连续空间。Tenured区维护空闲列表对于-XX:PretenureSizeThreshold的大对象JVM使用首次适应/最佳适应算法找到一个足够大的、连续的内存块来存放对象。二、内存回收原则1.GC类型Minor GCEden内存区空间不足时触发只清理Eden和S0、S1中的对象。Major GC当Tenured内存区空间不足时触发只清理Tenured中的对象。Full GC当方法区、Tenured内存区空间不足时触发清理整个堆空间Eden、S0、S1、方法区Metaspace2.死亡对象判断方法2.1 引用计数法给对象头中添加一个引用计数器每当该对象被引用计数器就加 1当引用失效计数器就减 1计数器为 0 的对象默认为死亡。这种方法的缺点是当多个对象相互引用但是这些对象除了相互引用外没有被使用此时GC就无法回收。voidtest(){NodeanewNode();// 对象1NodebnewNode();// 对象2a.nextb;b.nexta;// 方法结束对象1和对象2就用不到了但是由于他们相互引用GC无法回收他们}2.2 可达性分析算法将活跃栈帧本地变量表中的引用、方法区中引用类型的静态变量作为GC Roots每个GC Roots可以引出一棵树结构在JVM堆中包括字符串常量池不在任何一棵树下的对象被定义为死亡等待下次GC死亡对象的内存空间就会被释放。Java 引用强引用、软引用、弱引用大白话就是只要方法中用到的堆对象就不会被回收前提是强引用方法执行完了实例化的对象就没用了那就可以回收了。voidtest(){NodeanewNode();// 对象1NodebnewNode();// 对象2a.nextb;b.nexta;// 方法结束栈帧被释放// 虽然对象1和对象2互相引用但没有GC Roots指向它们// 结果对象1和对象2都不可达都会被回收}对于方法区中的类判定为死亡需要满足三个条件该类所有在堆中的实例都已经被回收。加载该类的ClassLoader已经被回收。内存中没有使用反射访问该类。3.垃圾收集算法3.1 标记-清除算法CMS垃圾回收器在老年代用的就是标记清除算法老年代是一块固定的连续存储空间垃圾回收后通过维护空闲列表来记录可用内存。标记清除算法首先会标记存活的对象统一回收所有没有被标记的对象缺点是内存碎片问题严重。3.2 标记-复制算法CMS垃圾回收器在青年代用的就是标记复制算法Eden和Survivor是固定的连续存储空间分别维护一个碰撞指针每次从低地址分配内存标记复制算法将存活对象移动到Survivor低地址便于维护碰撞指针。将内存分为大小相同的两块EdenSurvivor[P0、P1]只向Eden和P0中存数据执行垃圾回收时将存活的对象全部转移到P1内存中清空EdenP0的很好的解决了内存碎片问题。G1垃圾回收器基于标记复制算法将多个Page中所有存活的对象移动到新的空闲Page中并标记为Survivor将原Page放回空闲链表。4.内存回收过程4.1 三色标计法STW在基于可达性分析标记垃圾时会阻塞应用程序的所有线程保证标记完整一边扫地一边嗑瓜子。三色标记法实现了并发标记不影响应用线程的执行。三色标计法在标记时会基于上次标记的结果树只修改发生变化的子树减少了重复的扫描。为每个对象涂色每次标记只扫描灰色集合中的对象并扫描其子树白色对象没有被标记过灰色对象已经被标记过了但该对象下还有部分属性没被标记黑色对象已经被标记过了且对象下的属性也被标记过了因为标记是在并发条件下的当本轮标记后-准备清除前死亡对象又被重新引用了就会导致对象被错误的回收但是CMS和G1已经解决了这个问题。4.2 CMS初始标记单线程并Stop the World标记GC Roots能直达的对象。并发标记无需中断和用户线程同时运行三色标记法从GC Roots直达对象开始遍历整个对象树。重新标记多线程并STW标记并发标记阶段引用发生变化的对象扫描三色标记法灰色集合。并发清除和用户线程同时运行清理标记为死亡的对象。筛选回收STW老年代执行清除算法年轻代执行复制算法。4.3 G1获取对应区域的Page集合初始标记单线程并Stop the World标记GC Roots能直达的对象。并发标记无需中断和用户线程同时运行三色标记法从GC Roots直达对象开始遍历整个对象树。重新标记多线程并STW标记并发标记阶段引用发生变化的对象扫描三色标记法灰色集合防止对象被。并发清除和用户线程同时运行清理标记为死亡的对象。筛选回收STW执行复制算法选择多个Page构成回收集执行复制算法把回收集中的对象复制到新的Page中标记为Survivor将回收集的Page清空并放回空闲链表。