Разговор о ссылочных типах в java

Java задняя часть
Разговор о ссылочных типах в java

Эта статья участвовала в "Проект «Звезда раскопок»”, чтобы выиграть творческий подарочный пакет и бросить вызов творческим поощрительным деньгам.

Введение в ссылочные типы

Ссылка в java на самом деле похожа на имя или псевдоним объекта.Объект запрашивает часть пространства в памяти для сохранения данных.В зависимости от размера объекта пространство, которое ему может потребоваться, варьируется. При доступе к объекту он не будет напрямую обращаться к данным объекта в памяти, а будет обращаться к нему по ссылке. Ссылка также является типом данных, который можно рассматривать как что-то похожее на указатель в языке C++, который указывает адрес объекта в памяти, но мы не можем наблюдать, что это за адрес.

Если мы определим более одной ссылки на один и тот же объект, то эти ссылки не будут одинаковыми, потому что ссылка также является типом данных и требует определенного пространства памяти (стека, пространства стека) для сохранения. Но их значения одинаковы, оба указывают на расположение одного и того же объекта в памяти (куча, пространство кучи). Ссылочные типы в java делятся на две категории: типы значений и ссылочные типы, где типы значений — это базовые типы данных, такие как типы int, double, а ссылочные типы — это все типы, кроме базовых типов данных. После JDK.1.2 Java расширила понятие ссылки и разделила ссылку на четыре типа: Сильная ссылка, Мягкая ссылка, Слабая ссылка и Фантомная ссылка.Сила 4 ссылок последовательно уменьшается.

сильная цитата

Сильные ссылки являются наиболее часто используемыми ссылками. Если объект имеет сильную ссылку, сборщик мусора никогда не соберет его. Когда места в памяти недостаточно, виртуальная машина Java скорее выдаст ошибку OutOfMemoryError, вызывающую ненормальное завершение программы, чем решит проблему нехватки памяти, произвольно освобождая объекты с сильными ссылками. Например: A a = новый A(). Сильные ссылки обладают следующими тремя характеристиками:

Строгая ссылка может напрямую обращаться к целевому объекту.

Объект, на который указывает сильная ссылка, не будет истребован системой в любое время.

Сильные ссылки могут привести к утечкам памяти.

Исходный код выглядит следующим образом:

/**
 * Final references, used to implement finalization
 */
class FinalReference<T> extends Reference<T> {

    public FinalReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

После того, как объект вновь создан, он по умолчанию использует тип строгой ссылки, который является типом универсальной ссылки на объект. Исходный код FinalReference имеет только одну пустую реализацию, что также означает, что сильные ссылки являются «типами ссылок по умолчанию».

Проблемы со сбором GC

Объект становится временной строгой ссылкой из-за ссылки Финализатора.Даже если других сильных ссылок нет, он не может быть немедленно переработан; Объект может быть перезапущен по крайней мере после двух GC, потому что он может быть перезапущен следующим GC только после того, как FinalizerThread выполнит метод finalize объекта, и он может испытать несколько GC в течение периода, но объект не выполнено. Метод финализации; Когда ресурсов ЦП не хватает, поток FinalizerThread может задержать выполнение метода finalize объекта из-за его низкого приоритета; Поскольку метод finalize объекта не выполнялся в течение длительного времени, это может привести к тому, что большинство объектов f попадут в старое поколение.В это время легко вызвать GC старого поколения или даже полный GC, время паузы GC значительно больше и даже приводит к OOM;

мягкая ссылка

Мягкие ссылки используются для описания некоторых «полезных, но не необходимых» объектов. Стратегия восстановления мягких ссылок немного отличается в разных реализациях JVM.JVM не только учитывает текущую ситуацию с памятью, но также учитывает недавнее использование и время создания референта, на который указывает мягкая ссылка, чтобы всесторонне решить, следует ли перерабатывать референт. . Мягкие ссылки содержат две переменные:

Timestamp: Timestamp обновляется каждый раз, когда требуется метод получения. JVM может использовать это поле, чтобы выбрать мягкие ссылки на очистку, но это не обязательно для этого.

clock: блокировка времени, обновляемая сборщиком мусора.

Таким образом, любой GC может использовать эти поля и определять стратегию очистки программных ссылок, например: очищать недавно созданные или недавно использованные программные ссылки последними. После JDK 1.2 для реализации программных ссылок предоставляется класс SoftReference. Мягкие ссылки могут использоваться для реализации кэшей, чувствительных к памяти. Мягкие ссылки можно использовать в сочетании с очередью ссылок (ReferenceQueue).Если объект, на который ссылается мягкая ссылка, утилизируется сборщиком мусора, виртуальная машина Java добавит мягкую ссылку в соответствующую очередь ссылок. Исходный код выглядит следующим образом:

/**
 * 软引用对象由垃圾收集器根据内存需要决定是否清除。软引用经常用于实现内存敏感的缓存。
 *
 * 假如垃圾收集器在某个时间确定对象是软可达的,此时它可以选择原子地清除
 * 指向该对象的所有软引用,以及从该对象通过强引用链连接的其他软可达对象的所有软引用。
 * 与时同时或者之后的某个时间,它会将注册了reference queues的新清除的软引用加入队列。
 *
 * 在虚拟机抛出OutOfMemoryError异常之前,将保证清除对软可达对象的所有软引用。
 * 不过,并没有对清除软引用的时间以及清除顺序施加强制约束。
 * 但是,鼓励虚拟机实现偏向不清除最近创建或最近使用的软引用。
 *
 * 该类的直接实例可用于实现简单的缓存。
 * 该类或其派生子类也可用于更大的数据结构以实现更复杂的高速缓存。
 * 只要软引用的引用对象还是强可达的,即还在实际使用中,软引用就不会被清除。
 * 因此,复杂的高速缓存可以通过持有对最近使用缓存对象的强引用来防止其被清除,
 * 而不常使用的剩余缓存对象由垃圾收集器决定是否清除。
 */
public class SoftReference&lt;T&gt; extends Reference&lt;T&gt; {	
    static private long clock;
    private long timestamp;
	
