JDK1.2之后,Java扩充了引用的概念,将引用分为强引用、软引用、弱引用和虚引用四种。

强引用

类似于”Object a = new Object()”这类的引用,只要垃圾强引用存在,垃圾回收器就不会回收掉被引用的对象。

软引用

对于软引用关联的对象,在系统将要发生内存溢出异常之前,会把这些对象列入垃圾回收范围中进行回收。如果这次回收还没有足够内存,则抛出内存异常。

使用SoftReference类实现软引用

软引用的回收策略在不同的JVM实现会略有不同,javadoc中说明:

Virtual machine implementations are, however, encouraged to bias against clearing recently-created or recently-used soft references.

也就是说JVM不仅仅只会考虑当前内存情况,还会考虑软引用所指向的referent最近使用情况和创建时间来综合决定是否回收该referent。

弱引用

强度比软引用更弱,被弱引用关联的对象只能存活到下一次垃圾回收发生之前。当发生GC时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

使用WeakReference类实现弱引用

虚引用

一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用取得一个对象的实例(PhantomReference的get方法总是返回null)。为一个对象设置虚引用关联的唯一目的就是能够在这个对象被垃圾回收器回收掉后收到一个通知。

使用PhantomReference类实现虚引用

除了强引用外,Java还引入了SoftReference,WeakReference,PhantomReference,FinalReference ,这些类放在java.lang.ref包下,类的继承体系如下图

Reference类结构.png

用途

WeakReference和SoftReference都可以用来实现cache

PhantomReference用作跟踪垃圾回收

使用

WeakReference

示例:

WeakReference weakString = new WeakReference<>(“abc”);

WeakReference 多与ReferenceQueue一块使用:

对象(“abc”)被回收后,会把弱引用对象(weakString)放入队列ReferenceQueue中

WeakReference继承Reference,其中只有两个构造函数:

public class WeakReference extends Reference {

public WeakReference(T referent) {

super(referent);

}

public WeakReference(T referent, ReferenceQueue super T> q) {

super(referent, q);

}

}

WeakReference(T referent, ReferenceQueue super T> q):与上面的构造方法比较,多了个ReferenceQueue,在对象被回收后,会把弱引用对象,也就是WeakReference对象或者其子类的对象,放入队列ReferenceQueue中,注意不是被弱引用的对象,被弱引用的对象已经被回收了。

Reference && ReferenceQueue

Reference

public abstract class Reference {

//即Reference所包装的引用对象

private T referent; /* Treated specially by GC */

//ReferenceQueue本身通过链表实现队列,ReferenceQueue对象同时保存了一个Reference类型的head节点,Reference封装了next字段,这样就是可以组成一个单向链表。

volatile ReferenceQueue super T> queue;

/* When active: NULL

* pending: this

* Enqueued: next reference in queue (or this if last)

* Inactive: this

*/

@SuppressWarnings("rawtypes")

Reference next;

/* When active: next element in a discovered reference list maintained by GC (or this if last)

* pending: next element in the pending list (or null if last)

* otherwise: NULL

*/

//由jvm调用

transient private Reference discovered; /* used by VM */

/* List of References waiting to be enqueued. The collector adds

* References to this list, while the Reference-handler thread removes

* them. This list is protected by the above lock object. The

* list uses the discovered field to link its elements.

*/

private static Reference pending = null;

...

}

Reference对象有四种状态:

active

GC会特殊对待此状态的引用,一旦被引用的对象的可达性发生变化(如失去强引用,只剩弱引用,可以被回收),GC会将引用放入pending队列并将其状态改为pending状态

pending

位于pending队列,等待ReferenceHandler线程将引用入队queue

enqueue

ReferenceHandler将引用入队queue

inactive

引用从queue出队后的最终状态,该状态不可变

Reference对象有四种状态.png

ReferenceQueue

