El principio de los tipos de referencia de Java a menudo se pregunta en las entrevistas, y lo llevaré a analizarlo en profundidad.

Hay un total de 4 tipos de referencia en Java (de hecho, hay otros tipos de referencia como FinalReference): referencia fuerte, referencia suave, referencia débil, referencia virtual.

Entre ellos, la referencia fuerte es la forma de Objeto a = nuevo Objeto (); que usamos a menudo, y no hay una clase de Referencia correspondiente en Java.

Este artículo analiza principalmente la implementación de referencias blandas, referencias débiles y referencias virtuales.Estos tres tipos de referencias se heredan de la clase Reference, y la lógica principal también está en Reference.

pregunta

Antes del análisis, ¿hacer algunas preguntas?

1. La introducción de referencias blandas en la mayoría de los artículos en Internet es que se reciclarán cuando no haya suficiente memoria ¿Cómo se define memoria insuficiente? ¿Qué está fuera de la memoria?

2. La introducción de referencias virtuales en la mayoría de los artículos en Internet es: las referencias virtuales no determinan el ciclo de vida de los objetos. Se utiliza principalmente para realizar un seguimiento de la actividad de los objetos reclamados por el recolector de elementos no utilizados. ¿Es realmente?

3. ¿En qué escenarios se utilizan referencias virtuales en Jdk?

Referencia

Primero veamos varios campos en Reference.java

public abstract class Reference { //El objeto al que se hace referencia private T referent; //El usuario especifica la cola de reciclaje en el constructor de Reference volatile ReferenceQueue<? super T> queue; //Cuando la referencia se agrega a la cola, esto el campo se establece en el siguiente elemento de la cola para formar una estructura de lista vinculada volátil Referencia siguiente; //Durante GC, la capa inferior de la JVM mantendrá una lista vinculada llamada DiscoveredList, que almacena el objeto de referencia y los puntos de campo descubiertos a la lista enlazada El siguiente elemento de la JVM se establece como referencia privada transitoria descubierta; //El objeto de bloqueo para la sincronización de subprocesos static private class Lock { } private static Lock lock = new Lock(); //El objeto de referencia que espera ser agregado a la cola, lo establece el GC durante la configuración de la JVM del GC, habrá un subproceso de capa Java (ReferenceHandler) que extraerá continuamente elementos de los pendientes y los agregará a la cola . Referencia estática privada pendiente = nulo; }













El ciclo de vida de un objeto de referencia es el siguiente:

imagen.png

Se divide principalmente en dos partes: capa nativa y capa Java.

La capa nativa agrega los objetos de referencia que deben reciclarse a DiscoveredList durante GC (el código está en el método process_discovered_references en referenceProcessor.cpp) y luego mueve los elementos de DiscoveredList a PendingList (el código está en el método enqueue_discovered_ref_helper en referenceProcessor.cpp), el equipo PendingList El primero es el objeto pendiente en la clase Reference.

Mire el código de la capa de Java
clase estática privada ReferenceHandler extends Thread { public void run() { while (true) { tryHandlePending(true); } } } static boolean tryHandlePending(boolean waitForNotify) { Reference r; Cleaner c; try { sincronizado (bloqueo) { if (pendiente! = nulo) { r = pendiente; //Si es un objeto Limpiador, regístrelo y realice un procesamiento especial a continuación c = r instancia de Limpiador? (Limpiador) r: nulo; //Punto a la parte inferior de PendingList Un objeto pendiente = r.discovered; r.discovered = null; } else { //Si el objeto pendiente es nulo, espere primero. Cuando se agrega un objeto a PendingList, jvm ejecutará la notificación si (waitForNotify) {






















bloquear.esperar();
}
// reintentar si esperó
return waitForNotify;
}
}
}

    // 如果时CLeaner对象,则调用clean方法进行资源回收
    if (c != null) {
        c.clean();
        return true;
    }
    //将Reference加入到ReferenceQueue,开发者可以通过从ReferenceQueue中poll元素感知到对象被回收的事件。
    ReferenceQueue<? super Object> q = r.queue;
    if (q != ReferenceQueue.NULL) q.enqueue(r);
    return true;

}
El proceso es relativamente simple: consiste en extraer continuamente elementos de PendingList y luego agregarlos a ReferenceQueue. El desarrollador puede percibir el evento del objeto que se recicla a través del elemento de encuesta en ReferenceQueue.

