JVM垃圾回收机制之堆的分代回收
前言
前文我们了解了Java的GC机制,对于堆中的对象,JVM采用引用计数和可达性分析两种算法来标记对象是否可以清除,本文中我们还会了解到JVM将对分成了不同的区域,以便于更好的回收对象。
堆的分代
Java的堆是JVM中最大的一块内存区域,主要保存Java中各种类的实例。为了更好的管理堆中各个对象的内存,包括分配内存和回收内存。
JVM将堆分成了几块区域:
新生代(Young)
新生代又分为:
Eden
From Survivor
To Survivor
老年代(Old)
其中新生代占堆的1/3空间,老年代占堆的2/3空间。
而新生代中的Eden占新生代的8/10,From Survivor和To Survivor各占新生代的1/10。
堆模型如图:
从上图中我们可以看出:堆是由新生代和老年代组成,默认情况下,新生代 ( Young ) 与老年代 ( Old ) 的比例为 1:2 。其中,新生代 ( Young ) 又分为 Eden 和 From Survivor 、To Survivor区域。默认情况下,Eden 和from、to的比例为 :8 : 1 : 1 。JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。
堆的GC机制
堆中的GC分为两种:
Minor GC
Full GC
Minor GC发生在新生代,采用的算法是复制算法。
Java中新创建的对象都在新生代中,当对象被判定为死亡时(也就是无法访问),就会被GC回收内存,发生Minor GC时,会将Eden和From Survivor区域中的存活的对象复制到To Survivor区域中,然后将Eden和From survivor区域进行清理。
当一个对象活过了一次Minor GC后,它的年龄就加1,当对象的年龄达到了15时,对象就会被放入老年代。
Full GC发生在老年代,采用的是标记-清除算法。
标记:标记的过程其实就是,遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。
清除:清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。
当程序运行期间,若可以使用的内存被耗尽的时候,GC线程就会被触发并将程序暂停,随后将依旧存活的对象标记一遍,最终再将堆中所有没被标记的对象全部清除掉,接下来便让程序恢复运行。
标记-清除算法存在比较大的缺点:
进行GC时需要暂停应用程序,所以导致用户体验变差
会产生许多不连续的内存空间
所以我们一般会避免出现Full GC。
JVM参数
堆的初始大小、新生代、老年代的大小都可以通过JVM的参数进行配置。
下面是一些常用的JVM参数:
-Xms初始堆大小。如:-Xms256m-Xmx最大堆大小。如:-Xmx512m-Xmn新生代大小。通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor,即 90%-Xss线程的堆栈大小-XX:NewRatio新生代与老年代的比例,如 –XX:NewRatio=2,则新生代占整个堆空间的1/3,老年代占2/3-XX:SurvivorRatio新生代中 Eden 与 Survivor 的比值。默认值为 8。即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10-XX:PermSize永久代(方法区)的初始大小-XX:MaxPermSize永久代(方法区)的最大值-XX:+PrintGCDetails打印 GC 信息
下面是Eclipse的JVM参数配置方法:
Window --- Preferences --- Java --- Installed JREs --- 点击Edit
在Default VM arguments中添加参数:
总结
本文我们学习了JVM堆GC的分代机制,堆分为新生代和老年代,新生代中采用Minor GC,使用的是复制算法,老年代中采用Full GC,使用的是标记-清除算法。