Lecture du code source OpenJDK17-JVM-ZGC-Marquage simultané | Équipe technologique de JD Logistics

1. Introduction à ZGC

1.1 Introduction

ZGC est un garbage collector à faible latence et est à l'avant-garde de la technologie Java de garbage collection. Si vous comprenez ZGC, vous pouvez dire que vous comprenez la technologie de garbage collection la plus avancée de Java.

ZGC est en développement constant depuis son introduction en tant que fonctionnalité expérimentale dans le JDK11.

À partir du JDK14, ZGC commence à prendre en charge Windows.

Dans JDK15, ZGC n'est plus une fonctionnalité expérimentale et peut être officiellement mis en production.

Dans la dernière bibliothèque open source JDK, le code ZGC collecté par générations est apparu et il devrait être officiellement publié dans un avenir proche. Je pense que les performances de ZGC seront alors encore meilleures.



Figure 1 ZGC collecté par générations

Figure 1 ZGC collecté par générations

Comme indiqué ci-dessus, JDK21 possède déjà la fonctionnalité ZGC générationnel.

1.2 Caractéristiques du ZGC

1. Faible latence
2. Tas de grande capacité
3. Conseils de teinture
4. Lire la barrière

1.3 Phase de collecte des déchets



Figure 2 Processus de fonctionnement du ZGC

Figure 2 Processus de fonctionnement du ZGC

Comme le montre la figure ci-dessus, il y a principalement les étapes suivantes : marquage initial, marquage simultané/remappage simultané, réallocation préliminaire simultanée, réallocation initiale et réallocation simultanée. L'analyse principale cette fois est la partie "marquage simultané/remappage simultané" de le code source.

1.4 Lisez le code source en gardant des questions à l'esprit

1. Comment ZGC marque les pointeurs
2. Processus d'étiquetage tricolore ZGC
3. Comment ZGC évite-t-il les offres manquées en utilisant uniquement des barrières de lecture ?
4. Processus d'auto-guérison du pointeur pendant le processus de marquage ZGC
5. Processus de remappage simultané dans le marquage simultané ZGC

2.Code source

2.1 Entrée

L'entrée du code source de l'intégralité du ZGC est la fonction ZDriver::gc

void ZDriver::gc(const ZDriverRequest& request) {
  ZDriverGCScope scope(request);

  // Phase 1: Pause Mark Start
  pause_mark_start();

  // Phase 2: Concurrent Mark
  concurrent(mark);

  // Phase 3: Pause Mark End
  while (!pause_mark_end()) {
    // Phase 3.5: Concurrent Mark Continue
    concurrent(mark_continue);
  }

  // Phase 4: Concurrent Mark Free
  concurrent(mark_free);

  // Phase 5: Concurrent Process Non-Strong References
  concurrent(process_non_strong_references);

  // Phase 6: Concurrent Reset Relocation Set
  concurrent(reset_relocation_set);

  // Phase 7: Pause Verify
  pause_verify();

  // Phase 8: Concurrent Select Relocation Set
  concurrent(select_relocation_set);

  // Phase 9: Pause Relocate Start
  pause_relocate_start();

  // Phase 10: Concurrent Relocate
  concurrent(relocate);
}

Parmi eux, concurrent() est une définition de macro

// Macro to execute a termination check after a concurrent phase. Note
// that it's important that the termination check comes after the call
// to the function f, since we can't abort between pause_relocate_start()
// and concurrent_relocate(). We need to let concurrent_relocate() call
// abort_page() on the remaining entries in the relocation set.
#define concurrent(f)                 \
  do {                                \
    concurrent_##f();                 \
    if (should_terminate()) {         \
      return;                         \
    }                                 \
  } while (false)

On peut donc savoir que la fonction du marquage concurrent est concurrent_mark

void ZDriver::concurrent_mark() {
  ZStatTimer timer(ZPhaseConcurrentMark);
  ZBreakpoint::at_after_marking_started();
  //开始对整个堆进行标记
  ZHeap::heap()->mark(true /* initial */);
  ZBreakpoint::at_before_marking_completed();
}

2.2 Processus de notation simultané