También se debe tener en cuenta que habrá un procesamiento adicional para los objetos de tipo Cleaner (heredados de las referencias virtuales): cuando el objeto al que apunta se recicle, se llamará al método clean, que se utiliza principalmente para el reciclaje de los recursos correspondientes. La memoria fuera del montón DirectByteBuffer utiliza Cleaner para reciclar la memoria fuera del montón, que también es una aplicación típica de las referencias virtuales en Java.

Después de leer la implementación de Reference, echemos un vistazo a las diferencias en varias clases de implementación.

Referencia suave

SoftReference de clase pública extiende la referencia {

static private long clock;

private long timestamp;

public SoftReference(T referent) {
    super(referent);
    this.timestamp = clock;
}

public SoftReference(T referent, ReferenceQueue<? super T> q) {
    super(referent, q);
    this.timestamp = clock;
}

public T get() {
    T o = super.get();
    if (o != null && this.timestamp != clock)
        this.timestamp = clock;
    return o;
}

}

La implementación de las referencias blandas es muy sencilla, con dos campos más: reloj y marca de tiempo. clock es una variable estática, y este campo se establece en la hora actual cada vez que se produce un GC. El campo de marca de tiempo se asignará al reloj cada vez que se llame al método get (si no es igual y el objeto no se recopila).

**¿Cuál es el papel de estos dos campos? ** ¿Qué tiene esto que ver con las referencias blandas que se reclaman cuando la memoria no es suficiente?

Estos también tienen que mirar el código fuente de la JVM, porque la decisión de si el objeto debe reciclarse se implementa en el GC.

size_t
ReferenceProcessor::process_discovered_reflist(
DiscoveredList refs_lists[],
ReferencePolicy* policy,
bool clear_referent,
BoolObjectClosure* is_alive,
OopClosure* keep_alive,
VoidClosure* complete_gc,
AbstractRefProcTaskExecutor* task_executor)
{ //¿Recuerda la lista descubierta mencionada anteriormente? refs_lists es DiscoveredList. //El procesamiento de DiscoveredList se divide en varias etapas, y el procesamiento de SoftReference se encuentra en la primera etapa ... for (uint i = 0; i < _max_num_q; i++) { process_phase1(refs_lists[i], policy, is_alive, keep_alive, complete_gc); } }









//El objetivo principal de esta etapa es eliminar la SoftReference correspondiente de refs_list cuando la memoria es suficiente.
void
ReferenceProcessor::process_phase1(DiscoveredList& refs_list,
ReferencePolicy* policy,
BoolObjectClosure* is_alive,
OopClosure* keep_alive,
VoidClosure* complete_gc) {

DiscoveredListIterator iter(refs_list, keep_alive, is_alive);
// Decide qué referencias fácilmente alcanzables deben mantenerse vivas.
while (iter.has_next()) { iter.load_ptrs(DEBUG_ONLY(!discovery_is_atomic() /* allow_null_referent */)); / / Determinar si el objeto al que se hace referencia está vivo bool referent_is_dead = (iter.referent() != NULL) && !iter.is_referent_alive(); //Si el objeto al que se hace referencia ya no está vivo, llamará a la ReferencePolicy correspondiente para determinar si el el objeto se va a reciclar de vez en cuando if (referent_is_dead && !policy->should_clear_reference(iter.obj(), _soft_ref_timestamp_clock)) { if (TraceReferenceGC) { gclog_or_tty->print_cr(“Descartando referencia(” INTPTR_FORMAT “: %s” “) por política” , (void *)iter.obj(), iter.obj()->klass()->internal_name()); }










// Eliminar objeto de referencia de la lista
iter.remove();
// Hacer que el objeto de referencia vuelva a estar activo
iter.make_active();
// mantener el referente alrededor
de iter.make_referent_alive();
iter.mover_al_siguiente();
} más { iter.siguiente(); } } }




refs_lists almacena un cierto tipo de referencia (referencia virtual, referencia suave, referencia débil, etc.) descubierto por este GC, y la función del método process_discovered_reflist es eliminar objetos que no necesitan ser reciclados de refs_lists, y los últimos elementos restantes de refs_lists Todos los elementos que necesitan ser reciclados, y finalmente el primer elemento será asignado al campo Reference.java#pending mencionado anteriormente.

