Страницы

Поиск по вопросам

понедельник, 24 февраля 2020 г.

Когда может понадобиться самому загружать классы в java?

#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); // Берем наш класс Class clazzv1 = (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 я сюда не выкладываю чтоб не получилась портянка, но они легко гуглятся.

Комментариев нет:

Отправить комментарий