2.2.1 Marquage simultané

Regardez ensuite ZHeap::heap()->mark

void ZHeap::mark(bool initial) {
  _mark.mark(initial);
}

plus bas

void ZMark::mark(bool initial) {
  if (initial) {
    ZMarkRootsTask task(this);
    _workers->run(&task);
  }

  ZMarkTask task(this);
  _workers->run(&task);
}

Un cadre de tâches est utilisé ici et la logique d'exécution des tâches se trouve dans ZMarkTask.

class ZMarkTask : public ZTask {
private:
  ZMark* const   _mark;
  const uint64_t _timeout_in_micros;

public:
  ZMarkTask(ZMark* mark, uint64_t timeout_in_micros = 0) :
      ZTask("ZMarkTask"),
      _mark(mark),
      _timeout_in_micros(timeout_in_micros) {
    _mark->prepare_work();
  }

  ~ZMarkTask() {
    _mark->finish_work();
  }

  virtual void work() {
    _mark->work(_timeout_in_micros);
  }
};

La fonction spécifique exécutée ici est work, regardez down_mark->work

void ZMark::work(uint64_t timeout_in_micros) {
  ZMarkCache cache(_stripes.nstripes());
  ZMarkStripe* const stripe = _stripes.stripe_for_worker(_nworkers, ZThread::worker_id());
  ZMarkThreadLocalStacks* const stacks = ZThreadLocalData::stacks(Thread::current());

  if (timeout_in_micros == 0) {
    work_without_timeout(&cache, stripe, stacks);
  } else {
    work_with_timeout(&cache, stripe, stacks, timeout_in_micros);
  }

  // Flush and publish stacks
  stacks->flush(&_allocator, &_stripes);

  // Free remaining stacks
  stacks->free(&_allocator);
}

Stripe est ici une bande marquée et stacks est une pile locale de thread.

Regardez la méthode work_with_timeout

void ZMark::work_with_timeout(ZMarkCache* cache, ZMarkStripe* stripe, ZMarkThreadLocalStacks* stacks, uint64_t timeout_in_micros) {
  ZStatTimer timer(ZSubPhaseMarkTryComplete);
  ZMarkTimeout timeout(timeout_in_micros);

  for (;;) {
    if (!drain(stripe, stacks, cache, &timeout)) {
      // Timed out
      break;
    }

    if (try_steal(stripe, stacks)) {
      // Stole work
      continue;
    }

    // Terminate
    break;
  }
}

Vous pouvez voir qu'il y a une boucle ici, qui continuera à récupérer les données de la bande marquée jusqu'à ce qu'elle soit terminée ou que le temps soit écoulé. Il s'agit de la boucle principale du marquage tricolore ZGC. Ensuite, regardez la fonction de drainage. Drain signifie vider. Ici, le pointeur est extrait de la pile et marqué jusqu'à ce que la pile soit vide.

template <typename T>
bool ZMark::drain(ZMarkStripe* stripe, ZMarkThreadLocalStacks* stacks, ZMarkCache* cache, T* timeout) {
  ZMarkStackEntry entry;

  // Drain stripe stacks
  while (stacks->pop(&_allocator, &_stripes, stripe, entry)) {
    //标记和跟随
    mark_and_follow(cache, entry);

    // Check timeout
    if (timeout->has_expired()) {
      // Timeout
      return false;
    }
  }

  // Success
  return !timeout->has_expired();
}

Vous pouvez voir que les données sont extraites de la pile, puis marquées et marquées de manière récursive jusqu'à ce que la pile soit vidée.

Puisqu'il y a un endroit pour prendre les données de la pile, il doit y avoir un endroit pour mettre les données dans la pile. Écrivons d'abord ce point, appelé « point d'enregistrement 1 ».

Ensuite, regardez la fonction mark_and_follow

