Προγραμματισμός

Βελτιστοποίηση απόδοσης JVM, Μέρος 3: Συλλογή απορριμμάτων

Ο μηχανισμός συλλογής απορριμμάτων της πλατφόρμας Java αυξάνει σημαντικά την παραγωγικότητα του προγραμματιστή, αλλά ένας συλλέκτης απορριμμάτων που δεν έχει εφαρμοστεί σωστά μπορεί να καταναλώσει υπερβολικά πόρους εφαρμογής. Σε αυτό το τρίτο άρθρο στο Βελτιστοποίηση απόδοσης JVM σειρά, η Eva Andreasson προσφέρει στους αρχάριους Java μια επισκόπηση του μοντέλου μνήμης της πλατφόρμας Java και του μηχανισμού GC. Στη συνέχεια εξηγεί γιατί ο κατακερματισμός (και όχι το GC) είναι το σημαντικότερο "gotcha!" της απόδοσης εφαρμογών Java και γιατί η συλλογή και η συμπύκνωση γενετικών απορριμμάτων είναι οι κορυφαίες (αν και όχι οι πιο καινοτόμες) προσεγγίσεις για τη διαχείριση του κατακερματισμού σωρού σε εφαρμογές Java.

Συλλογή απορριμάτων (GC) είναι η διαδικασία που στοχεύει στην απελευθέρωση της κατεχόμενης μνήμης που δεν αναφέρεται πλέον από οποιοδήποτε προσβάσιμο αντικείμενο Java και αποτελεί ουσιαστικό μέρος του δυναμικού συστήματος διαχείρισης μνήμης της εικονικής μηχανής Java (JVM's). Σε έναν τυπικό κύκλο συλλογής απορριμμάτων φυλάσσονται όλα τα αντικείμενα που παραμένουν αναφερόμενα, και επομένως προσβάσιμα. Ο χώρος που καταλαμβάνεται από αντικείμενα που αναφέρθηκαν προηγουμένως απελευθερώνεται και ανακτάται για να καταστεί δυνατή η κατανομή νέων αντικειμένων.

Για να κατανοήσετε τη συλλογή απορριμμάτων και τις διάφορες προσεγγίσεις και αλγόριθμους GC, πρέπει πρώτα να γνωρίζετε μερικά πράγματα για το μοντέλο μνήμης της πλατφόρμας Java.

Βελτιστοποίηση απόδοσης JVM: Διαβάστε τη σειρά

  • Μέρος 1: Επισκόπηση
  • Μέρος 2: Μεταγλωττιστές
  • Μέρος 3: Συλλογή απορριμμάτων
  • Μέρος 4: Ταυτόχρονη συμπίεση GC
  • Μέρος 5: Επεκτασιμότητα

Συλλογή απορριμμάτων και το μοντέλο μνήμης πλατφόρμας Java

Όταν καθορίζετε την επιλογή εκκίνησης -Χμχ στη γραμμή εντολών της εφαρμογής Java (για παράδειγμα: java -Xmx: 2g MyApp) η μνήμη εκχωρείται σε μια διαδικασία Java. Αυτή η μνήμη αναφέρεται ως Σωρός Java (ή απλά σωρός). Αυτός είναι ο αποκλειστικός χώρος διευθύνσεων μνήμης όπου θα εκχωρηθούν όλα τα αντικείμενα που δημιουργούνται από το πρόγραμμα Java (ή μερικές φορές το JVM). Καθώς το πρόγραμμα Java συνεχίζει να λειτουργεί και εκχωρεί νέα αντικείμενα, ο σωρός Java (που σημαίνει ότι ο χώρος διευθύνσεων) θα γεμίσει.

Τελικά, ο σωρός Java θα είναι πλήρης, πράγμα που σημαίνει ότι ένα νήμα εκχώρησης δεν μπορεί να βρει μια αρκετά μεγάλη διαδοχική ενότητα ελεύθερης μνήμης για το αντικείμενο που θέλει να εκχωρήσει. Σε αυτό το σημείο, η JVM καθορίζει ότι πρέπει να γίνει συλλογή απορριμμάτων και ειδοποιεί τον συλλέκτη απορριμμάτων. Μια συλλογή απορριμμάτων μπορεί επίσης να ενεργοποιηθεί όταν καλεί ένα πρόγραμμα Java System.gc (). Χρησιμοποιώντας System.gc () δεν εγγυάται συλλογή απορριμμάτων. Προτού ξεκινήσει οποιαδήποτε συλλογή απορριμμάτων, ένας μηχανισμός GC θα καθορίσει πρώτα εάν είναι ασφαλές να το ξεκινήσετε. Είναι ασφαλές να ξεκινήσετε μια συλλογή απορριμμάτων όταν όλα τα ενεργά νήματα της εφαρμογής βρίσκονται σε ασφαλές σημείο για να το επιτρέψετε, π.χ. απλώς εξήγησε ότι θα ήταν κακό να ξεκινήσετε τη συλλογή απορριμμάτων στη μέση μιας τρέχουσας κατανομής αντικειμένων ή στη μέση εκτέλεσης μιας ακολουθίας βελτιστοποιημένων οδηγιών CPU (δείτε το προηγούμενο άρθρο μου σχετικά με τους μεταγλωττιστές), καθώς ενδέχεται να χάσετε το περιβάλλον και έτσι να χάσετε το τέλος Αποτελέσματα.

Ένας συλλέκτης σκουπιδιών πρέπει ποτέ ανακτήστε ένα ενεργό αντικείμενο αναφοράς. Με αυτόν τον τρόπο θα σπάσει τις προδιαγραφές της εικονικής μηχανής Java. Ο συλλέκτης απορριμμάτων δεν απαιτείται επίσης να συλλέγει αμέσως νεκρά αντικείμενα. Τα νεκρά αντικείμενα συλλέγονται τελικά κατά τους επόμενους κύκλους συλλογής απορριμμάτων. Ενώ υπάρχουν πολλοί τρόποι για την εφαρμογή της συλλογής απορριμμάτων, αυτές οι δύο παραδοχές ισχύουν για όλες τις ποικιλίες. Η πραγματική πρόκληση της συλλογής απορριμμάτων είναι να εντοπίσουμε όλα όσα είναι ζωντανά (εξακολουθούν να αναφέρονται) και να ανακτήσουν οποιαδήποτε μη αναφερόμενη μνήμη, αλλά να το κάνουν χωρίς να επηρεάζουν τις εφαρμογές που εκτελούνται περισσότερο από ό, τι είναι απαραίτητο. Ένας συλλέκτης απορριμμάτων έχει έτσι δύο εντολές:

  1. Για γρήγορη απελευθέρωση μη αναφερόμενης μνήμης για να ικανοποιήσετε το ποσοστό κατανομής μιας εφαρμογής, ώστε να μην εξαντλείται η μνήμη.
  2. Για να ανακτήσετε τη μνήμη ενώ επηρεάζετε ελάχιστα την απόδοση (π.χ. καθυστέρηση και απόδοση) μιας εφαρμογής που εκτελείται.

Δύο είδη συλλογής απορριμμάτων

Στο πρώτο άρθρο αυτής της σειράς άγγιξα τις δύο κύριες προσεγγίσεις για τη συλλογή απορριμμάτων, που είναι η καταμέτρηση αναφοράς και η ανίχνευση συλλεκτών. Αυτή τη φορά θα αναλύσω περαιτέρω κάθε προσέγγιση και μετά θα εισαγάγω μερικούς από τους αλγόριθμους που χρησιμοποιούνται για την εφαρμογή συλλεκτών ανίχνευσης σε περιβάλλοντα παραγωγής.

Διαβάστε τη σειρά βελτιστοποίησης απόδοσης JVM

  • Βελτιστοποίηση απόδοσης JVM, Μέρος 1: Επισκόπηση
  • Βελτιστοποίηση απόδοσης JVM, Μέρος 2: Μεταγλωττιστές

Συλλέκτες καταμέτρησης αναφοράς

Συλλέκτες καταμέτρησης αναφοράς παρακολουθείτε πόσες αναφορές δείχνουν σε κάθε αντικείμενο Java. Μόλις ο αριθμός για ένα αντικείμενο γίνει μηδέν, η μνήμη μπορεί να ανακτηθεί αμέσως. Αυτή η άμεση πρόσβαση σε ανακτημένη μνήμη είναι το κύριο πλεονέκτημα της προσέγγισης καταμέτρησης αναφοράς στη συλλογή απορριμμάτων. Υπάρχει πολύ μικρή επιβάρυνση όσον αφορά τη διατήρηση της μνήμης χωρίς αναφορά. Ωστόσο, η διατήρηση όλων των μετρήσεων αναφοράς μπορεί να είναι αρκετά δαπανηρή.

Η κύρια δυσκολία με τους συλλέκτες καταμέτρησης αναφοράς είναι η ακρίβεια των μετρήσεων αναφοράς. Μια άλλη γνωστή πρόκληση είναι η πολυπλοκότητα που σχετίζεται με το χειρισμό κυκλικών δομών. Εάν δύο αντικείμενα παραπέμπουν το ένα στο άλλο και κανένα ζωντανό αντικείμενο δεν αναφέρεται σε αυτά, η μνήμη τους δεν θα απελευθερωθεί ποτέ. Και τα δύο αντικείμενα θα παραμείνουν για πάντα με μη μηδενικό αριθμό. Η ανάκτηση της μνήμης που σχετίζεται με κυκλικές δομές απαιτεί σημαντική ανάλυση, η οποία φέρνει δαπανηρή επιβάρυνση στον αλγόριθμο και συνεπώς στην εφαρμογή.

Εντοπισμός συλλεκτών

Εντοπισμός συλλεκτών βασίζονται στην υπόθεση ότι όλα τα ζωντανά αντικείμενα μπορούν να βρεθούν αν επαναληφθούν επαναληπτικά όλες οι αναφορές και οι επακόλουθες αναφορές από ένα αρχικό σύνολο γνωστών ως ζωντανών αντικειμένων. Το αρχικό σύνολο ζωντανών αντικειμένων (ονομάζεται αντικείμενα ρίζας ή απλά ρίζες για συντομία) εντοπίζονται με ανάλυση των καταχωρητών, των καθολικών πεδίων και των πλαισίων στοίβας τη στιγμή που ενεργοποιείται μια συλλογή απορριμμάτων. Αφού εντοπιστεί ένα αρχικό ζωντανό σετ, ο συλλέκτης παρακολούθησης ακολουθεί αναφορές από αυτά τα αντικείμενα και τις ουρές μέχρι να επισημανθούν ως ζωντανές και στη συνέχεια να εντοπιστούν οι αναφορές τους. Επισήμανση όλων των αντικειμένων αναφοράς ζω σημαίνει ότι το γνωστό ζωντανό σετ αυξάνεται με την πάροδο του χρόνου. Αυτή η διαδικασία συνεχίζεται έως ότου βρεθούν και επισημανθούν όλα τα αντικείμενα που αναφέρονται (και συνεπώς όλα τα ζωντανά). Μόλις ο ανιχνευτής ανιχνεύσει όλα τα ζωντανά αντικείμενα, θα ανακτήσει την υπόλοιπη μνήμη.

Οι συλλέκτες ανίχνευσης διαφέρουν από τους συλλέκτες καταμέτρησης αναφοράς στο ότι μπορούν να χειριστούν κυκλικές δομές. Το catch με τους περισσότερους συλλέκτες ανίχνευσης είναι η φάση σήμανσης, η οποία συνεπάγεται αναμονή πριν μπορέσει να ανακτήσει μνήμη που δεν αναφέρεται.

Οι συλλέκτες ανίχνευσης χρησιμοποιούνται συνήθως για τη διαχείριση μνήμης σε δυναμικές γλώσσες. Είναι μακράν η πιο κοινή για τη γλώσσα Java και έχουν αποδειχθεί εμπορικά σε περιβάλλοντα παραγωγής για πολλά χρόνια. Θα επικεντρωθώ στην ανίχνευση συλλεκτών για το υπόλοιπο αυτού του άρθρου, ξεκινώντας με μερικούς από τους αλγόριθμους που εφαρμόζουν αυτήν την προσέγγιση για τη συλλογή απορριμμάτων.

Ανίχνευση αλγορίθμων συλλεκτών

Αντιγραφή και σήμανση και σκούπισμα Η συλλογή απορριμμάτων δεν είναι νέα, αλλά εξακολουθούν να είναι οι δύο πιο συνηθισμένοι αλγόριθμοι που εφαρμόζουν τη συλλογή απορριμμάτων ιχνηλάτησης σήμερα.

Αντιγραφή συλλεκτών

Οι παραδοσιακοί συλλέκτες αντιγραφής χρησιμοποιούν α από το διάστημα και ένα στο διάστημα - δηλαδή, δύο ξεχωριστά καθορισμένοι χώροι διευθύνσεων του σωρού. Στο σημείο συλλογής απορριμμάτων, τα ζωντανά αντικείμενα εντός της περιοχής που ορίζονται ως από-διάστημα αντιγράφονται στον επόμενο διαθέσιμο χώρο εντός της περιοχής που ορίζεται ως χώρος. Όταν μετακινούνται όλα τα ζωντανά αντικείμενα από το διάστημα, ολόκληρο το διάστημα μπορεί να ανακτηθεί. Όταν η κατανομή ξεκινά ξανά ξεκινά από την πρώτη δωρεάν τοποθεσία στο χώρο.

Σε παλαιότερες υλοποιήσεις αυτού του αλγορίθμου οι θέσεις εναλλαγής από διάστημα σε χώρο, που σημαίνει ότι όταν ο χώρος σε χώρο είναι πλήρης, η συλλογή απορριμμάτων ενεργοποιείται ξανά και ο χώρος προς το διάστημα γίνεται από το διάστημα, όπως φαίνεται στο Σχήμα 1.

Οι πιο σύγχρονες υλοποιήσεις του αλγορίθμου αντιγραφής επιτρέπουν στους αυθαίρετους χώρους διευθύνσεων εντός του σωρού να αντιστοιχιστούν ως διάστημα και διάστημα. Σε αυτές τις περιπτώσεις δεν χρειάζεται απαραίτητα να αλλάζουν θέση μεταξύ τους. Αντίθετα, ο καθένας γίνεται ένας άλλος χώρος διευθύνσεων μέσα στο σωρό.

Ένα πλεονέκτημα της αντιγραφής συλλεκτών είναι ότι τα αντικείμενα κατανέμονται σφιχτά στο χώρο, εξαλείφοντας εντελώς τον κατακερματισμό. Ο κατακερματισμός είναι ένα συνηθισμένο ζήτημα με το οποίο αντιμετωπίζονται άλλοι αλγόριθμοι συλλογής απορριμμάτων. κάτι που θα συζητήσω αργότερα σε αυτό το άρθρο.

Μειονεκτήματα των συλλεκτών αντιγραφής

Οι συλλέκτες αντιγραφής είναι συνήθως συλλέκτες stop-the-world, που σημαίνει ότι καμία εργασία εφαρμογής δεν μπορεί να εκτελεστεί για όσο διάστημα η συλλογή απορριμμάτων είναι σε κύκλο. Σε μια εφαρμογή stop-the-world, όσο μεγαλύτερη είναι η περιοχή που πρέπει να αντιγράψετε, τόσο υψηλότερος είναι ο αντίκτυπος στην απόδοση της εφαρμογής σας. Αυτό είναι ένα μειονέκτημα για εφαρμογές που είναι ευαίσθητες στο χρόνο απόκρισης. Με έναν συλλέκτη αντιγραφής πρέπει επίσης να λάβετε υπόψη το χειρότερο σενάριο, όταν όλα είναι ζωντανά στο διάστημα. Πρέπει πάντα να αφήνετε αρκετό χώρο για να μετακινήσετε ζωντανά αντικείμενα, πράγμα που σημαίνει ότι ο χώρος προς το διάστημα πρέπει να είναι αρκετά μεγάλος για να φιλοξενεί τα πάντα από το διάστημα. Ο αλγόριθμος αντιγραφής είναι ελαφρώς αναποτελεσματικός στη μνήμη λόγω αυτού του περιορισμού.

Συλλέκτες σήμανσης και σάρωσης

Τα περισσότερα εμπορικά JVM που αναπτύσσονται σε περιβάλλοντα παραγωγής επιχειρήσεων εκτελούν συλλέκτες mark-and-sweep (ή marking), οι οποίοι δεν έχουν αντίκτυπο στην απόδοση που έχουν οι συλλέκτες αντιγραφής. Μερικοί από τους πιο διάσημους συλλέκτες σήμανσης είναι CMS, G1, GenPar και DeterministicGC (βλ. Πόροι).

ΕΝΑ συλλέκτης σήμανσης και σάρωσης ανιχνεύει αναφορές και επισημαίνει κάθε αντικείμενο που βρέθηκε με ένα "ζωντανό" bit. Συνήθως ένα σετ bit αντιστοιχεί σε μια διεύθυνση ή σε ορισμένες περιπτώσεις ένα σύνολο διευθύνσεων στο σωρό. Το ζωντανό bit μπορεί, για παράδειγμα, να αποθηκευτεί ως bit στην κεφαλίδα του αντικειμένου, σε bit φορέα ή σε bit map.

Αφού όλα έχουν επισημανθεί ζωντανά, θα ξεκινήσει η φάση σάρωσης. Εάν ένας συλλέκτης έχει φάση σάρωσης, περιλαμβάνει βασικά κάποιο μηχανισμό για να διασχίσει ξανά το σωρό (όχι μόνο το ζωντανό σετ αλλά ολόκληρο το μήκος σωρού) για να εντοπίσει όλα τα μη επισημασμένα κομμάτια διαδοχικών χώρων διευθύνσεων μνήμης. Η μνήμη χωρίς σήμανση είναι δωρεάν και ανακτήσιμη. Στη συνέχεια, ο συλλέκτης συνδέει αυτά τα μη επισημασμένα κομμάτια σε οργανωμένες δωρεάν λίστες. Μπορεί να υπάρχουν διάφορες δωρεάν λίστες σε έναν συλλέκτη σκουπιδιών - συνήθως οργανώνεται από μεγάλα κομμάτια. Ορισμένα JVM (όπως το JRockit Real Time) εφαρμόζουν συλλέκτες με ευρετικά στοιχεία που δυναμικά καταγράφουν εύρη μεγεθών με βάση δεδομένα προφίλ εφαρμογών και στατιστικά μεγέθους αντικειμένων.

Όταν ολοκληρωθεί η φάση σάρωσης, η κατανομή θα ξεκινήσει ξανά. Νέες περιοχές κατανομής κατανέμονται από τις δωρεάν λίστες και τα κομμάτια μνήμης θα μπορούσαν να αντιστοιχιστούν σε μεγέθη αντικειμένων, μέσος όρος μεγέθους αντικειμένου ανά αναγνωριστικό νήματος ή τα μεγέθη TLAB που έχουν ρυθμιστεί στην εφαρμογή. Η τοποθέτηση του ελεύθερου χώρου πιο κοντά στο μέγεθος αυτού που προσπαθεί να εκχωρήσει η εφαρμογή σας βελτιστοποιεί τη μνήμη και θα μπορούσε να συμβάλει στη μείωση του κατακερματισμού.

Περισσότερα για τα μεγέθη TLAB

Τα διαμερίσματα TLAB και TLA (Thread Local Allocation Buffer ή Thread Local Area) συζητούνται στη βελτιστοποίηση απόδοσης JVM, Μέρος 1.

Μειονεκτήματα των συλλεκτών mark-and-sweep

Η φάση σήμανσης εξαρτάται από την ποσότητα ζωντανών δεδομένων στο σωρό σας, ενώ η φάση σάρωσης εξαρτάται από το μέγεθος του σωρού. Δεδομένου ότι πρέπει να περιμένετε μέχρι και τα δύο σημάδι και σκούπισμα Οι φάσεις είναι πλήρεις για την ανάκτηση μνήμης, αυτός ο αλγόριθμος προκαλεί προκλήσεις παύσης χρόνου για μεγαλύτερους σωρούς και μεγαλύτερα σετ δεδομένων.

Ένας τρόπος με τον οποίο μπορείτε να βοηθήσετε τις εφαρμογές που καταναλώνουν πολύ μνήμη είναι να χρησιμοποιήσετε τις επιλογές συντονισμού GC που ικανοποιούν διάφορα σενάρια και ανάγκες εφαρμογών. Ο συντονισμός μπορεί, σε πολλές περιπτώσεις, να βοηθήσει τουλάχιστον στην αναβολή οποιασδήποτε από αυτές τις φάσεις από το να γίνει κίνδυνος για την εφαρμογή σας ή τις συμφωνίες σε επίπεδο υπηρεσίας (SLA). (Ένα SLA καθορίζει ότι η εφαρμογή θα πληροί συγκεκριμένους χρόνους απόκρισης της εφαρμογής - δηλ. Καθυστέρηση.) Ο συντονισμός για κάθε αλλαγή φόρτωσης και η τροποποίηση της εφαρμογής είναι μια επαναλαμβανόμενη εργασία, ωστόσο, καθώς ο συντονισμός ισχύει μόνο για ένα συγκεκριμένο φόρτο εργασίας και ρυθμό κατανομής.

Εφαρμογές mark-and-sweep

Υπάρχουν τουλάχιστον δύο εμπορικά διαθέσιμες και αποδεδειγμένες προσεγγίσεις για την εφαρμογή της συλλογής mark-and-sweep. Η μία είναι η παράλληλη προσέγγιση και η άλλη είναι η ταυτόχρονη (ή κυρίως ταυτόχρονη) προσέγγιση.

Παράλληλοι συλλέκτες

Παράλληλη συλλογή σημαίνει ότι οι πόροι που εκχωρούνται στη διαδικασία χρησιμοποιούνται παράλληλα για τη συλλογή απορριμμάτων. Οι περισσότεροι παράλληλοι συλλέκτες που εφαρμόζονται στο εμπόριο είναι μονολιθικοί συλλέκτες stop-the-world - όλα τα νήματα εφαρμογών σταματούν μέχρι να ολοκληρωθεί ολόκληρος ο κύκλος συλλογής απορριμμάτων. Η διακοπή όλων των νημάτων επιτρέπει την αποτελεσματική χρήση όλων των πόρων παράλληλα για την ολοκλήρωση της συλλογής απορριμμάτων μέσω των φάσεων σήμανσης και σάρωσης. Αυτό οδηγεί σε πολύ υψηλό επίπεδο απόδοσης, συνήθως οδηγεί σε υψηλές βαθμολογίες σε σημεία αναφοράς απόδοσης όπως το SPECjbb. Εάν η απόδοση είναι απαραίτητη για την εφαρμογή σας, η παράλληλη προσέγγιση είναι μια εξαιρετική επιλογή.