最近通过看书系统的了解了JVM的GC原理,发现之前自己很多地方理解有偏差,不够详细。之前写过一篇JVM垃圾回收机制的文章,写的太乱了不想再后面加了,所以重开一篇。这篇文章主要介绍 基本概念和JVM的对象存活判定 。
JVM会自动给我们分配内存和释放内存,简单来说,jvm释放内存的过程就是垃圾回收。这里需要注意的是:JVM回收的是“垃圾”对象所占用的内存。
当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。
给对象添加一个引用计数器,当有一个地方引用该对象时,计数器值加一;当引用失效时,计数器值就减一。当计数器为零时,表示该对象没有在任何地方被引用,则该对象可以判定为可回收对象。
无法解决循环引用的问题。当两个对象互相引用时,则永远不会被回收。
在主流语言的主流实现中,都是通过可达性算法来判定对象是否存活。
以被称为“GC Roots”的对象为起点,从这些节点向下搜索,搜索的路径被称为引用链。 当对象没有任何的引用链与“GC ROOTS”相连 ,则该对象则被判定为可回收对象。
如图,对象object 5、object 6、object 7虽然互相有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象。
在java语言中,可作为GC Roots的对象包括下面几种:
从上面的判定算法可以看出,我们看重的是对象引用。实际上在JVM中,对象引用不仅仅是有或无两种形式。无论是哪种判定对象存活的方法,根本是在判定“引用”是否存在。java有四种引用:强引用、软引用、弱引用和虚引用。
名称 | 定义 | 回收情况 |
---|---|---|
强引用 | 一般使用的引用,比如new出来的对象,即“Object object = new Object()” | 在对象没有被根引用链接的时候被回收 |
软引用 | 有用但非必须的对象,比如一些缓存对象 | 在 系统将要发生内存溢出前 ,才会将这些引用列入回收范围,进行一次回收操作。在这次回收之后,系统仍然还没有足够的内存,才会抛出内存溢出异常 |
弱引用 | 用来描述非必须对象,强度低于软引用 | 只存活到下一次垃圾回收之前 ,当垃圾回收开始,无论内存是否足够,都会回收 |
虚引用 | 为一个对象设置虚引用关联的唯一目的就是 能在这个对象被收集器回收时收到一条系统通知 。 | 一个对象有无虚引用,不影响他生存时间;通过一个虚引用也无法获取一个对象实例 |
finalize方法中,可将待回收对象赋值给GC Roots可达的对象引用,从而达到对象再生的目的
需要注意的是 ,任何一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临下一次回收,它的finalize()方法不会被再次执行,即对象只能自救一次。
这里说的方法区,在hotspot中叫做“永久代”,java虚拟机规范中不要求一定要实现方法区的垃圾回收。原因主要是由于方法区回收的性价比太低。
以常量池中字面量的回收为例,假如一个字符串"abc"已经进入了常量池中,没有任何String对象引用常量池中的"abc"常量,也没有其他地方引用了这个字面量,这个"abc"常量就会被系统清理出常量池
同时满足下面三个条件可以回收:
需要注意的是,这里说的是“可以回收”,而不是一定回收。类的回收和对象的回收不一样的地方就在此。是否对类进行回收,HotSpot虚拟机提供了-Xnoclassgc参数进行控制。
还可以使用-verbose:class以及-XX:+TraceClassLoading、
-XX:+TraceClassUnLoading查看类加载和卸载信息,其中-verbose:class和-XX:+TraceClassLoading可以在Product版的虚拟机中使用,-XX:+TraceClassUnLoading参数需要FastDebug版的虚拟机支持。