void ZMark::mark_and_follow(ZMarkCache* cache, ZMarkStackEntry entry) {
  // Decode flags
  const bool finalizable = entry.finalizable();
  const bool partial_array = entry.partial_array();

  if (partial_array) {
    follow_partial_array(entry, finalizable);
    return;
  }

  // Decode object address and additional flags
  const uintptr_t addr = entry.object_address();
  const bool mark = entry.mark();
  bool inc_live = entry.inc_live();
  const bool follow = entry.follow();

  ZPage* const page = _page_table->get(addr);
  assert(page->is_relocatable(), "Invalid page state");

  // Mark
  //标记对象
  if (mark && !page->mark_object(addr, finalizable, inc_live)) {
    // Already marked
    return;
  }

  // Increment live
  if (inc_live) {
    // Update live objects/bytes for page. We use the aligned object
    // size since that is the actual number of bytes used on the page
    // and alignment paddings can never be reclaimed.
    const size_t size = ZUtils::object_size(addr);
    const size_t aligned_size = align_up(size, page->object_alignment());
    cache->inc_live(page, aligned_size);
  }

  // Follow
  if (follow) {
    if (is_array(addr)) {
      //递归处理数组
      follow_array_object(objArrayOop(ZOop::from_address(addr)), finalizable);
    } else {
      //递归处理对象
      follow_object(ZOop::from_address(addr), finalizable);
    }
  }
}

Il y a deux fonctions à examiner ici. Regardons d'abord page->mark_object, puis follow_object. Quant à follow_array_object, il est similaire à follow_object, nous ne le présenterons donc pas en détail.

Le code source page->mark_object est le suivant

inline bool ZPage::mark_object(uintptr_t addr, bool finalizable, bool& inc_live) {
  assert(ZAddress::is_marked(addr), "Invalid address");
  assert(is_relocatable(), "Invalid page state");
  assert(is_in(addr), "Invalid address");

  // Set mark bit
  const size_t index = ((ZAddress::offset(addr) - start()) >> object_alignment_shift()) * 2;
  return _livemap.set(index, finalizable, inc_live);
}

Vous pouvez voir qu'une carte est utilisée ici pour stocker les informations finalisables et inc_live (nouveau live) de toutes les adresses d'objet.

const size_t index = ((ZAddress::offset(addr) - start()) >> object_alignment_shift()) * 2;

À partir de ce code, la taille de la livemap est d'environ un tiers de la taille d'alignement des objets de l'ensemble du tas, puis multipliée par 2. Les informations stockées dans cette carte sont très importantes. Dans ZGC, le fait qu'une adresse ait été marquée et si un objet est vivant est jugé par les informations contenues dans cette carte.

Revenez à l'étape précédente et continuez à regarder la fonction follow_object

void ZMark::follow_object(oop obj, bool finalizable) {
  if (finalizable) {
    ZMarkBarrierOopClosure<true /* finalizable */> cl;
    obj->oop_iterate(&cl);
  } else {
    ZMarkBarrierOopClosure<false /* finalizable */> cl;
    obj->oop_iterate(&cl);
  }
}

Il y a ici deux branches basées sur finalisable. En regardant le nom de oop_iterate, nous pouvons deviner ce qu'il fait, c'est-à-dire itérer les pointeurs dans l'objet, mais c'est une supposition après tout. Les détails dépendent de la vérification du code.

ZMarkBarrierOopClosure Ce nom signifie barrière.Nous sommes déjà arrivés à la porte de la question 3 au début, et nous n'en sommes qu'à un pas. Avant de franchir la porte de la barrière de lecture, nous devons déplacer une grosse pierre bloquant la porte. Comment la méthode oop_iterate est-elle connectée avec ZMarkBarrierOopClosure.

2.2.2 Parcours itératif d'objets

Cette section examinera la logique interne de oop_iterate. Il s'agit d'un processus de branchement. Si vous ne voulez pas encore en savoir plus, vous pouvez l'ignorer et passer directement à la section suivante 2.2.3 Barrière de lecture.

Attention : accrochez-vous aux rampes, la voiture commence à accélérer en contrebas !

Enregistrez d'abord le type cl ZMarkBarrierOopClosure, nous l'enregistrons comme "point d'enregistrement 2".

Entrez la fonction oop_iterate

template <typename OopClosureType>
void oopDesc::oop_iterate(OopClosureType* cl) {
  OopIteratorClosureDispatch::oop_oop_iterate(cl, this, klass());
}

