什么是垃圾?
类比日常生活中,如果一个东西经常没被使用,那么就可以说是垃圾。
同理,如果一个对象不可能再被引用,那么这个对象就是垃圾,应该被回收。
垃圾:不可能再被引用的对象。
引用计数法因循环引用导致的内存泄漏
在一个对象被引用时加一,被去除引用时减一,这样我们就可以通过判断引用计数是否为零来判断一个对象是否为垃圾。这种方法我们一般称之为「引用计数法」。
- 什么是循环引用?
A 引用了 B,B 引用了 C,C 引用了 A,它们各自的引用计数都为 1。但是它们三个对象却从未被其他对象引用,(假设有1000个对象时,这三个就是垃圾;如果只有4个对象,那么另外一个就是垃圾)只有它们自身互相引用。从垃圾的判断思想来看,它们三个确实是不被其他对象引用的,但是此时它们的引用计数却不为零。
根搜索算法
根搜索算法的基本思路:通过一系列名为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
GC Roots:
所有当前被加载的 Java 类
Java 类的引用类型静态变量
Java类的运行时常量池里的引用类型常量
VM的一些静态数据结构里指向GC堆里的对象的引用
根搜索算法:一种通过遍历的方式判断对象是否可达的垃圾标记算法。
垃圾回收——标记清除算法
适用于存活对象少,垃圾对象多的情况;
它将垃圾回收分为两个阶段:标记阶段和清除阶段。
在标记阶段,标记所有从根节点出发的可达对象。因此,所有未被标记的对象就是未被引用的垃圾对象。
在清除阶段,清除所有未被标记的对象。
问题:产生空间碎片。
垃圾回收——复制算法
适合年轻代。
将内存分为两部分,每次只使用其中一部分。在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
问题:内存折半
垃圾回收——标记压缩算法
适合老年代。
对比于标记清除算法,在清除阶段,它会将所有的存活对象压缩到内存的另一端。之后清理边界之外的所有空间。
这种算法既避免了碎片的产生,又不需要两块相同的内存空间,因此性价比较高。
垃圾回收优化——增量算法
问题背景:Stop the world,在垃圾回收的过程中,应用软件的所有线程都会挂起。如果垃圾回收时间很长,会严重影响用户体验和系统稳定性。
增量算法的基本思想:让垃圾回收线程和应用线程交替执行。
每次只收集一小片区域的内存空间,接着切换应用程序线程。如此往复直到垃圾回收完成。
导致的问题:由于线程之间的切换,会使得垃圾回收成本增高,造成系统吞吐量下降。
“因地制宜”——分代算法
分代算法,就是根据 JVM 内存的不同内存区域,采用不同的垃圾回收算法。
例如对于存活对象少的新生代区域,比较适合采用复制算法。这样只需要复制少量对象,便可完成垃圾回收,并且还不会有内存碎片。
而对于老年代这种存活对象多的区域,比较适合采用标记压缩算法或标记清除算法,这样不需要移动太多的内存对象。
新生代复制算法的升级:
IBM公司的研究表明,在新生代中的对象 98% 是朝生夕死的。
在实际的 JVM 新生代划分中,不是采用等分为两块内存的形式。而是分为:Eden 区域、from 区域、to 区域 这三个区域。
所以在HotSpot虚拟机中,JVM 将内存划分为一块较大的Eden空间和两块较小的Survivor空间,其大小占比是8:1:1。当回收时,将Eden和Survivor中还存活的对象一次性复制到另外一块Survivor空间上,最后清理掉Survivor和刚才用过的Eden空间。