Java虚拟机 —— 垃圾回收

时间:2022-12-27 17:39:48

垃圾回收

垃圾回收主要解决三个问题(回收哪些Which,什么时候回收WHEN,如何回收HOW)

一、回收哪些

这三个问题,最主要的还是第一个,Which回收哪些,评断回收还是不回收的标准是看对象是否被引用

引用分为四种:

  • 强引用:一个对象被一个引用所指向。绝对不会被JVM回收的,即使内存不过用
  • 软引用:只有在堆内存不够用的时候才会被回收。使用SoftReference实现
  • 弱引用:弱引用相对于软引用,引用级别更低。只要垃圾回收器启动了回收,就会被回收掉。绝WeakReference实现
  • 虚引用:虚引用的主要作用是跟踪对象被垃圾回收的状态。虚引用对对象本身没有太大影响,必须和引用队列(ReferenceQueue)联合使用

1、引用计数法

如果两个对象相互引用,就不会被回收,当然,GC并没有采用这种算法

2、根搜索算法

从根开始,沿着整个对象图上的每条链接,确定可达的对象,如果对象不可达,则作为垃圾收集
是对(1)的改进,根对象到达某一对象不可达,GC就会对其回收。

public static void main(String[] args) {
        Node n1 = new Node();
        Node n2 = new Node();
        Node n3 = new Node();
        n1.next = n2;
        n3 = n2;
        n2 = null;
    }

Java虚拟机 —— 垃圾回收

二、何时回收,如何回收

这就需要垃圾收集算法来解决,但讲垃圾回收算法之前需要明白一个分代的概念

1、分代的策略

绝大多数的对象不会被长时间引用,这些对象在其Young期间就会被回收
很老的对象和很新的对象之间很少存在相互引用

Java虚拟机 —— 垃圾回收

Young代

大部分垃圾回收器对Young代都采用复制算法,为什么?因为Young代处于可达的对象数量少,所以复制成本不大
Young代由一个Eden区和2个Survivor区构成。绝大多数对象先分配到Eden区中,Survivor区中的对象都至少经历过一次垃圾回收
为什么要有2个Survivor区,是因为其中一个Survivor是空的,来存放Young代的对象。最后会清空Eden区和第一个Survivor区
思考:Survivor的大小设置的变化会产生什么影响

Old代

Young代的对象经过多次垃圾回收依然没有被回收,就会被转移到Old代
随着时间流逝,Old代的对象会越来越多,因此Old代的空间要与Young代的空间大
Old代的垃圾回收的两个特征:Old代垃圾回收的执行频率不需要太高,因为死的少。每次回收需要更长的时间来完成(如何理解,因为对象多吧)
垃圾回收通常会采用标记压缩算法。因为对象不会很快死亡,也不会大量产生内存碎片

Permanent代

主要用于装载Class、方法等信息
垃圾回收机制通常不会回收这一代的对象。
服务器程序通常会加载很多类,需要加大Permanent代内存。
OutOfMemoryError:PermGen space错误

2、垃圾回收算法

标记-清除算法

标记所有需要回收的对象,标记完成后,统一回收所有被标记的对象,
不足之处,标记和清除的效率不高,标记清除后会产生大量不连续的内存碎片

复制算法

为了解决效率问题,将内存分为大小相等的两块,每次使用一块,当这一块内存使用完了之后,将存活的对象放到另一块内存中,然后对原先那一半内存进行回收。实现简单,运行高效,不过代价是将内存缩小一半,代价过高。

标记-整理算法

是对标记清除算法的改进,进行标记好了之后,将存活的对象都向一边移动,然后直接清理掉端边界以外的内存

分代收集算法

商业虚拟机都采用“分代收集”算法,根据对象的存活周期将内存分为几块,一般是将java堆分成新生代和老年代,如果新生代有很少的存活对象,就用复制算法,老年代有很多存活对象,就用标记-清除或标记-整理算法

HotSpot虚拟机下的垃圾收集器

由于内存中的对象,是按存活周期存放在不同的内存块中的,所以,我们选择不同的算法来针对不同的内存块进行垃圾收集。从而,对于,不同的内存块,我们需要有不同的垃圾收集器。

新生代的垃圾收集器有:Serial收集器ParNew收集器Parallel Scavenge收集器

老年代的垃圾收集器有:Serial Old收集器Parallel Old收集器CMS收集器G1收集器

Serial收集器/Serial Old收集器

串行,是单线程的,使用“复制”算法。当它工作时,必须暂停其它所有工作线程。特点:简单而高效。一般用于Client模式的JVM中
Serial Old是老年代的单线程收集器,使用标记-整理算法。

ParNew收集器

ParNew收集器,是Serial收集器的多线程版。是运行在Server模式下的虚拟机中首选的新生代收集器。除了Serial收集器外,目前只有它能与CMS收集器配合工作。

Parallel Scavenge收集器

吞吐量优先收集器,吞吐量=程序运行时间/(JVM执行回收时间+程序运行时间),是server模式JVM的默认配置

Parallel Old收集器

老年代的多线程收集器,使用标记-整理算法,吞吐量优先,适合于Parallel Scavenge搭配使用

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,使用“标记-清除”算法。
回收线程数=(CPU核心数+3)/4
CMS收集器分4个步骤进行垃圾收集工作:
1、初始标记 2、并发标记 3、重新标记 4、并发清除
其中“初始标记”、“重新标记”是需要暂停其它所有工作线程的。

G1收集器

G1(Garbage First)收集器,基于“标记-整理”算法,可以非常精确地控制停顿。
可以不与其他收集器搭配,独立收集新生代和老年代。

三、内存管理技巧

1、尽量使用直接量:String str = "hello"

2、使用StringBuilder和StringBuffer进行字符串连接

3、尽早释放无用对象的引用

Object obj = new Object();obj = null;这行代码并不能发挥C++中的delete和free作用,其作用仅仅是断开obj 与new Object()的关联,new Object所占用的内存并没有释放掉。以此来诱发GC对其进行回收。如果是在方法中,其实要考虑两种情况,多数情况下不需要这么写,对象会随着因为方法调用的结束而结束。但如果obj =null之后,还有耗时耗内存的操作的话,就需要这样写。

四、设置Java虚拟机内存的一些参数

  • -Xmx 设置堆内存的最大容量
  • -Xms 设置堆内训初始容量
  • -XX:NewSize = size 设置Young代内存的默认容量
  • -XX:SurvivorRatio = 8 设置Young代中eden/survivor的比例
  • -XX:MaxNewSize = size 设置Young代内存的最大容量
  • -XX:PermSize = size 设置永久代的默认容量
  • -XX:MaxPermSize = size 设置永久代内存的最大容量

五、一次完整的GC流程

从ygc到fgc