plus bas

template <typename OopClosureType>
void OopIteratorClosureDispatch::oop_oop_iterate(OopClosureType* cl, oop obj, Klass* klass, MemRegion mr) {
  OopOopIterateBoundedDispatch<OopClosureType>::function(klass)(cl, obj, klass, mr);
}

plus bas

static FunctionType function(Klass* klass) {
    return _table._function[klass->id()];
  }

La méthode fonction est dans une classe, _table est l'instance d'objet de cette classe, _function est un tableau, voyons comment _function initialise la valeur.

public:
    FunctionType _function[KLASS_ID_COUNT];

    Table(){
      set_init_function<InstanceKlass>();
      set_init_function<InstanceRefKlass>();
      set_init_function<InstanceMirrorKlass>();
      set_init_function<InstanceClassLoaderKlass>();
      set_init_function<ObjArrayKlass>();
      set_init_function<TypeArrayKlass>();
    }

méthode de construction de table et définition de _fonction

Il y a plusieurs objets Klass ici, nous ne regardons que le premier. Enregistrons temporairement InstanceKlass comme "point d'enregistrement 3", puis regardons set_init_function.

template <typename KlassType>
    void set_init_function() {
      _function[KlassType::ID] = &init<KlassType>;
    }

Regardez la fonction init

template <typename KlassType>
    static void init(OopClosureType* cl, oop obj, Klass* k, MemRegion mr) {
      OopOopIterateBoundedDispatch<OopClosureType>::_table.set_resolve_function_and_execute<KlassType>(cl, obj, k, mr);
    }

continuer

template <typename KlassType>
    void set_resolve_function_and_execute(OopClosureType* cl, oop obj, Klass* k, MemRegion mr) {
      set_resolve_function<KlassType>();
      _function[KlassType::ID](cl, obj, k, mr);
    }

Continuez à suivre set_resolve_function

template <typename KlassType>
    void set_resolve_function() {
      if (UseCompressedOops) {
        _function[KlassType::ID] = &oop_oop_iterate_bounded<KlassType, narrowOop>;
      } else {
        _function[KlassType::ID] = &oop_oop_iterate_bounded<KlassType, oop>;
      }
    }

Vous pouvez voir qu'il y a UseCompressedOops ici. ZGC prend-il en charge les pointeurs compressés ? Les étudiants intéressés peuvent l’étudier en profondeur.

Regardez oop_oop_iterate_bounded

template <typename KlassType, typename T>
    static void oop_oop_iterate_bounded(OopClosureType* cl, oop obj, Klass* k, MemRegion mr) {
      ((KlassType*)k)->KlassType::template oop_oop_iterate_bounded<T>(obj, cl, mr);
    }

Ici, vous pouvez voir que la fonction oop_oop_iterate_bounded de KlassType est appelée. Qu'est-ce que KlassType ? Il s'agit d'un type générique, et l'un de ses types réels est l'InstanceKlass du point d'enregistrement 3.

Dans la JVM, une classe Java commune existe sous la forme d'un modèle Klass de type InstanceKlass.

Ce qui suit n'appartient pas au code source de ZGC, bien sûr, il s'agit toujours du code source jvm.

Entrez la fonction oop_oop_iterate_bounded d'InstanceKlass

template <typename T, class OopClosureType>
ALWAYSINLINE void InstanceKlass::oop_oop_iterate_bounded(oop obj, OopClosureType* closure, MemRegion mr) {
  if (Devirtualizer::do_metadata(closure)) {
    if (mr.contains(obj)) {
      Devirtualizer::do_klass(closure, this);
    }
  }

  oop_oop_iterate_oop_maps_bounded<T>(obj, closure, mr);
}

vers le bas

template <typename T, class OopClosureType>
ALWAYSINLINE void InstanceKlass::oop_oop_iterate_oop_maps_bounded(oop obj, OopClosureType* closure, MemRegion mr) {
  //非静态成员变量块起始位置
  OopMapBlock* map           = start_of_nonstatic_oop_maps();
  //非静态成员变量块结束位置
  OopMapBlock* const end_map = map + nonstatic_oop_map_count();
  //对非静态成员变量块进行遍历
  for (;map < end_map; ++map) {
    oop_oop_iterate_oop_map_bounded<T>(map, obj, closure, mr);
  }
}