	// 返回对象的引用。如果该引用对象已经被程序或者垃圾收集器清除,则返回null。
	// 把最近一次垃圾回收时间赋值给timestamp
    public T get() {
        T o = super.get();
        if (o != null &amp;&amp; this.timestamp != clock)
            this.timestamp = clock;
        return o;
    }

}

Давайте посмотрим пример:

/**
 * 软引用在pending状态时,referent就已经是null了。
 *
 * 启动参数:-Xmx5m
 *
 */
public class SoftReference {

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

    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(3000);
        MyObject object = new MyObject();
        SoftReference<MyObject> softRef = new SoftReference(object, queue);
        new Thread(new CheckRefQueue()).start();

        object = null;
        System.gc();
        System.out.println("After GC : Soft Get = " + softRef.get());
        System.out.println("分配大块内存");

        /**
         * 总共触发了 3 次 full gc。第一次有System.gc();触发;第二次在在分配new byte[5*1024*740]时触发,然后发现内存不够,于是将softRef列入回收返回,接着进行了第三次full gc。
         */
        //byte[] b = new byte[5*1024*740];

        /**
         * 也是触发了 3 次 full gc。第一次有System.gc();触发;第二次在在分配new byte[5*1024*740]时触发,然后发现内存不够,于是将softRef列入回收返回,接着进行了第三次full gc。当第三次 full gc 后发现内存依旧不够用于分配new byte[5*1024*740],则就抛出了OutOfMemoryError异常。
         */
        byte[] b = new byte[5*1024*790];

        System.out.println("After new byte[] : Soft Get = " + softRef.get());
    }

    public static class CheckRefQueue implements Runnable {

        Reference<MyObject> obj = null;

        @Override
        public void run() {
            try {
                obj = (Reference<MyObject>) queue.remove();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (obj != null) {
                System.out.println("Object for softReference is " + obj.get());
            }

        }
    }

    public static class MyObject {

        @Override
        protected void finalize() throws Throwable {
            System.out.println("MyObject's finalize called");
            super.finalize();
        }

        @Override
        public String toString() {
            return "I am MyObject.";
        }
    }
}

слабая ссылка

Он используется для описания второстепенных объектов.Сила слабее, чем у мягких ссылок.Объекты, связанные со слабыми ссылками, могут существовать только до тех пор, пока не будет отправлена ​​следующая сборка мусора. Начало сбора заключается в том, что объекты, связанные только со слабыми ссылками, будут собираться независимо от того, достаточно ли текущей памяти. Когда сборщик мусора Java готов переработать объект, на который указывает WeakReference, перед вызовом метода finalize() объекта сам объект WeakReference будет добавлен в объект ReferenceQueue, и их можно будет получить с помощью метода poll(). из ReferenceQueue. Исходный код выглядит следующим образом:

/**
 * 弱引用对象不能阻止自身的引用被回收。
 * 弱引用常用于实现规范化映射(对象实例可以在程序的多个地方同时使用)。
 *
 * 假如垃圾收集器在某个时间点确定对象是弱可达的。那时它将原子地清除对该对象的所有弱引用
 * 以及该引用通过强引用或者软引用连接的所有其他弱可达对象的所有弱引用。
 * 同时,它将表明前面所指的所有弱可达对象都可以执行finalize方法。
 * 与此同时或之后某一个时间,它将注册了reference queues的那些新清除弱引用加入队列。
 */
public class WeakReference&lt;T&gt; extends Reference&lt;T&gt; {

	// 创建没有注册ReferenceQueue的弱引用
    public WeakReference(T referent) {
        super(referent);
    }

