JVM真香系列:如何判断对象是否可被回收?

回复“000”获取大量电子书

JVM中程序寄存器、Java虚拟机栈、本地方法栈,这三个区是随着线程的创建而创建,随着线程结束而销毁。

其实就是这三个的生命周期和线程的生命周期一样。都是每个线程私有的。

每次方法的调用就会向栈里入栈一个栈帧,方法调用结束,跟着就出栈。

对象也是有生命周期的,所以对于不需要的对象要进行必要的清楚,否则久而久之,我们的内存就被一点一点的消耗完。

今天来学习,如何判断对象是否已经可以被回收?以及回收有哪些算法?

如何判断对象已死?

引用计数法

给对象添加一个引用计数器,每当一个地方引用它object时技术加1,引用失去以后就减1,计数为0说明不再引用。

  • 优点:实现简单,判定效率高;

  • 缺点:无法解决对象相互循环引用的问题,对象A中引用了对象B,对象B中引用对象A。

public class A {
 public B b; 
}
public class B {
 public C c; 
}
public class C {
 public A a; 
}
public class Test{

private void test(){
  A a = new A();
  B b = new B();
  C c = new C();

a.b=b;
  b.c=c;
  c.a=a;
 }
}

可达性分析算法

当一个对象到GC Roots没有引用链相连,即就是GC Roots到这个对象不可达时,证明对象不可用。

GC Roots种类:

Java 线程中,当前所有正在被调用的方法的引用类型参数、局部变量、临时值等。也就是与我们栈帧相关的各种引用。
所有当前被加载的 Java 类。
Java 类的引用类型静态变量。
运行时常量池里的引用类型常量(String 或 Class 类型)。
JVM 内部数据结构的一些引用,比如 sun.jvm.hotspot.memory.Universe 类。
用于同步的监控对象,比如调用了对象的 wait() 方法。

public class Test{
 private void test(C c){
  A a = new A();
  B b = new B();
  a.b=b;
  //这里的a/b/c都是GC Root;
 }
}

对象的引用类型

强引用:

User user=new User();

我们开发中使用最多的对象引用方式。

特点:我们平常典型编码Object obj = new Object()中的obj就是强引用。

通过关键字new创建的对象所关联的引用就是强引用。

当JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错误(OOM),使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。

对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以被垃圾收集的了,具体回收时机还是要看垃圾收集策略。

软引用:

SoftReference object=new SoftReference(new Object());

特点:软引用通过SoftReference类实现。软引用的生命周期比强引用短一些。

只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象:即JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。

软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

后续,我们可以调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。

应用场景:软引用通常用来实现内存敏感的缓存。如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。

弱引用:

WeakReference object=new WeakReference (new Object();

ThreadLocal中有使用。

弱引用通过WeakReference类实现。弱引用的生命周期比软引用短。

在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快回收弱引用的对象。

弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。应用场景:弱应用同样可用于内存敏感的缓存。

虚引用:

几乎没见过使用, ReferenceQueue 、PhantomReference。

finalize方法

这个方法就有点类似于“某个人被判了死刑,但是不一定会死”的情景。

即使在可达性分析算法中不可达的对象,也并非一定是“非死不可”的,这时候他们暂时处于“缓刑”阶段,真正宣告一个对象死亡至少要经历两个阶段:

1、如果对象在可达性分析算法中不可达,那么它会被第一次标记并进行一次刷选,刷选的条件是是否需要执行finalize()方法(当对象没有覆盖finalize()或者finalize()方法已经执行过了(对象的此方法只会执行一次)),虚拟机将这两种情况都会视为没有必要执行)。

2、如果这个对象有必要执行finalize()方法会将其放入F-Queue队列中,稍后GC将对F-Queue队列进行第二次标记,如果在重写finalize()方法中将对象自己赋值给某个类变量或者对象的成员变量,那么第二次标记时候就会将它移出“即将回收”的集合。

方法区的回收

在堆中,尤其是在新生代中,一次垃圾回收一般可以回收 70% ~ 95% 的空间,而方法区的垃圾收集效率远低于此。

方法区垃圾回收主要两部分内容:废弃的常量和无用的类。

垃圾回收算法

标记-清除

第一步:就是找出活跃的对象。我们反复强调 GC 过程是逆向的, 根据 GC Roots 遍历所有的可达对象,这个过程,就叫作标记。

第二步:除了上面标记出来的对象以外,其余的都清除掉。

  • 缺点:标记和清除效率不高,标记和清除之后会产生大量不连续的内存碎片

复制

新生代使用,新生代分中Eden:S0:S1= 8:1:1,其中后面的1:1就是用来复制的。

当其中一块内存使用完了,就将还存活的对象复制到另外一块上面,然后把已经使用过的内存空间一次清除掉。

一般对象分配都是进入新生代的eden区,如果Minor GC还存活则进入S0区,S0S1不断对象进行复制。对象存活年龄最大默认是15,大对象进来可能因为新生代不存在连续空间,所以会直接接入老年代。任何使用都有新生代的10%是空着的。

  • 缺点:对象存活率高时,复制效率会较低,浪费内存。

标记整理