Vous pouvez voir que le bloc variable membre non statique d'un objet est parcouru de manière itérative ici. continuer vers le bas

template <typename T, class OopClosureType>
ALWAYSINLINE void InstanceKlass::oop_oop_iterate_oop_map_bounded(OopMapBlock* map, oop obj, OopClosureType* closure, MemRegion mr) {
  T* p   = (T*)obj->obj_field_addr<T>(map->offset());
  T* end = p + map->count();

  T* const l   = (T*)mr.start();
  T* const h   = (T*)mr.end();
  assert(mask_bits((intptr_t)l, sizeof(T)-1) == 0 &&
         mask_bits((intptr_t)h, sizeof(T)-1) == 0,
         "bounded region must be properly aligned");

  if (p < l) {
    p = l;
  }
  if (end > h) {
    end = h;
  }

  for (;p < end; ++p) {
    Devirtualizer::do_oop(closure, p);
  }
}

Ici, nous continuons à parcourir les variables membres non statiques d'un objet. Continuez à regarder Devirtualizer :: do_oop

template <typename OopClosureType, typename T>
inline void Devirtualizer::do_oop(OopClosureType* closure, T* p) {
  call_do_oop<T>(&OopClosureType::do_oop, &OopClosure::do_oop, closure, p);
}

continuer

template <typename T, typename Receiver, typename Base, typename OopClosureType>
static typename EnableIf<IsSame<Receiver, Base>::value, void>::type
call_do_oop(void (Receiver::*)(T*), void (Base::*)(T*), OopClosureType* closure, T* p) {
  closure->do_oop(p);
}

Continuez, mais arrêtez de bouger. OopClosureType est un type de modèle. Quel est son type réel ? En remontant couche par couche, nous avons constaté que c'était la barrière de lecture ZMarkBarrierOopClosure au point d'enregistrement 2, qui nous ramenait au processus principal ZGC.

2.2.3 Barrière de lecture

En entrant ZMarkBarrierOopClosure, comme le montre la section précédente, le paramètre d'entrée p de cette fonction est le pointeur de la variable membre non statique de l'objet à marquer.

template <bool finalizable>
class ZMarkBarrierOopClosure : public ClaimMetadataVisitingOopIterateClosure {
public:
  ZMarkBarrierOopClosure() :
      ClaimMetadataVisitingOopIterateClosure(finalizable
                                                 ? ClassLoaderData::_claim_finalizable
                                                 : ClassLoaderData::_claim_strong,
                                             finalizable
                                                 ? NULL
                                                 : ZHeap::heap()->reference_discoverer()) {}

  virtual void do_oop(oop* p) {
    ZBarrier::mark_barrier_on_oop_field(p, finalizable);
  }

  virtual void do_oop(narrowOop* p) {
    ShouldNotReachHere();
  }
};

Continuez à regarder mark_barrier_on_oop_field

//
// Mark barrier
//
inline void ZBarrier::mark_barrier_on_oop_field(volatile oop* p, bool finalizable) {
  const oop o = Atomic::load(p);

  if (finalizable) {
    barrier<is_marked_or_null_fast_path, mark_barrier_on_finalizable_oop_slow_path>(p, o);
  } else {
    const uintptr_t addr = ZOop::to_address(o);
    if (ZAddress::is_good(addr)) {
      // Mark through good oop
      mark_barrier_on_oop_slow_path(addr);
    } else {
      // Mark through bad oop
      barrier<is_good_or_null_fast_path, mark_barrier_on_oop_slow_path>(p, o);
    }
  }
}

Il y a trois branches ici. Ne regardons pas d'abord le finalisable. Il y a deux branches selon que le pointeur est bon ou mauvais. Les deux branches appellent finalement la méthode de traitement de chemin lent mark_barrier_on_oop_slow_path.

Regardons directement la deuxième branche ci-dessous.

