Всего в Java существует 4 типа ссылок (на самом деле есть и другие типы ссылок, такие как FinalReference): сильная ссылка, мягкая ссылка, слабая ссылка, виртуальная ссылка. Среди них сильные ссылки — это то, что мы часто используем.Object a = new Object();
В этой форме в Java нет соответствующего класса Reference.
В этой статье в основном анализируется реализация мягких ссылок, слабых ссылок и виртуальных ссылок.Эти три типа ссылок унаследованы от класса Reference, и основная логика также находится в Reference.
Больше статей смотрите в личном блоге:GitHub.com/farmer Джон Брат…
вопрос
Перед разбором задать несколько вопросов?
1. Введение мягких ссылок в большинстве статей в интернете заключается в том, что они будут перерабатываться при нехватке памяти.Как определяется нехватка памяти? Что не хватает памяти?
2. Введение виртуальных ссылок в большинстве статей в Интернете таково: виртуальные ссылки не определяют жизненный цикл объектов. В основном используется для отслеживания активности объектов, утилизируемых сборщиком мусора. Это действительно так?
3. В каких сценариях используются виртуальные ссылки в Jdk?
Reference
Давайте взглянемReference.java
несколько полей в
public abstract class Reference<T> {
//引用的对象
private T referent;
//回收队列,由使用者在Reference的构造函数中指定
volatile ReferenceQueue<? super T> queue;
//当该引用被加入到queue中的时候,该字段被设置为queue中的下一个元素,以形成链表结构
volatile Reference next;
//在GC时,JVM底层会维护一个叫DiscoveredList的链表,存放的是Reference对象,discovered字段指向的就是链表中的下一个元素,由JVM设置
transient private Reference<T> discovered;
//进行线程同步的锁对象
static private class Lock { }
private static Lock lock = new Lock();
//等待加入queue的Reference对象,在GC时由JVM设置,会有一个java层的线程(ReferenceHandler)源源不断的从pending中提取元素加入到queue
private static Reference<Object> pending = null;
}
Жизненный цикл объекта Reference выглядит следующим образом:
Он в основном разделен на две части: собственный уровень и уровень Java.
Уровень Native добавляет объект Reference, который необходимо повторно использовать, в DiscoveredList во время GC (код находится вreferenceProcessor.cpp
серединаprocess_discovered_references
метод), а затем переместите элементы DiscoveredList в PendingList (код находится вreferenceProcessor.cpp
серединаenqueue_discovered_ref_helper
метод), глава списка PendingList является ожидающим объектом в классе Reference. Конкретный код анализироваться не будет Заинтересованные студенты могут прочитать эту статьюстатья.
Посмотрите на код слоя Java
private static class ReferenceHandler extends Thread {
...
public void run() {
while (true) {
tryHandlePending(true);
}
}
}
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
synchronized (lock) {
if (pending != null) {
r = pending;
//如果是Cleaner对象,则记录下来,下面做特殊处理
c = r instanceof Cleaner ? (Cleaner) r : null;
//指向PendingList的下一个对象
pending = r.discovered;
r.discovered = null;
} else {
//如果pending为null就先等待,当有对象加入到PendingList中时,jvm会执行notify
if (waitForNotify) {
lock.wait();
}
// retry if waited
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;
}
Процесс относительно прост: он заключается в непрерывном извлечении элементов из PendingList, а затем добавлении их в ReferenceQueue.Разработчик может воспринять событие повторного использования объекта из элемента опроса в ReferenceQueue.
Кроме того, следует отметить, что для объектов типа Cleaner (унаследованных от виртуальных ссылок) будет дополнительная обработка: при переработке объекта, на который он указывает, будет вызываться метод clean, который в основном используется для соответствующей утилизации ресурсов .В памяти вне кучи DirectByteBuffer Cleaner используется для повторного использования памяти вне кучи, что также является типичным применением виртуальных ссылок в java.
Прочитав реализацию Reference, давайте взглянем на различия в нескольких классах реализации.
SoftReference
public class SoftReference<T> extends Reference<T> {
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;
}
}
Реализация мягких ссылок очень проста, есть еще два поля:clock
а такжеtimestamp
.clock
Это статическая переменная, и в этом поле будет установлено текущее время каждый раз, когда происходит сборка мусора.timestamp
Полю будет присваиваться значение каждый раз, когда вызывается метод get.clock
(если не равны и объект не был собран).
Какова роль этих двух полей? Какое это имеет отношение к мягким ссылкам, которые восстанавливаются, когда памяти недостаточно?
Они также должны смотреть на исходный код JVM, потому что решение о том, нужно ли перерабатывать объект, реализуется в 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)
{
...
//还记得上文提到过的DiscoveredList吗?refs_lists就是DiscoveredList。
//对于DiscoveredList的处理分为几个阶段,SoftReference的处理就在第一阶段
...
for (uint i = 0; i < _max_num_q; i++) {
process_phase1(refs_lists[i], policy,
is_alive, keep_alive, complete_gc);
}
...
}
//该阶段的主要目的就是当内存足够时,将对应的SoftReference从refs_list中移除。
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 which softly reachable refs should be kept alive.
while (iter.has_next()) {
iter.load_ptrs(DEBUG_ONLY(!discovery_is_atomic() /* allow_null_referent */));
//判断引用的对象是否存活
bool referent_is_dead = (iter.referent() != NULL) && !iter.is_referent_alive();
//如果引用的对象已经不存活了,则会去调用对应的ReferencePolicy判断该对象是不时要被回收
if (referent_is_dead &&
!policy->should_clear_reference(iter.obj(), _soft_ref_timestamp_clock)) {
if (TraceReferenceGC) {
gclog_or_tty->print_cr("Dropping reference (" INTPTR_FORMAT ": %s" ") by policy",
(void *)iter.obj(), iter.obj()->klass()->internal_name());
}
// Remove Reference object from list
iter.remove();
// Make the Reference object active again
iter.make_active();
// keep the referent around
iter.make_referent_alive();
iter.move_to_next();
} else {
iter.next();
}
}
...
}
refs_lists
Определенный тип ссылки (виртуальная ссылка, мягкая ссылка, слабая ссылка и т. д.), найденный этим GC, сохраняется в GC, иprocess_discovered_reflist
Функция метода заключается в удалении объектов, не нуждающихся в переработке, изrefs_lists
удаленный,refs_lists
Последние оставшиеся элементы — это все элементы, которые необходимо переработать, и, наконец, первый элемент будет назначен упомянутому выше.Reference.java#pending
поле.
Существует 4 реализации ReferencePolicy: NeverClearPolicy, AlwaysClearPolicy, LRUCurrentHeapPolicy, LRUMaxHeapPolicy. Среди них NeverClearPolicy всегда возвращает false, что означает, что SoftReference никогда не будет переработан.Этот класс не используется в JVM, а AlwaysClearPolicy всегда возвращает true.referenceProcessor.hpp#setup
В методе можно задать политику AlwaysClearPolicy, а когда будет использоваться AlwaysClearPolicy, можете изучить сами, если интересно.
Методы should_clear_reference для LRUCurrentHeapPolicy и LRUMaxHeapPolicy точно такие же:
bool LRUMaxHeapPolicy::should_clear_reference(oop p,
jlong timestamp_clock) {
jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p);
assert(interval >= 0, "Sanity check");
// The interval will be zero if the ref was accessed since the last scavenge/gc.
if(interval <= _max_interval) {
return false;
}
return true;
}
timestamp_clock
статическое поле SoftReferenceclock
,java_lang_ref_SoftReference::timestamp(p)
соответствует полюtimestamp
. Если есть вызов с момента последнего GCSoftReference#get
,interval
Значение равно 0, иначе это разница во времени между несколькими GC.
_max_interval
Он представляет собой критическое значение, и его значение различается между LRUCurrentHeapPolicy и LRUMaxHeapPolicy.
void LRUCurrentHeapPolicy::setup() {
_max_interval = (Universe::get_heap_free_at_last_gc() / M) * SoftRefLRUPolicyMSPerMB;
assert(_max_interval >= 0,"Sanity check");
}
void LRUMaxHeapPolicy::setup() {
size_t max_heap = MaxHeapSize;
max_heap -= Universe::get_heap_used_at_last_gc();
max_heap /= M;
_max_interval = max_heap * SoftRefLRUPolicyMSPerMB;
assert(_max_interval >= 0,"Sanity check");
}
вSoftRefLRUPolicyMSPerMB
Значение по умолчанию — 1000. Метод расчета первого связан с доступным размером кучи после последней сборки мусора, а метод расчета последнего связан с (размером кучи — размером использования кучи при последней сборке мусора).
Увидев это, вы узнаете, когда SoftReference восстанавливается, и это связано с используемой политикой (по умолчанию должно быть LRUCurrentHeapPolicy), доступным размером кучи и временем, когда SoftReference в последний раз вызывал метод get.
WeakReference
public class WeakReference<T> extends Reference<T> {
public WeakReference(T referent) {
super(referent);
}
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
Видно, что WeakReference наследует только Reference на уровне Java без каких-либо изменений. Когда референтное поле имеет значение null? Чтобы прояснить этот вопрос, давайте посмотрим на упомянутый вышеprocess_discovered_reflist
метод:
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)
{
...
//Phase 1:将所有不存活但是还不能被回收的软引用从refs_lists中移除(只有refs_lists为软引用的时候,这里policy才不为null)
if (policy != NULL) {
if (mt_processing) {
RefProcPhase1Task phase1(*this, refs_lists, policy, true /*marks_oops_alive*/);
task_executor->execute(phase1);
} else {
for (uint i = 0; i < _max_num_q; i++) {
process_phase1(refs_lists[i], policy,
is_alive, keep_alive, complete_gc);
}
}
} else { // policy == NULL
assert(refs_lists != _discoveredSoftRefs,
"Policy must be specified for soft references.");
}
// Phase 2:
// 移除所有指向对象还存活的引用
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);
}
}
// Phase 3:
// 根据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);
}
}
return 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
//将Reference的referent字段置为null,之后会被GC回收
iter.clear_referent();
} else {
// keep the referent around
//标记引用的对象为存活,该对象在这次GC将不会被回收
iter.make_referent_alive();
}
...
}
...
}
Является ли это слабой ссылкой или другими типами ссылок, операция установки референта поля в значение null происходит вprocess_phase3
, а конкретное поведение определяетсяclear_referent
определяется стоимостью. а такжеclear_referent
Значение относительно ссылочного типа.
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
// Soft references
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);
}
update_soft_ref_master_clock();
// Weak references
size_t weak_count = 0;
{
GCTraceTime tt("WeakReference", trace_time, false, gc_timer);
weak_count =
process_discovered_reflist(_discoveredWeakRefs, NULL, true,
is_alive, keep_alive, complete_gc, task_executor);
}
// Final references
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 references
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);
}
...
}
Как видите, для мягких ссылок и слабых ссылокclear_referent
Все переданные поля верны, что также соответствует нашим ожиданиям: после того, как объект станет недостижимым, поле ссылки будет установлено в null, а затем объект будет восстановлен (для мягких ссылок, если памяти достаточно, в На этапе 1 соответствующая ссылка будет удалена из списка refs_list, а набор refs_list будет пустым на этапе 3).
Но для финальных ссылок и фантомных ссылок,clear_referent
Переданное поле имеет значение false, что означает, что объекты, на которые ссылаются эти два ссылочных типа,Если нет другой дополнительной обработки, пока ссылочный объект жив, ссылочный объект не будет переработан.. Окончательные ссылки связаны с тем, переопределяет ли объект метод finalize, что выходит за рамки анализа этой статьи.Далее рассмотрим фантомные ссылки.
PhantomReference
public class PhantomReference<T> extends Reference<T> {
public T get() {
return null;
}
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
Вы можете видеть, что метод get виртуальной ссылки всегда возвращает null, давайте посмотрим демонстрацию.
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);
}
Как видно из приведенного выше кода, виртуальные ссылки могут получать «уведомление», когда указанный объект недоступен (фактически, все классы, наследующие References, имеют эту функцию).Следует отметить, что после завершения GC,phanRef.referent по-прежнему указывает на ранее созданный объект, что означает, что объект объекта не был переработан!
Причина этого явления была упомянута в конце предыдущего раздела:对于Final references和 Phantom references,
clear_referent字段传入的时false,也就意味着被这两种引用类型引用的对象,如果没有其他额外处理,在GC中是不会被回收的。
Для фантомных ссылок, отrefQueue.remove();
После получения эталонного объекта вы можете вызватьclear
Метод принудительно разыменовывает связь между объектом и объектом, чтобы объект можно было повторно использовать при следующем сборе мусора.
End
В ответ на вопросы, поставленные в начале статьи, после прочтения анализа мы смогли дать ответы:
1. Мы часто видим введение мягких ссылок в Интернете: они будут переработаны только при нехватке памяти Как определяется нехватка памяти? Почему он вызывается из памяти?
Мягкие ссылки будут восстановлены при нехватке памяти.Определение недостаточности памяти связано со временем получения ссылочного объекта и текущим размером доступной памяти кучи.Формула расчета также приведена выше.
2. Внедрение виртуальной ссылки в Интернете это: она только по названию отличается от других ссылок, виртуальная ссылка не определяет жизненный цикл объекта. В основном используется для отслеживания активности объектов, утилизируемых сборщиком мусора. Это действительно так?
Строго говоря, виртуальные ссылки повлияют на жизненный цикл объектов, и если ничего не делать, пока виртуальные ссылки не будут переработаны, объекты, на которые они ссылаются, никогда не будут переработаны. Таким образом, в общем, после получения объекта PhantomReference из ReferenceQueue, если объект PhantomReference не будет переработан (например, на него ссылаются другие объекты, доступные для GC ROOT), вам необходимо вызватьclear
Метод освобождает ссылочную связь между PhantomReference и объектом, на который он ссылается.
3. В каких сценариях используются виртуальные ссылки в Jdk?
DirectByteBuffer — это подкласс с виртуальной ссылкой.Cleaner.java
Чтобы добиться восстановления памяти вне кучи, я напишу статью, в которой расскажу о внутренней и внешней памяти вне кучи.
Ps: Я недавно ищу работу, поэтому уже больше полумесяца не писал статьи.Изначально хотел просто записать несколько пунктов процитированных на Java, но когда написал, то понял, что Я не мог прояснить задействованные точки знаний, поэтому я снова написал так много. Надеюсь, вы можете получить удовлетворительное предложение!