public class ReferenceQueue {

/**

* Constructs a new reference-object queue.

*/

public ReferenceQueue() { }

private static class Null extends ReferenceQueue {

boolean enqueue(Reference extends S> r) {

return false;

}

}

static ReferenceQueue NULL = new Null<>();

static ReferenceQueue ENQUEUED = new Null<>();

private volatile Reference extends T> head = null;

}

这两个字段的主要功能:NULL是当我们构造Reference实例时queue传入null时,会默认使用NULL,这样在enqueue时判断queue是否为NULL,如果为NULL直接返回,入队失败。ENQUEUED的作用是防止重复入队,reference后会把其queue字段赋值为ENQUEUED,当再次入队时会直接返回失败。

boolean enqueue(Reference extends T> r) { /* Called only by Reference class */

synchronized (lock) {

// Check that since getting the lock this reference hasn't already been

// enqueued (and even then removed)

ReferenceQueue> queue = r.queue;

if ((queue == NULL) || (queue == ENQUEUED)) {

return false;

}

assert queue == this;

r.queue = ENQUEUED;

r.next = (head == null) ? r : head;

head = r;

queueLength++;

if (r instanceof FinalReference) {

sun.misc.VM.addFinalRefCount(1);

}

lock.notifyAll();

return true;

}

}

Reference与ReferenceQueue之间是如何工作的呢?

Reference里有个静态字段pending,同时还通过静态代码块启动了Reference-handler thread。当一个Reference的referent被回收时,垃圾回收器会把reference添加到pending这个链表里,然后Reference-handler thread不断的读取pending中的reference,把它加入到对应的ReferenceQueue中。

当reference与referenQueue联合使用的主要作用就是当reference指向的referent回收时,提供一种通知机制,通过queue取到这些reference,来做额外的处理工作。

static {

ThreadGroup tg = Thread.currentThread().getThreadGroup();

Thread handler = new ReferenceHandler(tg, "Reference Handler");

handler.setPriority(Thread.MAX_PRIORITY);

handler.setDaemon(true);

handler.start();

...

}

ThreadGroup

使用线程组的好处是可以对这一组的线程进行整体操作。

void setDaemon(boolean daemon)//更改此线程组的后台程序状态。

void setMaxPriority(int pri)//设置线程组的最高优先级

ReferenceHandler类调用tryHandlePending

/* High-priority thread to enqueue pending References */

private static class ReferenceHandler extends Thread {

...

public void run() {

while (true) {

tryHandlePending(true);

}

}

}

static boolean tryHandlePending(boolean waitForNotify) {

Reference r;

Cleaner c;

try {

synchronized (lock) {

if (pending != null) {

r = pending;

// 'instanceof' might throw OutOfMemoryError sometimes

// so do this before un-linking 'r' from the 'pending' chain...

c = r instanceof Cleaner ? (Cleaner) r : null;

// unlink 'r' from 'pending' chain

pending = r.discovered;

r.discovered = null;

} else {

// The waiting on the lock may cause an OutOfMemoryError

// because it may try to allocate exception objects.

if (waitForNotify) {

lock.wait();

}

// retry if waited

return waitForNotify;

}

}

}

ReferenceQueue super Object> q = r.queue;

if (q != ReferenceQueue.NULL) q.enqueue(r);

return true;

}

PhantomReference

虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

PhantomReference的get方法总是返回null,因此无法访问对应的引用对象;

其意义在于说明一个对象已经进入finalization阶段,可以被gc回收,用来实现比finalization机制更灵活的回收操作。

Phantom references are most often used for scheduling pre-mortem cleanup actions in a more flexible way than is possible with the Java finalization mechanism.

Phantom Reference can be used in situations, where sometime using finalize() is not sensible thing to do.This reference type differs from the other types defined in java.lang.ref Package because it isn’t meant to be used to access the object, but as a signal that the object has already been finalized, and the garbage collector is ready to reclaim its memory.

代码示例:blog.reference.PhantomTest

FinalReference/finalizers

finalizers作用:通过使用finalizers 中FinalizerThread来执行finalize方法()

原理:

实现了object的finalize()的类在创建时会新建一个FinalizerReference,这个对象是强引用类型,实现了finalize()的对象,下面直接叫原对象。原对象没有被其他对象引用时(FinalizeReference除外),执行GC不会马上被清除掉,而是放入一个静态链表中(ReferenceQueue),有一个守护线程专门去维护这个链表,如何维护呢?就是轮到该线程执行时就弹出里面的对象,执行它们的finalize(),对应的FinalizerReference对象在下次执行GC时就会被清理掉。

一个堆的FinalizerReference会组成一条双向链表,垃圾回收器应该会持有链表头(链表头在FinalizerReference中为一个静态成员)。

覆盖了finalize方法的对象至少需要两次GC才可能被回收。第一次GC把覆盖了finalize方法的对象对应的Finalizer reference加入referenceQueue等待FinalizerThread来执行finalize方法。第二次GC才有可能释放finalizee对象本身,前提是FinalizerThread已经执行完finalize方法了,并把Finalizer reference从Finalizer静态unfinalized链表中剔除,因为这个链表和Finalizer reference对finalizee构成的是一个强引用。

Item 8: Avoid finalizers and cleaners

Java的Finalizer引发的内存溢出 - 冰花ぃ雪魄 - 博客园

问题

为什么会泄漏?

直接原因就是守护线程优先级比较低(Thread.MAX_PRIORITY- 2),运行的时间比较少。如果较短时间内创建较多的原对象,就会因为守护线程来不及弹出原对象而使FinalizerReference和原对象都得不到回收。无论怎样调用GC都没有用的,因为只要原对象没有被守护线程弹出执行其finalize()方法,FinalizerReference对象就不会被GC回收。

引申问题:

1、private static Finalizer unfinalized= null; //做什么用?

unfinalized: 维护了一个未执行finalize方法的reference链表。维护静态字段unfinalized的目的是为了一直保持对未执行finalize方法的reference的强引用,防止被gc回收掉。

第一次GC时,会把该finalizee对应的reference放到Finalizer的refereneQueue中;

接着,FinalizerThread来执行finalizee的finalize方法,并把当前Finalizer从unfinalized中剔除。

当下一次GC发生时,由于unfinalized已经不再持有该对象的referent,故该对象被直接回收掉。

2、finalizer.setPriority(Thread.MAX_PRIORITY- 2);//为什么守护线程优先级比较低?

每个线程都有优先级,优先级的高低只和线程获得执行机会的次数多少有关。

并非线程优先级越高的就一定先执行,哪个线程的先运行取决于CPU的调度;

默认情况下main线程具有普通的优先级,而它创建的线程也具有普通优先级。

Thread对象的setPriority(int x)和getPriority()来设置和获得优先级。

MAX_PRIORITY :值是10

MIN_PRIORITY :值是1

NORM_PRIORITY :值是5(主方法默认优先级)

执行过程

对象初始化时(构造函数返回之前调用)调用register方法,register方法会把本对象封装为Finalizer放入静态unfinalized链表中。

JVM通过VM参数 RegisterFinalizersAtInit 的值来确定何时调用register,RegisterFinalizersAtInit默认为true,则会在构造函数返回之前调用call_register_finalizer方法。如果通过-XX:-RegisterFinalizersAtInit 设为false,则会在对象空间分配好之后就调用call_register_finalizer。

JVM在执行GC时,实现了finalize()的对象没有被其他对象引用时(FinalizeReference除外),会把该finalizee对应的reference放到Finalizer的refereneQueue中,等待FinalizerThread来执行finalizee的finalize方法。

下次GC时,finalizee对象才能被GC回收。