template <ZBarrierFastPath fast_path, ZBarrierSlowPath slow_path>
inline oop ZBarrier::barrier(volatile oop* p, oop o) {
  const uintptr_t addr = ZOop::to_address(o);

  // Fast path
  if (fast_path(addr)) {
    return ZOop::from_address(addr);
  }

  // Slow path
  const uintptr_t good_addr = slow_path(addr);

  if (p != NULL) {
    //指针自愈
    self_heal<fast_path>(p, addr, good_addr);
  }

  return ZOop::from_address(good_addr);
}

Il s'agit du code logique de base de la barrière de lecture. Ici, nous voyons également le pointeur s'auto-réparer.

Comme le montre ce code, la vérification du chemin rapide est d'abord effectuée. Si fast_path(addr) renvoie true, l'objet oop correspondant à addr est directement renvoyé. Si la vérification du chemin rapide échoue, une vérification du chemin lent est effectuée, addr est transmis à la fonction slow_path et la valeur de retour est stockée dans la variable good_addr. Si le pointeur entrant p n'est pas nul, appelez self_heal

Le slow_path ici est en fait la fonction mark_barrier_on_oop_slow_path.

Ici, nous examinons principalement deux fonctions, le traitement lent du chemin et l'auto-réparation du pointeur. Le processus de traitement lent du chemin est relativement long, regardons donc d'abord l'auto-réparation du pointeur.

template <ZBarrierFastPath fast_path>
inline void ZBarrier::self_heal(volatile oop* p, uintptr_t addr, uintptr_t heal_addr) {
  if (heal_addr == 0) {
    // Never heal with null since it interacts badly with reference processing.
    // A mutator clearing an oop would be similar to calling Reference.clear(),
    // which would make the reference non-discoverable or silently dropped
    // by the reference processor.
    return;
  }

  assert(!fast_path(addr), "Invalid self heal");
  assert(fast_path(heal_addr), "Invalid self heal");

  for (;;) {
    // Heal
    const uintptr_t prev_addr = Atomic::cmpxchg((volatile uintptr_t*)p, addr, heal_addr);
    if (prev_addr == addr) {
      // Success
      return;
    }

    if (fast_path(prev_addr)) {
      // Must not self heal
      return;
    }

    // The oop location was healed by another barrier, but still needs upgrading.
    // Re-apply healing to make sure the oop is not left with weaker (remapped or
    // finalizable) metadata bits than what this barrier tried to apply.
    assert(ZAddress::offset(prev_addr) == ZAddress::offset(heal_addr), "Invalid offset");
    addr = prev_addr;
  }
}

C'est relativement simple, similaire à l'opération cas de Java, qui consiste à remplacer le mauvais pointeur par un bon pointeur, puis à exécuter le chemin rapide.

Le bon pointeur est renvoyé par le chemin lent exécuté ci-dessus.

Regardez ensuite le chemin lent

//
// Mark barrier
//
uintptr_t ZBarrier::mark_barrier_on_oop_slow_path(uintptr_t addr) {
  assert(during_mark(), "Invalid phase");
  assert(ZThread::is_worker(), "Invalid thread");

  // Mark
  return mark<GCThread, Follow, Strong, Overflow>(addr);
}

Ici, le pointeur entrant dans la barrière de lecture est à nouveau marqué.

À ce stade, la logique principale de la barrière de lecture est effectivement terminée. Voici la logique de marquage déclenchée par la barrière de lecture.

2.2.4 Logique de marquage déclenchée par une barrière de lecture

continuer

template <bool gc_thread, bool follow, bool finalizable, bool publish>
uintptr_t ZBarrier::mark(uintptr_t addr) {
  uintptr_t good_addr;

  if (ZAddress::is_marked(addr)) {
    // Already marked, but try to mark though anyway
    good_addr = ZAddress::good(addr);
  } else if (ZAddress::is_remapped(addr)) {
    // Already remapped, but also needs to be marked
    good_addr = ZAddress::good(addr);
  } else {
    // Needs to be both remapped and marked
    good_addr = remap(addr);
  }

  // Mark
  if (should_mark_through<finalizable>(addr)) {
    ZHeap::heap()->mark_object<gc_thread, follow, finalizable, publish>(good_addr);
  }

  if (finalizable) {
    // Make the oop finalizable marked/good, instead of normal marked/good.
    // This is needed because an object might first becomes finalizable
    // marked by the GC, and then loaded by a mutator thread. In this case,
    // the mutator thread must be able to tell that the object needs to be
    // strongly marked. The finalizable bit in the oop exists to make sure
    // that a load of a finalizable marked oop will fall into the barrier
    // slow path so that we can mark the object as strongly reachable.
    return ZAddress::finalizable_good(good_addr);
  }

  return good_addr;
}