	// 创建注册了ReferenceQueue的弱引用
    public WeakReference(T referent, ReferenceQueue&lt;? super T&gt; q) {
        super(referent, q);
    }
}

фантомная ссылка

Фантомные ссылки — самые слабые из всех типов ссылок. Независимо от того, связан ли объект с виртуальной ссылкой, это никак не повлияет на жизненный цикл объекта, и экземпляр объекта не может быть получен через виртуальную ссылку. Единственная цель установки виртуальной ссылки для объекта — получение системного уведомления, когда объект утилизируется сборщиком мусора, который реализуется с помощью ReferenceQueue. Когда референт повторно используется gc, JVM автоматически добавляет сам виртуальный объект ссылки в ReferenceQueue, указывая, что референт, на который указывает ссылка, повторно используется. Затем вы можете получить ссылку, перейдя в очередь, и вы можете использовать ее для выполнения дополнительной работы по очистке. Вы можете использовать виртуальные ссылки для замены метода finalize объекта для освобождения ресурсов, что является более гибким и безопасным.

PhantomReference добавит его в объект ReferenceQueue только тогда, когда сборщик мусора Java фактически перезапустит объект, на который он указывает, чтобы можно было отследить уничтожение объекта. Здесь был вызван метод finalize() референтного объекта. Таким образом, конкретное использование отличается от двух предыдущих, оно должно передаваться в объекте ReferenceQueue. Когда объект, на который ссылается виртуальная ссылка, готов к сборке мусора, виртуальная ссылка добавляется в эту очередь. Исходный код выглядит следующим образом:

/**
 * 虚引用对象在被垃圾收集器检查到后加入reference queues队列,否则会被回收。
 * 虚引用最常用于实现比Java finalization机制更灵活的安排额外的清理工作。
 *
 * 如果垃圾收集器在某个时间点确定虚引用对象是虚可达的,那么在那个时间或之后某个时间它会将引用加入reference queues队列。
 *
 * 为了确保可回收对象保持不变,虚引用的引用无法使用:虚引用对象的get方法始终返回null。
 *
 * 与软引用和弱引用不同,当虚引用加入reference queues队列后垃圾收集器不会被自动清除。
 * 只通过虚引用可达的对象将保持不变,直到所有此类引用都被清除或自已变为不可达。
 */
public class PhantomReference&lt;T&gt; extends Reference&lt;T&gt; {

    // 由于不能通过虚引用访问对象,因此此方法始终返回null。
    public T get() {
        return null;
    }

    // 使用空ReferenceQueue队列创建一个虚引用没有意义:它的get方法总是返回null,
	// 并且由于它没有注册队列,所以也不会被加入队列有任何清理前的预处理操作。
    public PhantomReference(T referent, ReferenceQueue&lt;? super T&gt; q) {
        super(referent, q);
    }
}

Давайте посмотрим пример:

public class PhantomReferenceTest {

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

    public static void main(String[] args) throws InterruptedException {
        MyObject object = new MyObject();
        Reference<MyObject> phanRef = new PhantomReference<>(object, queue);
        System.out.println("创建的虚拟引用为: " + phanRef);
        new Thread(new CheckRefQueue()).start();

        object = null;

        int i = 1;
        while (true) {
            System.out.println("第" + i++ + "次GC");
            System.gc();
            TimeUnit.SECONDS.sleep(1);
        }

        /**
         * 在经过一次GC之后,系统找到了垃圾对象,并调用finalize()方法回收内存,但没有立即加入PhantomReference Queue中。因为MyObject对象重写了finalize()方法,并且该方法是一个非空实现,所以这里MyObject也是一个Final Reference。所以第一次GC完成的是Final Reference的事情。
         * 第二次GC时,该对象(即,MyObject)对象会真正被垃圾回收器进行回收,此时,将PhantomReference加入虚引用队列( PhantomReference Queue )。
         * 而且每次gc之间需要停顿一些时间,已给JVM足够的处理时间;如果这里没有TimeUnit.SECONDS.sleep(1); 可能需要gc到第5、6次才会成功。
         */

    }

    public static class MyObject {

        @Override
        protected void finalize() throws Throwable {
            System.out.println("MyObject's finalize called");
            super.finalize();
        }

        @Override
        public String toString() {
            return "I am MyObject";
        }
    }

    public static  class CheckRefQueue implements Runnable {

        Reference<MyObject> obj = null;

        @Override
        public void run() {
            try {
                obj = (Reference<MyObject>)queue.remove();
                System.out.println("删除的虚引用: " + obj + " , 虚引用的对象: " + obj.get());
                System.exit(0);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Суммировать

Уровни четырех ссылок Java от высокого к низкому:

Сильная ссылка > Мягкая ссылка > Слабая ссылка > Фантомная ссылка.

引用.PNG

Инструкции по розыгрышу

1. Это событие официально поддерживается Nuggets, подробности можно посмотретьnuggets.capable/post/701221…

2. Вы можете участвовать, комментируя содержание, связанное со статьей, и это должно быть связано с содержанием статьи!

3. Все статьи этого месяца будут участвовать в лотерее, и все желающие могут взаимодействовать еще больше!

4. В дополнение к официальной лотерее Наггетс, я также отправлю периферийные подарки (кружка и несколько значков Наггетс, кружка будет передана внимательным комментаторам, значки будут выбраны случайным образом, и количество будет увеличиваться в зависимости от количества комментариев).