Hay 4 implementaciones de ReferencePolicy: NeverClearPolicy, AlwaysClearPolicy, LRUCurrentHeapPolicy, LRUMaxHeapPolicy.

Entre ellos, NeverClearPolicy siempre devuelve falso, lo que significa que SoftReference nunca se reciclará. En la JVM, esta clase no se usa y AlwaysClearPolicy siempre devuelve verdadero. En el método referenceProcessor.hpp#setup, puede establecer la política en AlwaysClearPolicy. En cuanto a cuándo se utilizará AlwaysClearPolicy, cualquier persona interesada puede hacer su propia investigación.

Los métodos should_clear_reference de LRUCurrentHeapPolicy y LRUMaxHeapPolicy son exactamente iguales:

bool LRUMaxHeapPolicy::should_clear_reference(oop p,
jlong ​​timestamp_clock) { jlong ​​interval = timestamp_clock - java_lang_ref_SoftReference::timestamp§; afirmar (intervalo> = 0, "Comprobación de cordura");

// El intervalo será cero si se accedió a la referencia desde el último barrido/gc.
if(intervalo <= _max_interval) { return false; }

devolver verdadero;
}

timestamp_clock es el reloj de campo estático de SoftReference, y java_lang_ref_SoftReference::timestamp§ corresponde al campo timestamp. Si se llama a SoftReference#get después del último GC, el valor del intervalo es 0; de lo contrario, es la diferencia de tiempo entre varios GC.

_max_interval representa un valor crítico y su valor es diferente entre LRUCurrentHeapPolicy y LRUMaxHeapPolicy.

void LRUCurrentHeapPolicy::setup() { _max_interval = (Universe::get_heap_free_at_last_gc() / M) * SoftRefLRUPolicyMSPerMB; afirmar (_max_interval> = 0, "Comprobación de cordura"); }


void LRUMaxHeapPolicy::setup() { size_t max_heap = MaxHeapSize; max_heap -= Universo::get_heap_used_at_last_gc(); montón_máximo /= M;


_max_interval = max_heap * SoftRefLRUPolicyMSPerMB;
afirmar (_max_interval> = 0, "Comprobación de cordura");
}

Entre ellos, SoftRefLRUPolicyMSPerMB tiene como valor predeterminado 1000. El método de cálculo del primero está relacionado con el tamaño de almacenamiento dinámico disponible después del último GC, y el método de cálculo del último está relacionado con (tamaño del almacenamiento dinámico - tamaño de uso del almacenamiento dinámico en el último GC). Aquí recomiendo un círculo de intercambio de aprendizaje de arquitectura para todos. Guía de estudio de comunicación Pseudo Xin: 1253431195 (hay muchas preguntas y respuestas de entrevistas), que compartirá algunas grabaciones de video grabadas por arquitectos senior: Spring, MyBatis, Netty análisis de código fuente, alta concurrencia, alto rendimiento, arquitectura de microservicio distribuida The El principio de optimización del rendimiento de JVM, la arquitectura distribuida, etc. se han convertido en el sistema de conocimiento necesario para los arquitectos. También puede recibir recursos de aprendizaje gratuitos, que actualmente se están beneficiando mucho.

Al ver esto, sabrá cuándo se reclama SoftReference y está relacionado con la política utilizada (el valor predeterminado debe ser LRUCurrentHeapPolicy), el tamaño de almacenamiento dinámico disponible y la hora en que SoftReference llamó por última vez al método get.

Referencia débil

public class WeakReference extiende Referencia {

public WeakReference(T referent) {
    super(referent);
}

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

}

Se puede ver que WeakReference solo hereda Reference en la capa de Java sin ningún cambio. ¿Cuándo se establece el campo de referencia en nulo? Para resolver esto, veamos el método process_discovered_reflist mencionado anteriormente:

size_t
ReferenceProcessor::process_discovered_reflist(
DiscoveredList refs_lists[],
ReferencePolicy* policy,
bool clear_referent,
BoolObjectClosure* is_alive,
OopClosure* keep_alive,
VoidClosure* complete_gc,
AbstractRefProcTaskExecutor* task_executor)
{

//Fase 1: Eliminar todas las referencias blandas que no están activas pero que no se pueden reciclar de refs_lists (solo cuando refs_lists es una referencia blanda, la política aquí no es nula)
if (policy != NULL) { if (mt_processing) { RefProcPhase1Task phase1 (*this, refs_lists, policy, true/ marks_oops_alive /); task_executor->execute(fase1); } else { for (uint i = 0; i < _max_num_q; i++) { process_phase1(refs_lists[i], policy , is_alive, keep_alive, complete_gc); } } } else { // política == NULL afirmar(refs_lists != _discoveredSoftRefs, “Se debe especificar la política para las referencias blandas.”); }












// Fase 2:
// Eliminar todas las referencias a objetos que todavía están
activos if (mt_processing) { RefProcPhase2Task phase2(*this, refs_lists, !discovery_is_atomic() / marks_oops_alive /); task_executor->execute(phase2); } else { for (uint i = 0; i < _max_num_q; i++) { process_phase2(refs_lists[i], is_alive, keep_alive, complete_gc); } }






// Fase 3:
// Determinar si reciclar objetos muertos según el valor de clear_referent
if (mt_processing) { RefProcPhase3Task phase3(*this, refs_lists, clear_referent, true / marks_oops_alive /); task_executor->execute(phase3); } else { for (uint i = 0; i < _max_num_q; i++) { process_phase3(refs_lists[i], clear_referent, is_alive, keep_alive, complete_gc); } }







devuelve total_list_count;
}

void
ReferenceProcessor::process_phase3(DiscoveredList& refs_list,
bool clear_referent,
BoolObjectClosure* is_alive,
OopClosure* keep_alive,
VoidClosure* complete_gc) { ResourceMark rm; DiscoveredListIterator iter(refs_list, keep_alive, is_alive); while (iter.has_next()) { iter.update_discovered (); iter.load_ptrs(DEBUG_ONLY(false /* allow_null_referent */)); if (clear_referent) { // NULL out referent pointer //Establezca el campo de referencia de Reference en nulo, y luego será reciclado por GC iter. clear_referent() ; } else { // mantener el referente alrededor // marcar el objeto al que se hace referencia como vivo, el objeto no se reciclará en este GC iter.make_referent_alive(); }















}

}

Tanto si se trata de una referencia débil como de otros tipos de referencia, la operación de establecer el referente de campo en nulo se produce en process_phase3, y el comportamiento específico está determinado por el valor de clear_referent. El valor de clear_referent está relacionado con el tipo de referencia.

ReferenceProcessorStats ReferenceProcessor::process_discovered_references(
BoolObjectClosure* is_alive,
OopClosure* keep_alive,
VoidClosure* complete_gc,
AbstractRefProcTaskExecutor* task_executor,
GCTimer* gc_timer) { NOT_PRODUCT(verify_ok_to_handle_reflists()); //process_discovered_reflist方法的第3个字段就是clear_referent // Referencias blandas size_t soft_count = 0; { GCTraceTime tt(“SoftReference”, trace_time, false, gc_timer); soft_count = process_discovered_reflist(_discoveredSoftRefs, _current_soft_ref_policy, true, is_alive, keep_alive, complete_gc, task_executor); }










actualizar_soft_ref_master_clock();

// Referencias débiles
tamaño_t débil_cuenta = 0;
{ GCTraceTime tt(“WeakReference”, trace_time, false, gc_timer); débil_cuenta = process_discovered_reflist(_discoveredWeakRefs, NULL, true, is_alive, keep_alive, complete_gc, task_executor); }




// Referencias finales
size_t final_count = 0;
{ GCTraceTime tt(“FinalReference”, trace_time, false, gc_timer); final_count = process_discovered_reflist(_discoveredFinalRefs, NULL, false, is_alive, keep_alive, complete_gc, task_executor); }




// Phantom referencias
size_t phantom_count = 0;
{ GCTraceTime tt(“PhantomReference”, trace_time, false, gc_timer); phantom_count = process_discovered_reflist(_discoveredPhantomRefs, NULL, false, is_alive, keep_alive, complete_gc, task_executor); } } Como puede ver , Para las referencias blandas y las referencias débiles, el campo clear_referent se pasa como verdadero, lo que también está en línea con nuestras expectativas: después de que el objeto sea inalcanzable, el campo de referencia se establecerá en nulo y luego el objeto se recuperará (para las referencias blandas). referencias, si hay suficiente memoria, en la Fase 1, la referencia relevante se eliminará de refs_list, y en la Fase 3, refs_list será un conjunto vacío).







Pero para las referencias finales y las referencias fantasma, el campo clear_referent se pasa como falso, lo que significa que los objetos a los que hacen referencia estos dos tipos de referencia, si no hay otro procesamiento adicional, siempre que el objeto de referencia siga vivo, el objeto al que se hace referencia no ser reciclado. Las referencias finales están relacionadas con si el objeto anula el método de finalización, que no está dentro del alcance del análisis de este artículo. A continuación, echemos un vistazo a las referencias de Phantom.

Referenciafantasma

public class PhantomReference<T> extends Reference<T> {

    public T get() {
        return null;
    }

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

}

Puedes ver que el método get de la referencia virtual siempre devuelve nulo, veamos una demostración.

  public static void demo() throws InterruptedException {
        Object obj = new Object();
        ReferenceQueue<Object> refQueue =new ReferenceQueue<>();
        PhantomReference<Object> phanRef =new PhantomReference<>(obj, refQueue);

        Object objg = phanRef.get();
        //这里拿到的是null
        System.out.println(objg);
        //让obj变成垃圾
        obj=null;
        System.gc();
        Thread.sleep(3000);
        //gc后会将phanRef加入到refQueue中
        Reference<? extends Object> phanRefP = refQueue.remove();
         //这里输出true
        System.out.println(phanRefP==phanRef);
    }

Como se puede ver en el código anterior, una referencia virtual puede recibir una 'notificación' cuando el objeto señalado es inalcanzable (de hecho, todas las clases que heredan Referencias tienen esta función). Cabe señalar que después de que se completa el GC, phanRef .referent aún apunta al objeto de creación anterior, lo que significa que el objeto Object no se ha reciclado.

La razón de este fenómeno se ha mencionado al final de la sección anterior: para referencias finales y referencias fantasma, el campo clear_referent se pasa como falso, lo que significa que los objetos referenciados por estos dos tipos de referencia, si no hay otro tipo de referencia adicional procesamiento, no se reciclará en el GC.

Para las referencias virtuales, después de obtener el objeto de referencia de refQueue.remove();, puede llamar al método clear para eliminar a la fuerza la relación entre la referencia y el objeto, de modo que el objeto se pueda reciclar la próxima vez que se pueda GCed.

Final

En respuesta a las preguntas planteadas al inicio del artículo, tras leer el análisis, hemos podido dar respuestas:

1. A menudo vemos la introducción de referencias blandas en Internet: solo se reciclará cuando no haya suficiente memoria ¿Cómo se define memoria insuficiente? ¿Por qué se llama fuera de la memoria?

Las referencias blandas se recuperarán cuando la memoria sea insuficiente. La definición de memoria insuficiente está relacionada con el tiempo de obtención del objeto de referencia y el tamaño de memoria disponible del montón actual. La fórmula de cálculo también se proporciona arriba.

2. La introducción de la referencia virtual en Internet es: es solo de nombre, a diferencia de otras referencias, la referencia virtual no determina el ciclo de vida del objeto. Se utiliza principalmente para realizar un seguimiento de la actividad de los objetos reclamados por el recolector de elementos no utilizados. ¿Es realmente?

Estrictamente hablando, las referencias virtuales afectarán el ciclo de vida de los objetos, si no se hace nada, mientras las referencias virtuales no se reciclen, los objetos a los que se refieren nunca se reciclarán. Entonces, en general, después de obtener el objeto PhantomReference de ReferenceQueue, si el objeto PhantomReference no se reciclará (por ejemplo, otros objetos a los que se puede acceder mediante GC ROOT hacen referencia a él), debe llamar al método clear para liberar la relación de referencia entre PhantomReference y su objeto de referencia.

3. ¿En qué escenarios se utilizan referencias virtuales en Jdk?

En DirectByteBuffer, la subclase de referencia virtual Cleaner.java se usa para lograr la recuperación de memoria fuera del montón Escribiré un artículo para hablar sobre el interior y el exterior de la memoria fuera del montón.

Supongo que te gusta

Origin blog.csdn.net/m0_54828003/article/details/127197202
Recomendado
Clasificación