Java的垃圾回收

张开发
2026/4/10 15:27:48 15 分钟阅读

分享文章

Java的垃圾回收
怎样判断对象存活1、可达性分析算法。这是主流虚拟机采用的算法。它通过一系列称为GC Roots的根对象作为起始点从这些根出发向下搜索搜索走过的路径称为引用链。能够被 GC Roots 直接或间接引用的对象视为“可达”存活否则视为“不可达”可回收。可作为“根”的对象有下面几种1虚拟机栈栈帧中引用的对象。直观感觉栈帧不是代表方法的吗通常方法很快就会执行完方法内部的局部变量也是临时的可以做根没错其实可以从反面理解这个问题如果它不作为根那么它直接引用或间接引用的那些对象不就无“根”了那就要被垃圾回收了方法还怎么执行2方法区中类静态属性引用的对象。这个就很好理解了类静态属性一般都是跟随程序的整个生命周期的它作为根很自然。3方法区中常量引用的对象。确切来说是常量池中引用的那些对象。举例每个类被加载后JVM 会在堆中创建一个对应的 java.lang.Class 对象比如你定义了一个User类你现在通过new User(...)创建了一个User对象此时在堆中会有一个User对象同时由于你用到了User类所以在创建User对象之前其实JVM需要先把User类加载进来User被加载进来之后就会产生一个User.class对象注意不是User对象这个User.class对象也存放于堆中但是其引用会存放于方法区中的运行时常量池。 所以这个User.class对象也可以作为“根”。换句话说程序中所有的 .class 对象都可以作为“根”。当然常量池中引用的对象不只这一种情况还有常量池中的String对象还有各个类中被static final 修饰的对象等。4本地方法栈中JNI引用的对象。跟虚拟机栈中引用的对象相同的原理。2、引用计数算法未被主流虚拟机采用。给对象添加一个引用计数器每当有地方引用它就1引用失效就-1。它未被采用的主要原因是很难解决对象互相引用的情况。例如objA.b objB , objB.a objA。最后可能objA 和 objB 都没有人再使用了但是它们之间由于互相引用计数器都不为0无法回收。引用的类型强引用最常接触的引用类型。比如 User user new User(郭靖”16“临安牛家村”)。对于这种引用只要引用还在即强可达也就不会被回收。虚拟机宁可内存溢出崩溃也不会回收它。软引用有用但不是必需的对象。在代码逻辑里比较常见的场景就是 if(软引用对象还在{ doSomething... } else { doOtherSomething }。对于软引用指向的对象系统会在将要发生内存溢出之前将其回收。对应Java中的 SoftReference。简单理解能不回收就尽量不回收只要内存还够用就让它继续存活。弱引用比软引用更弱一些也大都有 if(弱引用对象还在{ doSomething... } else { doOtherSomething } 的场景。每次遇到垃圾回收都会被回收。虚引用最弱的引用。为一个对象设置虚引用的唯一目的是能在这个对象被回收时收到一个系统通知。它唯一的作用监听对象被 GC 正式回收的时机配合引用队列做后置清理工作。强可达从“根”到该对象至少有一条链路全是强引用。软可达从“根”到该对象不存在强可达路径但至少有一条链路里所有引用都不弱于软引用。弱可达从“根”到该对象不存在强可达路径和软可达路径至少有一条链路里所有引用都不弱于弱引用。例子假设objA是Global类的static final 变量且 objA.v1 objB, objB.c1 new WeakReference(objC), objC.d objD那么objC 到它的根 objA 就没有强可达路径只有一条弱可达路径在垃圾回收时 objC 就会被回收objD虽然是以强引用形式依附于objC但是objD也无法强可达它的根objA所以垃圾回收时也会被一起回收。垃圾收集算法1、标记-清除算法最基础的算法。先遍历整个堆标记出活对象再遍历整个堆清理没有标记的对象。简单粗暴。缺点就是 1在回收垃圾后会留下很多内存碎片这些碎片无法再整合到一起导致后续给对象分配空间时越来越难以找到足够大的连续空间2效率不高。需要遍历所有对象以检查可达性且此阶段通常需要Stop-The-World暂停所有用户线程影响程序响应。2、复制算法它把内存分成两块每次只用一块嗯有点浪费。它也需要遍历内存找出活对象但是一旦找到活对象就马上复制到另一块内存等遍历完所有活对象也就都复制到了另一块内存了刚刚遍历完的这块内存就全部清理成空场。该方法在新生代中常用。实际工程中新生代什么是新生代、老年代很多对象的存活时间都是非常短暂的比如一些局部临时变量所以每次垃圾遍历后就会发现大约98%的对象其实都是死对象即垃圾那么复制算法的复制过程其实也就很轻松了因为并没有很多数据即活对象需要复制。所以针对这个实际情况复制算法在实际应用时会灵活优化。内存会被分成一块Eden和两块Survivor区域比例是8:1:1每次都只会使用Eden和一块Survivor空出一块Survivor。每次垃圾清理时都会把Eden和Survivor中的活对象复制到这块空闲的Survivor上然后把Eden和原Survivor清空。这样呢也就只有10%的内存是空闲的了不会造成过多的浪费。但是确实遇到存活对象特别多空闲的Survivor存不下的情况怎么办呢这时就会启用担保机制即把这些放不下的活对象都放到老年代区域去。注意虚拟机采用哪种算法并不是单一的比如复制算法常用于新生代也就是在堆内存中划出一块作为新生代区这块区域的垃圾收集算法就采用复制算法。为什么复制算法比标记-清除算法效率高复制算法标记-清除算法只需遍历一遍遍历两遍无内存碎片给新对象分配内存简单有内存碎片给新对象分配内存时逻辑更加复杂直接重置一块内存简单快捷要逐个修改垃圾对象的内存状态、维护空闲链表步骤繁琐3、标记-整理算法老年代区域的对象存活时间都普遍较长 比如垃圾遍历后发现活对象占比98%如果也采用复制算法 那就得把区域分成1:1,浪费一半的内存空间所以不合算。于是标记-整理算法被提出了。它先遍历出所有活对象然后让这些活对象全向另一端移动然后再直接清理掉边界以外的内存。4、分代收集算法它与前面三种不同并不是一种具体的算法而是一种方案即针对不同代新生代、老年代采用不同的垃圾收集算法比如新生代使用复制算法老年代使用标记-整理算法。目前不管多么先进的回收器都避免不了STW不然就会出现脏数据只是每种回收器都有自己的优化方案会尽量避免过多、过长的STW。

更多文章