在《深入理解Java虚拟机》第三章中,作者提到了垃圾收集器与内存分配策略。Java的内存垃圾回收,方法论好懂,但是涉及到垃圾收集器的具体实现则比较复杂。博主也看得头大。所以呢,我先梳理下大概的知识点,给大家直观的印象。
本文大概阐述这几点:对象存活检测算法,引用类别,垃圾收集算法,几种垃圾收集器实现,内存分配与回收策略。
1. 对象存活检测
- 引用计数法
很多初学者都以为JVM的对象回收与否的判断靠的是一个对象引用计数器。客观来说,引用计数法效率高,实现简单,但是很难解决对象循环引用的问题。所以主流的JVM实现中,并没有采用此算法。 - 可达性分析法
以图的形式组织各个对象节点,以“GC Roots“作为对象起始点,从这些节点往下搜索,如果某些节点不可达,则这些不可达节点可以被回收。
如何判断对象能否被回收,关键在于准确判断对象是否仍被引用。那怎样才算一个对象被引用呢?且看对象的引用类别
2. 对象引用类别
早期的Java对象引用定义成:reference类型的数据中存储的数值代表另一块内存的起始地址,则称reference为一个引用。现在的Java把引用分为四类:
- 强引用
常见的对象创建方式对应的都是强引用,只要强引用在,内存就不被回收 - 软引用
利用SoftReference创建的引用,软引用关联的对象,在系统即将内存溢出时,会将他们进行回收。 - 弱引用
利用WeakReference创建的引用,弱引用关联的对象只要当前系统进行垃圾回收,弱引用对象就会被回收。 - 虚引用
虚引用就是个幌子,虚引用是否存在,对象完全不受影响,无法通过虚引用使用对象实例。虚引用存在的意义在于对象被回收时收到系统通知。
3.垃圾收集算法
只讲方法论的时候,总还是友好的。。。大概有四种算法:标记-清除算法,复制算法,标记-整理算法和分代收集算法。后面三种算法都是相对第一种的改进。
- 标记-清除算法
算法分标记和清除两个阶段,标记出需要回收的对象,然后统一回收。优点是简单,缺点是效率不高、回收后产生很多碎片空间,不方便利用。 - 复制算法
为了加快效率,复制算法将内存分为两部分,每次使用其中一块,当快用完时,把内存块中的存活对象复制到另一块内存,然后把当前内存块清空。算法的优点是简单,时间效率高,但是空间利用就太铺张了!目前的商业JVM均采用这种方式回收堆的新生代区(可参见博主的上篇文章, Java内存区域及溢出),新生代区的对象回收率很高,每次使用新生代的Eden空间和一块Survivor空间,回收的时候将存活的对象复制到另一块Survivor空间,然后清除掉之前的Eden、Survivor空间。在新生代中,复制算法的优点比较明显,效率高,空间使用率也不错。然而在对象存活率高的老年代中,复制次数多且需要的额外空间担保多,所以提出了下面的标记-整理法。 - 标记-整理法
标记-整理法与标记-清理法类似,都是先标记,但是标记完后,整理法将存活的对象向一端移动,清理掉其他区域的对象。该方法一般用在堆的老年代区。 - 分代收集法
分代收集法不算是一种具体的垃圾回收算法,是一种思路。根据对象的存活中期将堆分成新生代和老年代,新生代采用复制法,老年代采用标记-整理法。分代收集法在现代商业JVM中广泛使用。
4. 垃圾收集器
上面说完了垃圾回收的算法,垃圾回收器就是具体的实现了。多数厂商会采用组合的方式给出堆各个年代的收集器。JDK1.7Update 14之后的HotSpot虚拟机包含的收集器如下图所示:
7种分别作用于各自代的收集器,连线表示可以搭配使用完成整个堆的垃圾收集工作。这7种收集器各有优劣,想详细了解的同学可以参考这篇博文,深入理解JVM(5) : Java垃圾收集器,详细的比较分析。
5.内存分配与回收策略
对象的内存分配主要是往堆上分配,JNI编程也可以分配在栈上。堆上的新生代Eden区是大多数对象的落脚点,在启动了本地线程分配缓冲的时候,按线程优先在TLAB上分配,少数情况会分配在老年代。分配的细节由垃圾收集器的组合方式以及JVM内存设置有关。几条普遍的内存分配规则是:
- 对象优先在新生代Eden区分配
- 大对象直接在老年代分配,大对象指的是需要大量连续内存的对象,比如长字符串、数组等。要避免创建朝生夕灭的短命大对象
- 长期存活的对象将进入老年代
堆中的GC可以分为新生代GC(Minor GC)和老年代GC(Major GC/Full GC)两种。新生代GC,频率高,速度快;老年代GC一般会伴随一次新生代GC,GC耗时长。发生Minor GC前,会检查老年代最大连续可用内存空间大小是否大于新生代对象占用总大小,以保证GC安全,如果不符合可能要执行Full GC。当然安全与否的检查标准不只这一种,这里不在赘述。
6.总结
内存回收与垃圾收集器是影响系统性能与并发的主要因素之一。JVM提供多种垃圾回收器以及调参参数,方便用户根据需求进行设置以达到最佳性能。Java内存回收,迈出了第一步,后面还有很多要学,加油!
很惭愧,做了一点微小的贡献!