|
Εισαγωγή
Μία Java εφαρμογή μπορεί να αποτελείται από μόνο μία ή πολλές κλάσεις.
Αυτές οι κλάσεις φορτώνοντε δυναμικώς, εν ώρα εκτέλεσης της εφαρμογής,
όταν η κλάση που ήδη τρέχει αναφέρεται σε αυτές με το όνομά τους. Όλη
αυτή η διαδικασία φόρτωσης των κλάσεων γίνεται από τον εκάστοτε Φορτωτή
Κλάσεων (Class Loader). Οι φορτωτές κλάσεων είναι και οι ίδιοι κλάσεις
γραμμένες σε Java και η δουλειά τους είναι να φορτώνουνε τo bytecode
των κλάσεων στην εικονική μηχανή. Χωρίς αυτούς καμία εφαρμογή δεν
μπορεί να τρέξει. Μαζί με την κάθε κλάση φορτώνοντε και οι υπερκλάσεις
(super classes), οι διεπαφές (interfaces) και ο,τιδήποτε άλλο πάνω στο
οποίο βασίζεται η κάθε κλάση.
Προβλήματα και λύσεις
Ωστόσο εδώ υπάρχει ένα πρόβλημα. Οι ίδιοι οι class loaders είναι
γραμμένοι σε Java (και αποτελούντε από bytecode), χωρίς αυτούς δεν
μπορεί να φορτωθεί και να τρέξει καμία άλλη κλάση και κατ' επέκταση
ούτε και οι ίδιοι! Από τη στιγμή που οι class loaders είναι γραμμένοι
σε Java τότε εσχάτως όλοι επεκτείνουνε τη java.lang.Object κλάση (όλες οι Java
κλάσεις επεκτείνουνε αυτονόητα τη java.lang.Object).
Οπότε κανένας class loader δεν μπορεί να τρέξει αν δε φορτωθούν
πλήρως όλες οι υπερκλάσεις του οι οποίες όμως βασίζοντε στον
class loader για να φορτωθούνε!
Η λύση είναι φυσικά ότι υπάρχει ένας class loader (που ονομάζεται
bootstrap) που αποτελεί τμήμα αυτής καθεαυτής της εικονικής μηχανής και
είναι γραμμένος στη γλώσσα που είναι γραμμένη και η εικονική μηχανή.
Όταν ο χρήστης χρησιμοποιεί τη java εντολή για να τρέξει ένα πρόγραμμα
τότε ο bootstrap φορτώνει τους άλλους class loaders, οι οποίοι με τη
σειρά τους προσπαθούνε να φορτώσουνε την κλάση που ακολουθεί και
ψάχνουνε να βρούνε τη main μέθοδο.
ClassLoaders
Υπάρχουνε διάφοροι class loaders (μπορούμε να γράψουμε και τους δικούς
μας αν το επιθυμούμε) και ο καθένας φορτώνει συγκεκριμένες κλάσεις. Για
παράδειγμα άλλος είναι ο class loader που φορτώνει τις κλάσεις που
βρίσκοντε στο classpath και άλλος αυτός που φορτώνει τις Applets.
Όπως καταλαβαίνουμε υπάρχει μία ιεραρχία και στους class loaders, ένα
«δέντρο» που η ρίζα του είναι ο πρωτογενής class loader (bootstrap) και
διακλαδώνεται και σε άλλους. Ο αρχικός class loader φορτώνει κάποιους
άλλους και αυτοί με τη σειρά τους φορτώνουνε άλλους κ.ό.κ. Ο πατέρας
όλων των class loaders είναι ο bootstrap class loader (ο οποίος
φορτώνει τις κλάσεις από το runtime API, δηλαδή το rt.jar) και μετά
είναι ο ExtClassLoader (ο οποίος φορτώνει τις κλάσεις από τον /ext
υποκατάλογο), μετά έρχεται ο AppClassLoader (φορτώνει τις κλάσεις από
το classpath) κ.ό.κ.
Όταν μία κλάση (ας πούμε class A) φορτωθεί από έναν class loader τότε
αυτή η κλάση ρωτάει τον ίδιο class loader να φορτώσει και όλες τις
εξαρτόμενες κλάσεις (δηλαδή όλες οι κλάσεις που εξαρτούντε από την A).
Ο class loader με τη σειρά του πάντα αποστέλνει μήνυμα προς τα πάνω,
στο δέντρο των class loaders, για να δει αν κάποιος «γονέας» class
loader θέλει να φορτώσει την κλάση πρώτα. Αν κανένας γονέας class
loader δεν μπορεί να φορτώσει την κλάση τότε τη φορτώνει ο αρχικός
class loader.
Πότε όμως φορτώνοντε οι κλάσεις; Σε ακριβώς δύο περιπτώσεις: όταν η
λέξη-κλειδί new χρησιμοποιείται (String s = new
String("hello")) και όταν το bytecode κάνει μία στατική αναφορά
σε μία κλάση (Class.forName ή System.out).
Όταν η εικονική μηχανή ανταμώσει ένα από τα παραπάνω, τότε ο σχετικός
class loader θα αναλάβει να φορτώσει την κλάση χρησιμοποιώντας τον
κώδικα ClassLoader.loadClass(String className, boolean
resolveIt). To classname είναι το όνομα της κλάσης ενώ το
resolveIt χρησιμοποιείται για να δείξει αν η κλάση πρέπει να επιλυθεί ή
όχι. Μπορούμε να σκεφτούμε την επίλυση μίας κλάσης ώς τη διαδικασία
φόρτωσής της για να τρέξει (δηλαδή φόρτωση και όλων των από αυτή
εξαρτόμενων κλάσεων). Μία κλάση που δεν πρέπει να επιλυθεί μπορεί απλώς
να φορτωθεί στη μνήμη μόνο και μόνο για να δούμε αν υπάρχει ή για να
βρούμε την υπερκλάση της για παράδειγμα.
Aποστολόπουλος Πάρις
Κωνσταντινίδης
Πάνος
|