6. 垃圾收集GC
(1)当需要排查各种内存溢出,内存泄漏等问题,当GC成为系统达到更高性能的瓶颈时,我们就需要对这些自动化的GC进行监控和调节。
(2)PC计数器、本地方法栈、虚拟机栈,随方法或者线程的结束而消亡,所以不用考虑回收其内存。内存回收的主要区域是 堆Heap 和 方法区。
(3) 垃圾收集器,在回收对象前需要判断对象是否还存活,判断对象是否存活方法:
a.引用计数算法: 给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就+1;当引用失败时,计数器值就-1。
任何时刻,计数器值为0的对象就是不可能被再使用的。
优点:实现简单,效率高
缺点:很难解决对象之间相互引用的问题。如GcBean里面的问题。(两个在堆上的对象相互引用,但是他们的句柄都指向了null,
已经没有句柄指向他们,所以这两个对象不会再被用到,但是,因为他们之间相互引用,所以不会被垃圾回收。
b.可达性分析算法(Reachability Analysis): 思路,通过一系列称为 "GC Roots"的对象,作为起始点,从这些节点开始向下
搜索,搜索走过的路径称为引用链(Reference Chain),当一个对象到GC Roots 没有任何引用链时,则证明这个对象是不可用的。
可以被垃圾收集器回收的。
----------------------------------------------------------
| ---------- |
| |GC Roots| |
----------------------------------------------------------
|
|
--------- ----------
| obj1 | | obj3 |
--------- ----------
| |
| |
--------- ----------
| obj2 | | obj4 |
--------- ----------
如图obj1和obj2是可用对象,不会被GC回收;
obj3和obj4虽然他们之间有联系,但是,他们到GC Roots 不可达,所以,他们将会判定为不可回收对象。
c.在java中可作为GC Roots的对象的有:
I.虚拟机栈中引用的对象。
II.方法区中类静态属性引用的值。
III.方法区中常量引用的对象
IV.本地方法栈中 JNI(即一般说的native方法) 引用的对象。
(4)回收堆
I. 对象在经过可达性分析后,发现没有与GC Roots相关联的引用链,它将会被第一次标记(标记此对象没有引用链),并且进行一次筛选,此对象是否有必要执行finalize()方法。
II. 筛选的条件是: 此对象是否有必要执行finalize()方法,
当对象没有覆盖finalize()方法,或者对象的finalize()方法被虚拟机掉用过,虚拟机将这两种情况都视为,没有必要执行。
III. 如果对象被虚拟机判定有必要执行finalize()方法时,那么对象会被放置在一个叫做F-QUEUE的队列当中,稍后会有一个虚拟机
建立的,优先级低的Finalizer线程去执行它。
IV. finalize()方法,是对象逃脱被回收的最后一次机会,如果对象在finalize方法中重新与GC Roots上的任何一个对象关联上,
那么GC在第二次标记时,将把这个对象移除“即将回收集合”;如果对象这是还没有逃脱,那它就基本被回收了。
V. 需要注意一点,每个对象的finalize()方法只会被JVM执行一次,如果对象通过finalize()方法逃出过被回收一次,那么就不会
有第二次。
finalize()应尽量避免使用,运行代价高昂,不确定性大,无法保证各个对象的调用顺序。
(5)回收方法区
永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。
I.常量池中有(类、接口、方法、字段的符号引用,字符),判断一个常量是否是废弃常量比较简单,有没有对象引用常量,如果没有其他地方引用
了这个常量,如果这是发生内存回收,而且必要的话,这个常量就会被回收。
II.无用的类:
a. 加载类的ClassLoader已经被回收
b. 该类的所有实例都已经被回收,即java堆中不存在该类的任何实例。
c.无法在任何地方通过反射访问该类。
满足这三个条件,类就可以被回收。Hotspot虚拟机,提供, -Xnoclassgc 参数控制。
(6)垃圾收集算法
I. 标记-清除(Mark-Sweep)算法:
a.标记出所有需要被回收的对象
b.标记完成后,统一回收被标记的对象。
缺点: 效率问题,标记和清除两个过程效率都不高。
空间问题,标记清除之后会产生大量不连续的内存碎片,可能会导致以后要分配大内存对象时,无法找到足够的连续内存,而不得不
触发另一次垃圾收集动作。
II. 复制(Copying)算法:HotSpot虚拟机
a. 将用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块内存用完了,就将还存活的对象复制到另外一块内存,然后把
使用过的空间,一次性清除掉。
优点:实现简单,运行高效
缺点: 内存缩小成原来的一般,影响性能。
b. 新生代中的对象98%是 朝生夕死,所以不需要按照1:1的比例来划分内存空间。而是将 新生代内存 分为一块较大的Eden空间和两块较小的Suvivor空间,
每次使用Eden和其中一块的Survivor;
当回收时,将Eden区和Survivoe区的存活对象,一次性复制到另外一块Survivor区,清理Eden区,和刚才用过的Survivor空间。
Eden区和Survivor区占比:Eden:S1:S2 = 8:1:1 , 即每次新生代中可用内存空间为整个内存空间的 (1+8 ):1 = 90% ,
c.如果Survivor区空间不够用时,需要依赖 老年代 进行分配担保机制,把对象分配到老年代。
III. 标记-整理 算法:
a.标记出所有需要被回收的对象
b.标记完成后,让所有的存活对象向一端移动,然后清理掉边界意外的内存区域。
IV. 分代收集(Generation Collection) 算法
分代收集没有新东西,根据对象存活周期不同,把对象分为几部分。
a. 把java堆分为老年代和新生代,新生代使用复制算法(Eden , Suvivor1 , Suvivor2 ),
b.老年代因为对象存活率高,没有更多的内存使用复制算法收集,所以,使用 标记-清除,或者使用 标记-整理算法 进行垃圾回收。
(7) 垃圾收集器
I.http://blogs.sun.com
Serial ParNew CMS Serial Old GI ..
(8) Full GC 和 Minor GC
I.新生代GC ,Minor GC ,指发生在新生代的垃圾收集动作,因为java对象大多都是朝生夕死,所以Monor GC非常频繁,一般回收速度也比较快。
II.老年代GC ,FULL GC ,发生在老年代的GC ,一般比新生代GC慢十倍以上。
(9) 内存分配策略:
I.对象优先在Eden区分配。
II.大对象直接进入老年代(很长的字符串,或者数组byte[]),老年代垃圾回收比较慢,所以应尽量少使用大对象(还有短大对象)。
-XX:PretenureSizeThreshold ,参数,大于此参数值的对象进入老年代。
III.长期存活的对象进入老年代。