JVM的内存主要分两大区域:线程独享区和线程共享区。线程独享区还可以细化三块,那就是程序计数器、虚拟机栈和本地方法栈。程序计数器其实就是存放我们当前线程所执行到的字节码的位置的一个标识,这各区域我们开发人员不会操作到,所以这个区域也没有内存溢出之类的情况。虚拟机栈就是为虚拟机中的java方法服务的,存放栈帧信息。栈帧信息中包含局部变量表和返回值地址等信息。每个方法在执行的时候都会创建栈帧,在方法执行完毕后栈帧随之销毁。本地方法栈与虚拟机栈唯一的区别就是本地方法栈中存放的是本地方法,也就是被native修饰的非java方法。线程共享区也可以细化为两块,堆区和方法区。堆区主要存放的就是我们new出来的对象实例,这个区域是我们在日常编码工作中经常触碰的区域,也是垃圾回收的主要场所。方法区存储的是虚拟机加载类的信息,例如类的属性、方法、接口、常量、静态变量以及编译之后的代码。
垃圾回收机制涉及到的算法
首先在判定一个对象是否为可回收对象的时候,涉及到了引用计数法和可达性分析算法。
引用计数法是给每个对象都添加一个引用计数器,当有地方引用此对象的时候,引用计数器的值+1,若对此对象的引用释放的时候,引用计数器的值-1,这样当引用计数器的值为0的时候就可以将此对象判定为可回收对象。但是这种方法存在问题,例如当两个对象除彼此引用外并无其他引用的时候,两个对象就可以视为可回收对象,但是此时两个对象的引用计数器的值都是1,也就永远不会被判定为可回收对象。
可达性分析算法可以避免引用计数器的缺点,它通过一系列“GC Roots”对象作为起点进行搜索,搜索走过路径看作“引用链”,如果一个对象和“GC Roots” 之间没有可达的路径,那么就被标记为不可达对象,但是不会立即被判定为可回收对象。而是至少有两次被标记为不可达对象后才会被判定为可回收对象。由于垃圾回收只要针对的区域是堆区,方法区和栈区不被GC所管理,因而选择这些区域的对象作为”GC Roots”,例如栈帧中局部变量表引用的对象、方法区中类的静态属性引用的对象。被”GC Roots”引用的对象不被GC回收。
其次在对已经被判定为垃圾的对象进行回收的时候,涉及到了标记-清除算法、复制算法、标记-整理算法、分代收集算法。
标记-清除算法是用于新生代垃圾回收的算法,是将可达性分析算法标识的可回收对象进行回收,但是这一算法回收后内存中会出现大量不连续的可用空间,导致分配大对象的时候可能出现空间不足问题。
复制算法也是用于新生代垃圾回收的算法,我们知道垃圾回收的时候会将堆内存继续划分新生代和老年代,新生代可以继续划分Eden区和SurvivorA和SurvivorB区,复制算法的工作过程是先将Eden区和SurvivorA区中继续存活的对象复制到SurvivorB区,然后清空Eden区和SurvivorA区。下次垃圾回收的时候将Eden区和SurvivoB区中继续存活的对象复制到SurvivorA区,然后清空Eden区和SurvivorB区。对象每经历一次复制,年龄都会+1,默认情况下当年龄达到15的时候,这个对象就可以进入老年代。这个15有我们也可以通过修改MaxTenuringThreshold属性值来进行修改。
标记-整理算法是用于老年代垃圾回收的算法,与标记-清除算法相似,只是在中间多了一步整理的操作,我们可以想象为把老年代分为两块A、B。A中存放不可回收对象,B中存放可回收对象,当A中产生可回收对象的时候,就将这个对象移入B中,当B中出现不可回收对象的时候,就将这个对象移入A中。这样一来,每次垃圾回收只清空B区域中的对象就可以了。
分代收集算法就是结合了复制算法和标记-整理算法,根据不同的分代,选择不同的回收算法。
垃圾收集器
Parallel Scavenge是新生代垃圾收集器,使用复制算法,工作原理是将用户线程停止,多个垃圾回收线程同时执行,执行完毕后恢复用户线程。但是可以指定垃圾回收的时长,从而达到一定的吞吐量(即CPU用于运行用户代码的时间与CPU消耗的总时间的比值)。吞吐量 = 执行用户代码时间 / (执行用户代码时间 + 垃圾回收占用时间)
Serial Old是老年代垃圾收集器,使用标记-整理算法,工作原理是将用户线程停止,单个垃圾回收线程同时执行,执行完毕后恢复用户线程。
G1是清理整个堆空间的垃圾收集器,不区分新生代和老年代,它在垃圾收集的时候共有四个步骤:初始标记、并发标记、最终标记、筛选回收。其中除了初始标记阶段,其他阶段均可以设置为并发或者并行执行。
内存分配
1、对象优先会被分配到新生代的Eden区。
2、大对象直接分配到老年代,多大可以看作大对象也可以通过我们手动设置。长期存活的对象进入老年代,之前说的复制算法,对象每被复制一次年龄都会+1,当达到某一峰值的时候就可以定义为长期存活的对象,被转移到老年代。
3、动态年龄判断,不是对象年龄必须达到我们设置的峰值的时候才会进入老年代,当Survivor区中相同年龄的对象总大小已经超过Survivor区总大小的一半,那么大于该年龄的对象就可以直接进入老年代了。
4、空间分配担保,当新生代中内存不足的时候,可以向老年代来借用内存。所以一每次新生代垃圾回收的时候,都会验证老年代的连续可用空间是否大于新生代所有对象的总空间。若大于,则证明此次垃圾回收是安全的,直接执行。若不大于则证明此次垃圾回收是不安全的。当不安全的时候,虚拟机会检测是否允许空间担保失败,若不允许则进行Full GC。若允许虚拟机会继续检测老年代的连续可用空间是否大于历次晋升到老年代的对象平均大小,若不大于免责执行Full GC,若大于,则执行新生代垃圾回收。