Les fonctions à suivre ici sont au nombre de trois : ZAddress::good, remap et ZHeap::heap()->mark_object.

ZHeap::heap()->mark_object marque l'objet dans le tas.

Regardons d'abord la logique de ZAddress :: good

inline uintptr_t ZAddress::offset(uintptr_t value) {
  return value & ZAddressOffsetMask;
}

inline uintptr_t ZAddress::good(uintptr_t value) {
  return offset(value) | ZAddressGoodMask;
}

On peut voir que seul le bit de marque du pointeur est modifié, ce qui est relativement simple et cohérent avec notre compréhension du processus ZGC avant de lire le code.

2.2.5 Remappage

En continuant à examiner la méthode de remappage par remappage, nous trouvons ici la question de départ 5 [Processus de remappage simultané dans le marquage simultané ZGC]

uintptr_t ZBarrier::remap(uintptr_t addr) {
  assert(!ZAddress::is_good(addr), "Should not be good");
  assert(!ZAddress::is_weak_good(addr), "Should not be weak good");
  return ZHeap::heap()->remap_object(addr);
}

continuer

inline uintptr_t ZHeap::relocate_object(uintptr_t addr) {
  assert(ZGlobalPhase == ZPhaseRelocate, "Relocate not allowed");

  ZForwarding* const forwarding = _forwarding_table.get(addr);
  if (forwarding == NULL) {
    // Not forwarding
    return ZAddress::good(addr);
  }

  // Relocate object
  return _relocate.relocate_object(forwarding, ZAddress::good(addr));
}

Vous pouvez voir que la table de transfert est vérifiée en premier. Si la table de transfert n'existe pas, un bon pointeur sera renvoyé directement. S'il existe, l'opération de redistribution sera effectuée. L'étape suivante est la logique de la redistribution. Cet article va Je ne l'explique pas en détail. Il sera discuté dans l'article sur la redistribution à l'avenir. Introduction détaillée.

2.2.6 Retour au processus de marquage barrière de lecture

Ensuite, revenez au début de la version 2.2.4 et continuez à regarder ZHeap::heap()->mark_object

template <bool gc_thread, bool follow, bool finalizable, bool publish>
inline void ZHeap::mark_object(uintptr_t addr) {
  assert(ZGlobalPhase == ZPhaseMark, "Mark not allowed");
  _mark.mark_object<gc_thread, follow, finalizable, publish>(addr);
}

continuer

template <bool gc_thread, bool follow, bool finalizable, bool publish>
inline void ZMark::mark_object(uintptr_t addr) {
  assert(ZAddress::is_marked(addr), "Should be marked");

  ZPage* const page = _page_table->get(addr);
  if (page->is_allocating()) {
    // Already implicitly marked
    return;
  }

  const bool mark_before_push = gc_thread;
  bool inc_live = false;

  if (mark_before_push) {
    // Try mark object
    if (!page->mark_object(addr, finalizable, inc_live)) {
      // Already marked
      return;
    }
  } else {
    // Don't push if already marked
    if (page->is_object_marked<finalizable>(addr)) {
      // Already marked
      return;
    }
  }

  // Push
  ZMarkThreadLocalStacks* const stacks = ZThreadLocalData::stacks(Thread::current());
  ZMarkStripe* const stripe = _stripes.stripe_for_addr(addr);
  ZMarkStackEntry entry(addr, !mark_before_push, inc_live, follow, finalizable);
  stacks->push(&_allocator, &_stripes, stripe, entry, publish);
}