final class Finalizer extends FinalReference {

private static ReferenceQueue queue = new ReferenceQueue<>();

private static Finalizer unfinalized = null;

private static final Object lock = new Object();

/* Invoked by VM */

static void register(Object finalizee) {

new Finalizer(finalizee);

}

private Finalizer(Object finalizee) {

super(finalizee, queue);

add();

}

private void add() {

synchronized (lock) {

if (unfinalized != null) {

this.next = unfinalized;

unfinalized.prev = this;

}

unfinalized = this;

}

}

private static class FinalizerThread extends Thread {

private volatile boolean running;

FinalizerThread(ThreadGroup g) {

super(g, "Finalizer");

}

public void run() {

if (running)

return;

// Finalizer thread starts before System.initializeSystemClass

// is called. Wait until JavaLangAccess is available

while (!VM.isBooted()) {

// delay until VM completes initialization

VM.awaitBooted();

}

final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();

running = true;

for (;;) {

try {

Finalizer f = (Finalizer)queue.remove();

f.runFinalizer(jla);

} catch (InterruptedException x) {

// ignore and continue //finalize方法抛异常时不处理

}

}

}

}

private void runFinalizer(JavaLangAccess jla) {

synchronized (this) {

if (hasBeenFinalized()) return;

remove();

}

try {

Object finalizee = this.get();

if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {

jla.invokeFinalize(finalizee);

/* Clear stack slot containing this variable, to decrease

the chances of false retention with a conservative GC */

finalizee = null;

}

} catch (Throwable x) { }

super.clear();

}

其中:

// Finalizer thread starts before System.initializeSystemClass is called. Wait until JavaLangAccess is available

表示System.initializeSystemClass初始后sun.misc.VM#booted=true,才往下执行,否则等待。

java中System类简介(转) - 沧海一滴 - 博客园

使用JavaLangAccess和SharedSecrets来获取JVM中的实例 - yums467的专栏 - CSDN博客

如何执行finalize方法的?

sun.misc.SharedSecrets#getJavaLangAccess 用于获取系统初始化后的对象

sun.misc.JavaLangAccess#invokeFinalize用于执行参数对象的finalize()方法

原理是:

java.lang.System中,静态初始化registerNatives()方法时,会调用 initializeSystemClass方法,进而执行setJavaLangAccess方法;setJavaLangAccess方法会对sun.misc.SharedSecrets.javaLangAccess赋对象,对象中invokeFinalize(Object o)的实现就是执行o.finalize()方法。

public final class System {

/* register the natives via the static initializer.

*

* VM will invoke the initializeSystemClass method to complete

* the initialization for this class separated from clinit.

* Note that to use properties set by the VM, see the constraints

* described in the initializeSystemClass method.

*/

private static native void registerNatives();

static {

registerNatives();

}

private static void initializeSystemClass() {

// register shared secrets

setJavaLangAccess();

...

// Subsystems that are invoked during initialization can invoke

// sun.misc.VM.isBooted() in order to avoid doing things that should

// wait until the application class loader has been set up.

// IMPORTANT: Ensure that this remains the last initialization action!

sun.misc.VM.booted();

}

java.lang.System#setJavaLangAccess

private static void setJavaLangAccess() {

// Allow privileged classes outside of java.lang

sun.misc.SharedSecrets.setJavaLangAccess(new sun.misc.JavaLangAccess(){

public sun.reflect.ConstantPool getConstantPool(Class> klass) {

return klass.getConstantPool();

}

......

public void invokeFinalize(Object o) throws Throwable {

o.finalize();

}

});

}

参考

java引_JAVA中的引用相关推荐

  1. java 软引用_Java中弱引用和软引用的区别以及虚引用和强引用介绍

    知道弱引用和软引用的概念与如何使用它们是两码事,引用类在垃圾回收工作的过程中有重要作用.我们都知道垃圾回收器会回收符合回收条件的对象的内存,但并不是所有的程序员都知道回收条件取决于指向该对象的引用类型 ...

  2. java 指针 引用_java中的引用与c中的指针

    指针 首先要弄清楚指针和c中的指针是不一样的,前者是概念,后者是具体实例. 换句话说,C中的指针只是指针中的一种,其他语言也有指针,比如C++.但是我们同别人交流的时候,大都不会从概念上去交流,一般会 ...

