背景知识:Java内存分布分为以下几个区域
- 本地方法区:(线程私有)native code占用的内存。
- 方法区:存放Class定义,常量,全局变量
- PC计数器:(线程私有)记录当前指令位置
- 栈:(线程私有)对正在运行方法指令,变量值,实例引用进行压栈
- 堆:存放对象实例
GC要解决2个问题
- 计算出需要回收的无用对象
- 如何回收无用对象
问题1:计算出需要回收的无用对象
- 引用计数
- GC Root
- GC Root可以是
- 栈中reference
- java虚拟机栈(栈帧中的本地变量表)中引用的对象
- 本地方法栈中JNI本地方法的引用对象
- 方法区中的reference
- 方法区中的类静态属性
- 方法区中的常量
- 栈中reference
问题2:如何回收无用对象
- 标记-清除
- 将还在用的实例进行标记,然后将没标记的内存全部释放掉
- CMS 老年代 GC 算法
- 缺点是:
- 标记,清除的效率都不高;
- 这将导致不连续的内存碎片,使得后面无法进行大块内存分配,从而导致再次触发GC CMS一大缺点
- 复制
- 缺点:在对象存活率高的情况下,要执行较多的复制操作,效率将会变低.新生代所使用的算法
- 算法假设
§ 以事实依据作为假设:大部分新生代成活率不到10%
§ 新生代的内存分成3块,Eden和2个survivor. 因为新生代对象成活率比较低,根据此假设得出结论:当新生代用需要GC时,所有存活对象可以被一个空白的survivor上容纳。
§ 使用-XX:SurvivorRatio=8来改变eden与survivor的比例
§ 大多数情况下这个假设都可以成立,极端条件下--有内存担保机制
§ 最直接的是请求是MinorGC时,Survivor无法容纳存活新生代,那么由老生代空间担保,新生代被挪入老生代
§ 为了防止上面的情况方式,有间接手段
§ 防止个体太大:如果实例需要的内存大小超过M,则直接分配到老生代
§ 防止累积:新生代超过N次GC都没被回收,将变为老生代。-XX:MaxTenuringThreshold=N.Parallel收集器默认是15.
§ 防止累积:如果suvivor中 同一代的实例在占据了survivor区(稍后讲)的一半以上,则年龄大于等于此代的一同进入老生代.
算法流程
§ 当Eden+Survivor满后,触发minor GC
§ 先进行复制拷:把所有Eden+Survivor中的存活对象全部拷贝到另外一个Survivor中(正常情况)。此时Eden+一个Survivor中的对象一定是可回收的。
§ 再进行内存回收:Eden+此Survivor。
§ minor GC 过程中,新生代对象的使用可以由另外一个Survivor满足。
标记-整理
标记-整理算法在标记-清除算法基础上做了改进,标记阶段是相同的标记出所有需要回收的对象
在标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。
标记-整理算法相比标记-清除算法的优点是内存被整理以后不会产生大量不连续内存碎片问题。
分代GC策略中新生代向老年代升级
- 目的: 保证新生代使用复制GC算法的高效,期望保证每次Minor GC时新生代中的存活对象尽可能的少
- 新生代向老年代转变的条件
- Minor GC时,Survivor无法容纳存活新生代,由老生代空间担保,新生代被挪入老生代。
- 新生代超过N次GC都没被回收,将变为老生代。N可以通过JVM参数指定,-XX:MaxTenuringThreshold=N. Parallel收集器默认是15.
- 如果suvivor中 同一代的实例在占据了survivor区(稍后讲)的一半以上,则年龄大于等于此代的一同进入老生代。
- 如果实例需要的内存大小超过M,则直接分配到老生代。 M可以通过JVM参数指定。-XX:PretenureSizeThreshold=M。
上面介绍的是堆内存的GC,这里介绍一下方法区GC原则
- class所有的实例均被回收。
- class的classloader被回收。
- class没有再被引用,以免使用reflect被初始化。JVM启动参数 -Xnoclassgc 表示不对方法区进行垃圾回收。请谨慎使用.