Il y a deux logiques importantes ici : la première est que les éléments marqués ne sont plus poussés, et la seconde est que les éléments non marqués sont poussés vers la pile.

Rappelez-vous le "point d'enregistrement 1" précédent, c'est là que se trouve le pointeur poussé sur la pile, et le pointeur poussé sera retiré lors du prochain cycle de marquage.

À ce stade, le processus principal de marquage tricolore ZGC est connecté du début à la fin et les fleurs sont terminées.

3. Examen du problème

3.1 Questions d'ouverture

1. Comment ZGC marque les pointeurs
2. Processus de marquage tricolore ZGC
3. Comment ZGC évite-t-il les offres manquées en utilisant uniquement des barrières de lecture ?
4. Processus d'auto-guérison du pointeur pendant le processus de marquage ZGC
5. Processus de remappage simultané dans le marquage simultané ZGC

Parmi les cinq questions ci-dessus, 1, 2, 4 et 5 ont déjà été soulevées lors du processus d'analyse du code source. La question 3 nécessite d'examiner l'ensemble du processus.

3.2 Comment ZGC évite-t-il les offres manquées en utilisant uniquement des barrières de lecture ?

3.2.1 Dans quelles circonstances l'offre sera-t-elle manquée ?

Lorsque les deux situations suivantes se produisent simultanément :

1. Ajout d'une nouvelle référence de l'objet noir à l'objet blanc
2. Suppression de toutes les références de l'objet gris à l'objet blanc

3.2.2 Comment l'éviter ?

Détruisant la condition 1, il s'agit de la solution de mise à jour incrémentielle (Incremental Update).

Condition de destruction 2, il s'agit de la solution d'instantané d'origine (Snapshot At The Beginning, SAT B).

ZGC utilise une solution de mise à jour incrémentielle. Cependant, la mise à jour incrémentielle n’est-elle pas une nouveauté ? Elle devrait être une barrière en écriture. Comment réaliser une barrière en lecture ?

En fait, lorsqu'une nouvelle référence est ajoutée, la référence doit avoir été lue quelque part auparavant, donc l'opération de lecture déclenche la barrière de lecture. Dans le code source précédent, nous savons qu'il existe un processus de marquage dans la barrière de lecture ZGC, et un bon pointeur sera automatiquement renvoyé. C'est-à-dire qu'il n'y a pas d'objets blancs dans les pointeurs renvoyés à travers la barrière de lecture, ce qui fondamentalement évite la condition 1.

4. Élargissez votre réflexion

La marque ZGC est sur le pointeur, pas sur l'objet. Lorsqu'une région est recyclée, pour un objet de cette région, il est impossible d'obtenir les pointeurs d'autres objets pointant vers cet objet. Alors comment savoir si cet objet est vivant?

Cette question nous oblige à relier le code de la phase de marquage et de la phase de redistribution pour obtenir la réponse.

5. Fin

C'est tout pour l'analyse du code source du processus de marquage simultané ZGC. Si vous le trouvez inspirant ou utile, n'hésitez pas à l'aimer et à le soutenir ~

 

Auteur : JD Logistics Liu Jiacun

Source : JD Cloud Developer Community Ziyuanqishuo Tech Veuillez indiquer la source lors de la réimpression

L'auteur du framework open source NanUI s'est tourné vers la vente d'acier et le projet a été suspendu. La liste gratuite numéro un dans l'App Store d'Apple est le logiciel pornographique TypeScript. Il vient de devenir populaire, pourquoi les grands commencent-ils à l'abandonner ? Liste TIOBE d'octobre : Java connaît la plus forte baisse, C# se rapproche de Java Rust 1.73.0 publié Un homme a été encouragé par sa petite amie IA à assassiner la reine d'Angleterre et a été condamné à neuf ans de prison Qt 6.6 officiellement publié Reuters : RISC-V la technologie devient la clé de la guerre technologique sino-américaine Nouveau champ de bataille RISC-V : non contrôlé par une seule entreprise ou un seul pays, Lenovo prévoit de lancer un PC Android
{{o.name}}
{{m.nom}}

Je suppose que tu aimes

Origine my.oschina.net/u/4090830/blog/10116830
conseillé
Classement