  3. java如何引用文件_java 中如何引用json文件

    java如何创建Json文件?每次做题前,小编都会先潇洒的写下一个解字,然后开始看题-- 新建json文件放在服务器指定目录下,然后读取和写入信息. 小编是想把讨论世界上最可怕的事情,莫过于有眼睛却发 ...

  4. c java 引用类型_java中的引用类型

    介绍 java中的引用有4种类型:强引用(Strong Reference),软引用(Soft Reference),弱引用(Weak Reference),虚引用(Phantom Reference ...

  5. java 事务_Java中事务总结详解(精华)

    1.什么是JAVA事务? 通常的观念认为,事务仅与数据库相关. 事务必须服从ISO/IEC所制定的ACID原则.ACID是原子性(atomicity).一致性(consistency).隔离性 (is ...

  6. [转载] c++多态与java多态性_Java中的多态性

    参考链接: Java中的加法和串联 c++多态与java多态性 Polymorphism is one of the core concepts of OOPS paradigm. The meani ...

  7. java 且_JAVA中逻辑运算符“|”和“”与“||”和“”的用法

    1.使用规则: (1)& 可以用作逻辑与的运算符,表示逻辑与(and) a.当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则,只要有一方为false,则结果为false ...

  8. java 包装类_Java中的包装类

    Java中哪些需要包装类 它们将原始数据类型转换为对象.如果我们希望修改传递给方法的参数,则需要对象(因为基元类型是按值传递的). java.util包中的类只处理对象,因此在这种情况下包装类也有帮助 ...

  9. java 难度_java中难度大一点的面试题

    1.请大概描述一下Vector和ArrayList的区别,Hashtable和HashMap的区别.(5) (1)Vector和ArrayList的异同 实现原理,功能相同,可以互用 主要区别: Ve ...

最新文章

  1. tomcat下载与安装..使用和配置环境变量
  2. Python,Pandas,Bokeh Cheat Sheet-Data Science
  3. JAVA:this的使用
  4. 计算机系统与网络技术简答题,计算机与网络技术基础 简答题
  5. 博客园电子月刊[第一期]
  6. 用python处理文本数据_用Python读取几十万行文本数据
  7. Spring Boot笔记-get请求发送json数据(方便前端vue解析)
  8. OEA 中 WPF 树型表格虚拟化设计方案
  9. 《移动通信》学习总结
  10. BestCoder Round #70
  11. 计算机可以关闭家庭组,win10系统禁用(关闭)家庭组功能的具体方法
  12. Hexo博客使用 Next主题 后的一些相关配置 记录
  13. 解决微信开发工具卡顿的问题
  14. rocketdock 打不开 有进程
  15. uni-app nvue/vue 引入第三方字体教程,在线ttf转base64和在线识别字体网站分享
  16. C++ 模板中的类型获取(一)
  17. 如何通过IP找到地址?
  18. 【软考系统架构设计师】2009年下系统架构师综合知识历年真题
  19. 如何用Windows命令提示符(cmd.exe)进入指定目录
  20. SQL*Plus 系统变量之7 - BLO[CKTERMINATOR]

热门文章

  1. 五尺天涯 经典台词:岁月易流逝,珍惜眼前人。
  2. 微信对账单--每日定时任务获取昨日微信支付账单
  3. GVINS / VINS-mono运行报错:undefined symbol: _ZN6google21kLogSiteUninitializedE,重新安装ceres可以解决
  4. 机械原理(机电)_简要问答_笔记
  5. 强大的web打印插件--Lodop
  6. [洛谷]P3374 【模板】树状数组 1 (#树状数组)
  7. Freda的越野跑(openjudge)
  8. 北华大学c语言题库百度云,北华大学C语言题库精简打印版(全).doc
  9. 大话设计模式三之单一职责原则、开放-封闭原则、依赖倒置原则、里氏代换原则
  10. Python爬虫:中国结算,关于新开股票账户数等参数数据的爬取