GC 问题侧重点

虚拟机栈、本地方法栈、程序计数器随线程而生,随线程而灭。栈中的栈帧随方法的进入和退出而有条不紊地执行着出栈与入栈操作,每个栈帧分配多少内存基本是在类结构确定下来时就已知了(尽管运行期会有JIT编译器进行一些优化)。所以这几个区域的内存分配和回收都具备确定性,不用过多地考虑回收问题,方法结束或线程结束,内存就自然随之回收了,而Java堆和方法区则不一样,我们只有在程序的运行期才知道需要创建哪些对象,这部分内存的分配和回收都是动态的,GC关注的也是这部分内存。

判定对象存活算法:

  • 引用计数器(Java没有使用该算法),当有一个地方引用它时,就加1,引用失效就减1,任何时刻计数器为0即是死亡对象,使用者有:flashplayer,Python语言, Squirrel。缺点:很难解决对象之间的相互引用问题。

  • 根搜索算法,通过一系列名为“GC Roots”的对象作为起始点,从这些节点向下搜索,所走路径叫做引用链,当一个对象到GC Roots没有任何引用链相连时(即从GC Roots到 对象不可达),该对象即是死的。(可作为GC Roots的对象有:虚拟机栈引用的对象,方法区类静态属性引用的对象,方法区常量引用的对象,本地方法栈中JNI引用的对象)

引用

  • 强引用:代码中普遍存在的,类如:Object obj=new Object();,只要强引用存在,GC就永远不会回收它;

  • 软引用:系统要发生内存溢出异常时,将会把这类对象放入回收范围,并进行第二次回收;

  • 弱引用:该类对象只能生存到下一次GC发生前;

  • 虚引用:对象是否有虚引用存在,不会影响其生存时间,也无法通过虚引用获得一个对象实例,只是用来在这个对象被回收时得到一个系统通知而已;

finalize()方法

在根搜索算法中不可达的对象,也并非是“非死不可”的,这时是处于一个“缓刑”阶段,对象真正死亡需要经历两次标记过程,如果根搜索算法不可达,会被标记一次,第二次标记才会死亡,第一次标记后,会看这个对象是否可以执行finalize方法(如果对象没有覆盖finalize方法或者之前已被虚拟机调用过则不可再执行finalize方法,即该方法只能被执行一次,下一次回收,该方法将不会被执行了)。若不可执行,只能等死,若可以执行,则会被放入一个F-Queue队列中,如果对象能在这时段重新与引用链上的任意对象建立关联,则成功拯救自己,否则,GC稍后会对F-Queue的对象进行二次小规模标记,如果没有关联上,则就真的离死不远了。
try-finaly或其他方式可能会比finalize方法做得更好,所以,不推荐使用这个方法。

Minor GC与Full GC

  • 新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都是朝生夕灭的特性,所以Minor GC非常频繁,回收速度快。

  • 老年代GC(Major GC/Full GC):指发生在老年代的GC,出现了Major GC,大多会伴随至少一次的Minor GC,速度比Minor GC慢10倍以上

设置Eden与Survivor大小比值

-XX:SurvivorRatio参数设置Eden与survivor大小比值,而这里的survivor指survivor区域的一半,即=from space(survivor0)=to space(survivor1),故如设置-XX:SurvivorRatio=8,则Eden=8,from space=1,to space=1。

新生代中,一般情况下,from space(survivor0)与to space(suivivor1)大小相等,便于GC的时候相互复制。

输出GC日志

-XX:+PrintGCDetails输出GC日志。

源自:《深入理解Java虚拟机:JVM高级特性与最佳实践》