它的主要思路,就是移动所有存活的对象,且按照内存地址顺序依次排列,然后将末端内存地址以后的内存全部回收。 但是需要注意,这只是一个理想状态。对象的引用关系一般都是非常复杂的,我们这里不对具体的算法进行描述。我们只需要了解,从效率上来说,一般整理算法是要低于复制算法的。这个算法是规避了内存碎片和内存浪费。

让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

从上面的三个算法来看,其实没有绝对最好的回收算法,只有最适合的算法。

STW

STW=Stop The world,字面翻译过来就是整个世界都停止了。

在JVM中也有这么个说法,就是STW,是指JVM垃圾收集器在收集垃圾对象的时候,其他所有线程都被挂起(除了垃圾收集器之外),JVM中一种全局暂停现象。

----全局停顿,想想就很可怕,所有的Java代码停止执行,native代码可以执行,但是不能与JVM进行交互,这些基本上都是由于GC引起的。

但是也还有另外的几种场景也可以导致STW:

1.Garbage collection pauses
2.Code deoptimization
3.Flushing code cacheClass redefinition (e.g. hot swap or instrumentation)
4.Biased lock revocation
5.Various debug operation (e.g. deadlock check or stacktrace dump)

(0)

相关推荐

  • JVM专栏

    目录 一.JVM内存结构 1.JDK1.8内存结构 JDK1.8与JDK1.7内存结构比较 程序计数器 虚拟机栈 本地方法栈 堆 元空间(Metaspace) 配置参数与异常 2.内存溢出 内存溢出与 ...

  • 深度揭秘垃圾回收底层,这次让你彻底弄懂她

    Java 与 C++ 之间有一堵由内存动态分配和垃圾收集技术所围成的高墙 ---<深入理解Java虚拟机> 我们知道手动管理内存意味着自由.精细化地掌控,但是却极度依赖于开发人员的水平和细 ...

  • JVM垃圾回收算法

    后端开发技术 来自:后端开发技术 谈到Java不得不谈GC,谈到GC不得不谈垃圾回收算法 对象已死吗 在进行垃圾回收之前,第一件事就是判断哪些对象还存活着,哪些对象已死需要被回收. 1.引用计数算法 ...

  • Java编程开发之浅析Java引用机制

    对于一个Java的对象而言,存储主要分为两种,一种是内存堆(Heap),内存堆是无序的,主要用来存放创建的Java对象:一种是内存栈(Stack),主要用来存放Java引用,然后在管理过程使用Java ...

  • Java开发之虚拟机八股文分享

    简述JVM内存模型 线程私有的运行时数据区: 程序计数器.Java 虚拟机栈.本地方法栈. 线程共享的运行时数据区:Java 堆.方法区. 简述程序计数器 程序计数器表示当前线程所执行的字节码的行号指 ...

  • Java之GC机制

    Java之GC机制

  • JVM真香系列:图解垃圾回收器

    回复"000"获取大量电子书 不知不觉,JVM系列已经到回收算法的实现了. 本文主要内容 先普及三个概念: 并行收集:指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态. 并 ...

  • JVM真香系列:堆内存详解

    回复"000"获取大量电子书 前面的文章中已经有所提到过堆,只是大致介绍了一下.本文就来详细聊聊JVM中的堆. 在 JVM中,堆被划分成两个不同的区域:新生代 ( Young ). ...

  • JVM真香系列:方法区、堆、栈之间到底有什么关系

    回复"000"获取大量电子书 栈指向堆 如果在栈帧中有一个变量,类型为引用类型,比如: package com.tian.my_code.test; public class Jv ...

  • JVM真香系列:轻松掌握JVM运行时数据区

    回复"000"获取大量电子书 前面我们讲了从java源文件到class文件,在从class文件到JVM.那么今天继续聊JVM是如何布局的. JVM运行时数据区有几个?看看官网是就知 ...

  • JVM真香系列:轻松理解class文件到虚拟机(下)

    回复"000"获取大量电子书 类加载器 类加载器是很多人认为很硬的骨头.其实也没那么可怕,请听老田慢慢道来. 在装载(Load)阶段,通过类的全限定名获取其定义的二进制字节流,需要 ...

  • JVM真香系列:轻松理解class文件到虚拟机(上)

    回复"000"获取大量电子书 JVM初探 class文件到JVM中,就相当于我们吃饭,食物吃进了肚子里,不同的营养成分被身体不同的器官吸收. 查找class文件并导入到JVM中 ( ...

  • JVM真香系列:.java文件到.class文件

    回复"000"获取大量电子书 认识JVM 什么是JVM JVM 全称 Java Virtual Machine,也就是我们耳熟能详的 Java 虚拟机.它能识别 .class后缀的 ...

  • 真香系列之盗版的WPS?

    ‍‍ ‍‍逃离工地后明显觉得能写的素材少了. 我琢磨了下原因,发现是因为到格子间后,面对的就那几个固定的同事,故事有限:有趣程度也远比不上工地上的那些个吃喝嫖赌抽.吃拿卡要偷的主儿. 这就让我有点伤脑 ...

  • 10万元左右就能买!这款合资SUV实锤"真香"系列

    如何在有限的预算下,购买一辆称心如意的新座驾?成为当前年轻消费者普遍的难题.以10-15万级别的SUV为例,这里面既有主打高配置的国产SUV,也有主打品牌和品质的合资SUV,到底哪款车最值得年轻人入手 ...