Είναι πάρα πολλές οι φορές που συναντώ στησίματα σε servers με JVM options και στην ερώτηση μου “γιατί είναι επιλεγμένα αυτά τα options?” η απάντηση είναι: “Γιατί το είχε πει ο “Θανάσης” “που ήξερε” ή “Τι να σου πω; έτσι το βρήκα και μου φάνηκε καλό”.
Αποφάσισα, λοιπόν, να γράψω αυτό το άρθρο, για να αναφέρω τις πιο συχνές παραμέτρους που χρησιμοποιώ εγώ για Java 6 μέχρι και 8, σε παραγωγικά και μη περιβάλλοντα.
Ρυθμίσεις σε όλα τα περιβάλλοντα
Οι παρακάτω παράμετροι είναι αυτοί που χρησιμοποιώ γενικά και σε παραγωγικά και σε staging ή development περιβάλλοντα.
Διαχείριση μνήμης
-Xms<heap size> και -Xmx<heap size>
Για να αποφύγουμε η java να αποφασίζει και να προσαρμόζει το heap size δυναμικά, προκαλώντας καθυστερήσεις, καθορίζουμε κατώτερο και ανώτερο φράγμα. Μέχρι και τη java 7 υπήρχε η λογική αυτά να έχουν την ίδια τιμή. Για τη java 8 MHN το κάνετε! Βάλτε πραγματικά μια τιμή που, κατά την εκκίνηση, η εφαρμογή σας θα είναι σχετικά άνετη και μέχρι πόσο θέλετε να φτάσει. Ρυθμίστε ορθότερα τον GC (παρακάτω) και μην βάζετε την ίδια τιμή!Μπορεί να οριστεί μονάδα μέτρησης π.χ g,m,k, αλλιώς η τιμή είναι σε bytes.
-XX:PermSize=<perm gen size> και -XX:MaxPermSize=<perm gen size>
Το αυτό με τα παραπάνω για το permanent generation. ΔΕΝ ΙΣΧΥΕΙ ΓΙΑ JAVA 8!!!
-XX:MaxMetaspaceSize=<metaspace size> (ισχύει μόνο για java 8)
Η java 8 της Oracle χρησιμοποιεί τη φυσική μνήμη του συστήματος για τα class metadata και αυτό λέγεται Metaspace (κάτι που κάνει εδώ και καιρό το JRockit και η IBM JVM). Τα καλά νέα είναι δε βγαίνουν πλέον προβλήματα τύπου java.lang.OutOfMemoryError: PermGen space. Τα κακά νέα είναι ότι αυτό μπορεί να ανεβαίνει ανεξέλεγκτα, οπότε πρέπει να το περιορίσουμε και να το κάνουμε monitor. O garbage collector διαχειρίζεται αυτήν την περιοχή μνήμης ως εξής: την πρώτη φορά που θα τη μαζέψει, είναι όταν τα class metadata φτάσουν στο MetaspaceSize (12Mbytes σε 32bit client VM και 16Mbytes σε 32bit server VM -παρακάτω θα τις ξεχωρίσουμε- και σε μεγαλύτερα κάπως μεγέθη σε 64bit VM’s). Οπότε βάζουμε MetaspaceSize σε μεγαλύτερη τιμή, για να αποφύγουμε την εκτέλεσή του. Μετά την πρώτη φορά το μέγεθος που θα χρειαστεί να φτάσουν, για να μαζέψει πάλι ο GC, είναι συνήθως μεγαλύτερο. Συνεπώς η πρακτική του “αλλάζω από την Java 6,7 το MaxPermSize σε MaxMetaspaceSize” είναι ΚΑΚΗ ΠΡΑΚΤΙΚΗ!
-Xmn<young size> και -XX:NewRatio
Ωχ young generation! Τι είναι αυτό; Μετά τη συνολική μέγιστη μνήμη που όλοι (ελπίζω) ξέρουμε και ρυθμίζουμε, το επόμενο εξαιρετικά σημαντικό στοιχείο, που πρέπει να ρυθμίζουμε και να γνωρίζουμε, είναι το young generation. Γενικά ο κανόνας λέει ότι όσο πιο ψηλά είναι το young generation τόσο λιγότερες φορές το GC θα εκτελεστεί για minor collection. Άρα το βάζουμε ψηλά και καθαρίσαμε…σωστά; Όχι ακριβώς! Σε ένα σαφώς ορισμένο πεδίο μνήμης, αν έχεις πολύ μεγάλο young generation σημαίνει ότι έχεις αναγκαστικά μικρότερο tenured generation,
(άλλο πράγμα του διαβόλου, όπου εκεί ζουν αντικείμενα με μεγαλύτερη διάρκεια ζωής) ήτοι μεγαλύτερη συχνότητα σε GC των major collections. Οπότε η επιλογή μεγέθους εξαρτάται καθαρά από την κατανομή της διάρκειας ζωής των αντικειμένων της εφαρμογής μας. Το μέγεθος του young generation καθορίζεται και από την παράμετρο -XX:NewRatio που ουσιαστικά μας καθορίζει την αναλογία young και old generation. Π.χ
[bash]$ java -XX:NewSize=32m -XX:MaxNewSize=512m -XX:NewRatio=3 MyApp[/bash]
Αυτό σημαίνει ότι η jvm θα προσπαθήσει να κρατήσει το old generation, ώστε να είναι τρεις φορές μεγαλύτερο από το young generation, αλλά το young generation δεν θα είναι ποτέ κάτω από τα 32mb και ποτέ πάνω από τα 512m.
Φιλική συμβουλή: Αν δεν ξέρεις τι κάνει η εφαρμογή σου και τι δημιουργεί σε επίπεδο βραχέων και μη αντικειμένων, τότε ΜΗΝ ΠΕΙΡΑΞΕΙΣ ΤΙΠΟΤΑ! Αν δεις ότι έχει θέματα επιδόσεων, ξεμένεις από μνήμη κλπ, τότε κάνε μια καλή ανάλυση και ξεκίνα να πειράζεις. Εκπληκτικό εργαλείο είναι ο profiler του Netbeans.
Ρυθμίσεις σε παραγωγικά ή staging περιβάλλοντα
-XX:+UseConcMarkSweepGC και -XX:+CMSParallelRemarkEnabled
Mostly Concurrent Mark and Sweep Garbage Collector, αυτός ο GC είναι εκπληκτικός, έχει φοβερές επιδόσεις και δεν υπάρχει κανένας λόγος να μην τον χρησιμοποιείς σε web εφαρμογές. Μαζί με τα παραπάνω μπορεί να χρησιμοποιηθεί και το -XX:+UseCMSInitiatingOccupancyOnly για να κάνει τον GC λιγότερο απρόβλεπτο και από εμπειρία έχω δει καλύτερα αποτελέσματα με λιγότερα full GC.
-XX:+ScavengeBeforeFullGC και -XX:+CMSScavengeBeforeRemark
Ρυθμίζει τον garbage collector, ώστε εκείνος να μαζέψει πρώτα το young generation πριν το Full GC ή το CMS, προκαλώντας καλύτερες επιδόσεις.
-server
Κανε το σέρβερ σου να συμπεριφέρεται ως σέρβερ και ενεργοποίησε τα server χαρακτηριστικά της JVM. (Αν έχεις 64bit jvm, αυτό είναι ήδη ενεργοποιημένο. Βάλτο όμως ούτως ή άλλως, γιατί μπορεί να αλλάξει στο μέλλον).
-Dsun.net.inetaddr.ttl=<TTL σε sec>
Το DNS caching έχει ως εξής
- Java 6 caching για πάντα (όχι πολύ βολικό θα έλεγα)
- Java 7 & 8 30sec αλλά μόνο αν δεν έχει εγκατασταθεί ο security manager, οπότε προσοχή γιατί πάλι μπορεί να αποθηκεύει για πάντα
Αυτό που κάνω, όταν έχω εγκατεστημένο security manager, είναι ότι αλλάζω στο %JRE%/lib/security/java.security και βάζω networkaddress.cache.ttl=3600
γιατί προσωπικά θεωρώ ότι η μία ώρα είναι αρκετά καλή επιλογή. Όταν δεν έχω εγκατεστημένο security manager, βάζω το jvm option παραπάνω.
Ρυθμίσεις για να βρούμε τι δεν πάει καλά
-verbose:gc -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:”<διαδρομή αρχείου log>”
Σώζει σε αρχείο τις δραστηριότητες του GC με timestamps και μεγάλη λεπτομέρεια. Για να μην έχουμε τεράστια αρχεία και για να τα διαχειριστούμε πιο εύκολα, χρησιμοποιούμε -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=(πχ 100) -XX:GCLogFileSize=(πχ 50mb). Ωραίο εργαλείο για ανάλυση είναι το GCViewer και ο profiler του Netbeans.
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<διαδρομη αρχείου>`date`.hprof
Αν η εφαρμογή “σκάσει” από OOM, τότε θέλεις να ξέρεις τι έγινε, οπότε χρησιμοποίησέ το σε παραγωγικά περιβάλλοντα.
Ρυθμίσεις για να συνδέσουμε εργαλείο παρακολούθησης
Απλά βάλτε τα παρακάτω. Εάν θέλετε authentication και ssl, αυτό δεν θα το καλύψω στον οδηγό. Οδηγίες θα βρείτε εδώ http://docs.oracle.com/javase/7/docs/technotes/guides/management/agent.html
-Djava.rmi.server.hostname=<εξωτερική ip>
-Dcom.sun.management.jmxremote.port=<port>
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
Επίλογος
Δεν υπάρχουν “μαγικές ρυθμίσεις”! Πρέπει πρώτα να ξέρεις τι κάνει η εφαρμογή σου και μετά να ξεκινήσεις να πειράζεις. Αν δεν ξέρεις τι κάνει η εφαρμογή σου, τότε ΜΗΝ ΠΕΙΡΑΞΕΙΣ ΤΙΠΟΤΑ! Αν δεις ότι έχει θέματα επιδόσεων, ξεμένεις από μνήμη κλπ, τότε κάνε μια καλή ανάλυση και ξεκίνα να πειράζεις. Εκπληκτικό εργαλείο είναι ο profiler του Netbeans.
photo by Takipi (http://blog.takipi.com/)