#java #jvm #classloader
Помогите пожалуйста разобраться с динамической загрузкой классов в java. Как я понимаю, при старте программы загружаются классы из rt.jar, потом загружается главный класс, а все остальные пользовательские классы загружаются по мере необходимости (например класс A загружается только когда в программе создается объект класса A). Первый мой вопрос заключается в следующем: что собственно значит "JVM загружает класс"? Имеется в виду что в этот момент происходит компиляция класса? Так же мне интересно, зачем JVM загружает класс, когда используется его статический метод? Я провел небольшой эксперимент: написал класс, в котором есть один статический метод и несколько нестатических. public class StExmp { static{ c = 5; } private int a; public int getA() { return a; } public void setA(int a) { this.a = a; } public int getB() { return b; } public void setB(int b) { this.b = b; } private int b; private static int c; public StExmp(int a, int b){ this.a = a; this.b = b; c = 5; } public static void show(){ System.out.println(c); } } Вызываю статический метод в главном классе StExmp.show();, потом запускаю программу с флагом -verbose:class и вижу что когда программа дошла до вызова этого метода, она загрузила весь класс. А почему нельзя загружать только статические члены класса? Ведь получилось что из-за вызова одного маленького метода пришлось загрузить весь класс, хотя он больше никак и не используется. И главный вопрос: Когда может потребоваться самому загружать классы? Например с помощью ClassLoader.loadClass()? Вот этого я совсем не могу понять. Ведь если мы загружаем какой то класс, значит мы собираемся его как то использовать? Почему тогда нельзя просто использовать его в программе (например, создать объект) JVM же сама его загрузит. А когда это может потребоваться делать методом ClassLoader.loadClass()?
Ответы
Ответ 1
Ну и топик. Про загрузку классов можно много чего написать. Тема интересная, правда, уже 100 лет теорией не занимался. В контексте треда, пожалуй, отвечу на вопросы конкретные. Если в чём-то не прав, поправляйте, критика приветствуется. Первый мой вопрос заключается в следующем: что собственно значит "JVM загружает класс"? У вас есть скомпилированные байт коды классов, ClassLoader их грузить по мере необходимости. Зачем нам держать в памяти класс, если он не используется? Класс будет загружен только в момент использования. Точно так же, если на класс не осталось никаких ссылок, то ClassLoader может выгрузить класс из памяти при проходе GC. Имеется в виду что в этот момент происходит компиляция класса? Ваши классы уже скомпилированы javac. ClassLoader в память его грузит. Это если в 2-х словах, на самом деле там происходит верификация байт-кода и т.п. Так же мне интересно, зачем JVM загружает класс, когда используется его статический метод? Статически метод, константы - это метаданные класса. Не представляю, как их можно загрузить в отрыве без класс. А почему нельзя загружать только статические члены класса? Ведь получилось что из-за вызова одного маленького метода пришлось загрузить весь класс, хотя он больше никак и не используется. Предположим, есть у вас: public class StExmp { public static void show(){ System.out.println(c); } } Ок, давайте методом рассуждения выведем необходимость грузить класс. Вы хотите, чтобы метод show был загружен без класса. Но ведь в коде вы потом вызываете метод как StExmp.show()? Если класс не загружен, как вы себе представляете вызов метода? Ну хорошо, предположим загрузчик метод добавить в какую-то общую таблицу виртуальных методов. А потом вы создадите класс: public class StExmp2 { public static void show(){ System.out.println(c); } } Метод show загрузчик так же добавит его в общую таблицу статических методов? Проблему уже видите? Как потом при вызове StExmp2.show() понять какой из этих методов вызвать? Когда может потребоваться самому загружать классы? Например с помощью ClassLoader.loadClass()? Вот этого я совсем не могу понять. Ведь если мы загружаем какой то класс, значит мы собираемся его как то использовать? Почему тогда нельзя просто использовать его в программе (например, создать объект) JVM же сама его загрузит. А когда это может потребоваться делать методом ClassLoader.loadClass()? Например, у вас high-load проект. Приложение должно работать непрерывно. Но вам понадобилось заменить реализацию какого-то метода. Не перезапускать же всё приложение? Если оно стетйтлесс, то ещё ладно, но если там в памяти много данных/кэш и т.п.? Можно заменить налету. Когда это надо? Ну, скажем, вы хотите поправить какой-то критический баг, оптимизировали метод и т.п. Тут можно много чего придумать. К примеру у вас игра, в которой есть возможность добавлять кастомных npc. Вы просто пишите новые класс, который в рантайме подтягивается. Или просто неизвестно какой класс будет использоваться в итоге, решение принимаете в рантайме и грузите необходимый класс. Бывает случаи, когда классы хранятся в базе (да-да, бывает такое). Их иначе и не загрузить вовсе.Ответ 2
Итак. По умолчанию класслоадеров всего три но вам никто не мешает определять свой. Вам никто не мешает грузить классы не из jar или из файлов, а из БД или вообще через HTTP. Каждый новый класслоадер работает в своем неймспейсе, потому они могут загружать классы с одинаковым именем, но с абсолютно разным содержимым. В качестве "простого" примера: Определим интерфейс Meower, чтоб не мучаться с рефлекшном: package pkg; public interface Meower { String meow(); } package pkg; И вот такую вот конструкцию: public class Main { static String CLASS_NAME = "Cat"; static String CLASS_V1 = "package pkg;\n" + "\n" + "public class Cat implements Meower {\n" + " public String meow() {\n" + " return \"Meo..ow\";\n" + " }\n" + "}\n"; static String CLASS_V2 = "package pkg;\n" + "\n" + "public class Cat implements Meower {\n" + " public String meow() {\n" + " return \"Mrr, meo..ow\";\n" + " }\n" + "}\n"; public static void main(String[] args) throws Exception { // Делаем из строки SourceFile SourceFile srcv1 = new SourceFile(CLASS_NAME, CLASS_V1); SourceFile srcv2 = new SourceFile(CLASS_NAME, CLASS_V2); // Компилируем и загружаем ClassLoader clrv1 = new MemoryClassLoader(srcv1); ClassLoader clrv2 = new MemoryClassLoader(srcv2); // Берем наш класс Classclazzv1 = (Class ) Class.forName("pkg." + CLASS_NAME, true, clrv1); Class clazzv2 = (Class ) Class.forName("pkg." + CLASS_NAME, true, clrv2); // Инстанцируем экземпляры Meower ov1 = clazzv1.newInstance(); Meower ov2 = clazzv2.newInstance(); // Мяукаем System.out.println(ov1.meow()); // Meo..ow System.out.println(ov2.meow()); // Mrr, meo..ow } } Иными словами, немного помучавшись и дописав какую-нибудь Factory я смогу(хоть и не рекомендую этого делать :)) в рантайме, без остановки JVM, брать исходники откуда-нибудь, компилировать их на ходу и менять поведение остальной программы. Также, учитывая сказанное выше, можно придумать еще несколько причин вызвать Class.forName, наиболее распространенный - заранее прогрузить, а не затормозить когда вызовут. Менее распространенный - если вы грузите не с локальной файловой системы и хотите убедиться заранее что класс доступен. ПС: исходники MemoryClassLoader и SourceFile я сюда не выкладываю чтоб не получилась портянка, но они легко гуглятся.
Комментариев нет:
Отправить комментарий