JVM垃圾收集和内存回收
一、常用的判断对象存活算法
要进行垃圾回收,首先要做的一件事就是判断哪些对象是垃圾,哪些对象又是可用的。下面是两种常见的垃圾判断算法。
- 引用计数器算法
为对象添加一个引用计数器,当有引用地方引用到它时,计算器就加一,当一个引用失效的时候,计数器就减一。
优点:实现简单,高效
缺点:很难解决对象之间循环引用的问题
- 根搜索算法
根据一系列GC ROOT的引用链来判断哪些对象已经失效。(不在引用链里面的对象都判为已失效)
Java中,可以作用GC ROOT的对象包括以下:
- 虚拟机栈中引用的对象(栈帧的本地变量表)。
- 方法去的静态属性引用的对象
- 方法区的常量引用的对象
- 本地方法栈中JNI引用的对象
二、根搜索算法
这里重点介绍一下根搜索算法,因为主流的HotSpot虚拟机和大多JVM虚拟机用的都是这一算法。
- 关于finalize()方法
被根搜索算法标记不可用的对象,其实不会马上死亡。虚拟机会给一次自我救赎的机会。这个机会就在finalize里面。如果对象覆盖了finalize方法,JVM第一次发现对象不可用的时候,会先执行finalize方法。如果对象在finalize方法中重新与某个对象关联起来,就可以逃过死亡。如果JVM下一次GC的时候如果发现该对象还是不可用,就会让其真正死亡。
需要注意的是,每个对象只有一次救赎机会,也就是只会执行一次ifinalize方法。也就是如果执行完finalize方法后,该对象又被GC发现不可用了,这时是不会再有救赎的机会了。同时也不推荐用finalize()方法来防止对象被回收
oopMap: 让JVM知道哪些地方存放着对象引用。
safePoint: 代码进入安全点后才会执行GC。Safepoint的选定既不能太少以致于让GC等待时间太长,也不能过于频繁以致于过分增大运行时的负荷。所以,安全点的选定基本上是以程序“是否具有让程序长时间执行的特征”为标准进行选定的。safePoint的选择
- 循环的末尾 (防止大循环的时候一直不进入safepoint,而其他线程在等待它进入safepoint)
- 方法返回前
- 调用方法的call之后
- 抛出异常的位置
三、垃圾回收算法
-
标记-清除算法
先把所有不可用的内存块标记一下,最后统一清除。主要两个缺点:
- 标记和清除的过程效率都不太高
- 会产生内存碎片,等到有对象需要分配较大内存但是又没有这么大的连续内存时,不得不提前触发一次垃圾收集动作。
-
复制算法
复制算法解决了效率和内存碎片的问题。它的理念内存分为两块,内存分配时只使用其中一块,之后GC时将可用对象复制到另一块内存中,接着将原来那个内存块的对象全部清除掉。
这种方法简单粗暴,效率很高。但是却严重浪费了内存空间。
现代商业虚拟机一般都采用复制算法来回收新生代对象。因为新生代大多都是朝生夕死,经过一次GC后存活的对象非常少,所以可以将内存分为8:1:1(Eden:survive:survive)。当执行GC的时候,直接查看Eden区和其中一块已经分配对象的survive区,然后将可用对象都复制到剩下的那块还没survive区中。这样内存使用率就高达90%了。(真正使用的内存时8+1)。
另外,当10%的内存空间不够分配存活对象时,JVM会启动担保机制,将老生代的内存空间预支出来使用。
-
标记-整理算法
复制算法在对象存活率很高的情况,效率会变低。所以不适合用当老生代的回收算法。于是有人提出了标记整理算法。思路就是将可用的对象都向一端移动,最后清除端边界以外的内存即可。
-
分代算法
现在JVM虚拟机一般都根据新生代和老生代的特点分别使用不同的回收算法。新生代生存率低,所以使用复制算法。老生代生存率高,所以使用标记-清除或者标记-整理算法。
四、垃圾收集器
JVM垃圾收集器.png
-
Serial 收集器
这是一款比较老的收集器。由于是单线程处理,所以GC的时候停顿明显。jdk1.3.1这个收集器之前是唯一的选择。
Serial对新生代对象(new)采用的是复制算法。老生代对象采用的是标记-整理算法。
-
ParNew 收集器
其实就是Serial的多线程版本。其他和Serial都差不多。
除了Serial收集器外,它只能和CMS配合使用。
-
Parallel Scavenge 收集器
采用复制算法,并行的多线程收集器。
特点就是可以人为的控制吞吐量。吞吐量就是真正代码执行时间和CPU消耗的总时间的比值。比如虚拟机在CPU上总共消耗了100分钟,GC花了1分钟。那吞吐量就是99%。可以根据下面3个参数来控制吞吐量:
-XX:MaxGCPauseMillis 最大GC停顿时间(毫秒)。(也不要以为越小就越好)
-XX:GCTimeRatio 直接设置吞吐量大小,范围0-100%
-XX:+UseAdaptiveSizePolicy 当这个参数打开之后,就不需要手工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)。
-
Serial Old 收集器
Serial 收集器的老生代收集器。用标记-整理算法。这个收集器的主要意义是在client模式下使用。另外,它还有两大用途。
- 与Parallel Scavenge搭配使用
- 作为CMS的后备预案,当并发收集发送 Curruent Mode Failure的时候使用。
-
Parallel Old 收集器
Parallel的老生代收集器。采用标记-整理算法。一般与Parallel Scavenge搭配使用。
-
CMS 收集器
这是一款并发低停顿收集器。它的目标就是尽可能的减少GC造成的时间停顿。所以比较适合对web应用这种交互式应用。
采用的是标记-清除算法。涉及的内容有点多,就不详细介绍。读者可自行百度或者查看以下链接。
JVM实用参数(七)CMS收集器
-
G1收集器
G1收集器是收集器技术发展的最前沿结果。刚发布的JDK9已经将它作为默认垃圾收集器。
采用标记-整理算法。深入理解JVM G1收集器
网友评论