Подтвердить что ты не робот

Является ли путь к классам Java окончательным после запуска JVM?

В последнее время я много читал о загрузке классов Java. Часто я сталкивался с текстами, в которых утверждалось, что во время выполнения невозможно добавлять классы в путь к классам и загружать их без хакера класса (URLClassLoaders и т.д.).

Насколько я знаю, классы загружаются динамически. Это означает, что их представление байт-кода только загружается и преобразуется в объект java.lang.Class при необходимости.

Итак, не следует ли добавлять JAR или *.class файл в путь к классам после запуска JVM и загрузки этих классов при условии, что они еще не загружены? (Чтобы быть ясным: в этом случае путь к классам - это просто папка в файловой системе. "Добавление файла JAR или *.class" просто означает удаление их в этой папке.)

И если это не так, означает ли это, что путь к классу проверяется при запуске JVM, и все полные имена найденных классов кэшируются во внутреннем "списке"?

Было бы здорово, если бы вы могли указать мне некоторые источники в своих ответах. Предпочтительно официальная документация SUN: Sun JVM Spec. Я прочитал спецификацию, но ничего не нашел о пути к классам, и если она была завершена при запуске JVM.

P.s.

Это теоретический вопрос. Я просто хочу знать, если это возможно. Я ничего не хочу практиковать. Есть только моя жажда знаний:)

4b9b3361

Ответ 1

Поскольку никто не мог дать мне определенный ответ или ссылку на соответствующую часть документации, я сам даю ответ. Тем не менее я хотел бы поблагодарить всех, кто пытался ответить на вопрос.

Короткий ответ:

Путь к классам не является окончательным при запуске JVM.

Фактически вы можете поместить классы в путь к классам после запуска JVM, и они будут загружены.


Длинный ответ:

Чтобы ответить на этот вопрос, я пошел после неизвестных пользователей и написал небольшую тестовую программу.

Основная идея состоит в том, чтобы иметь два класса. Один из них - основной класс, создающий второй класс. При запуске второй класс не находится в пути к классам. После запуска программы cli вам будет предложено нажать enter. Прежде чем нажимать кнопку ввода, вы копируете второй класс в пути к классам. После нажатия введите второй экземпляр экземпляра. Если путь к классам был бы окончательным при запуске JVM, это создало бы исключение. Но это не так. Поэтому я предполагаю, что путь класса не является окончательным при запуске JVM.

Вот исходные коды:

JVMTest.java

package jvmtest;

import java.io.Console;
import jvmtest.MyClass;

public class JVMTest {
  public static void main(String[] args) {
    System.out.println("JVMTest started ...");

    Console c = System.console();
    String enter = c.readLine("Press Enter to proceed");
    MyClass myClass = new MyClass();
    System.out.println("Bye Bye");
  }
}

MyClass.java

package jvmtest;

public class MyClass {
  public MyClass() {
    System.out.println("MyClass v2");
  }
}

Структура папок выглядит следующим образом:

jvmtest/
  JVMTest.class
  MyClass.class

Я начал программу cli с помощью этой команды:

> java -cp /tmp/ jvmtest.JVMTest

Как вы можете видеть, у меня была папка jvmtest в /tmp/jvmtest. Вы, очевидно, должны изменить это в соответствии с тем, где вы кладете классы.

Итак, вот шаги, которые я выполнил:

  • Убедитесь, что только JVMTest.class находится в jvmtest.
  • Запустите программу с помощью команды сверху.
  • Чтобы быть уверенным, нажмите клавишу ввода. Вы должны увидеть исключение, сообщающее вам, что ни один класс не может быть найден.
  • Теперь запустите программу еще раз.
  • После запуска программы и вам будет предложено нажать Enter, скопируйте файл MyClass в папку jvmtest.
  • Нажмите enter. Вы должны увидеть "MyClass v1".

Дополнительные примечания:

Это также сработало, когда я упаковал класс MyClass в банке и выполнил тест выше.

Я запустил это на своем Macbook Pro под управлением Mac OS X 10.6.3

> Java -version

приводит к:

java version "1.6.0_20"
Java(TM) SE Runtime Environment (build 1.6.0_20-b02-279-10M3065)
Java HotSpot(TM) 64-Bit Server VM (build 16.3-b01-279, mixed mode)

Ответ 2

Здесь есть два понятия, которые смешиваются: путь к классам и файлы классов в пути к классам.

Если вы укажете путь к классу в каталог, вы, как правило, не будете пытаться добавить файл в каталог и получить его как часть пути к классам. Из-за потенциального размера всех классов в classpath для современной JVM не реально загружать их при запуске. Однако это имеет ограниченную ценность, поскольку в него не будут включены файлы Jar.

Однако изменение самого пути к классам (какие каталоги, баны и т.д.) на запущенной JVM будет зависеть от реализации. Насколько я знаю, на стандартных Sun JVM нет документально подтвержденного (как в гарантированном порядке) способа выполнения этого.

В общем, если это то, что вам нужно сделать (иметь динамический путь к классам, который изменяется во время выполнения), то вы хотите внедрять ClassLoader, если не по какой-либо другой причине, чем иметь возможность выбросить его и создать новый тот, который больше не ссылается на эти классы, если их нужно разгружать.

Однако для небольшого количества динамической нагрузки есть лучшие способы. В Java 1.6 вы можете указать все файлы jar в каталоге (*.jar), чтобы вы могли указывать пользователям добавлять дополнительные библиотеки в указанное место (хотя они должны быть там при запуске).

У вас также есть возможность включить файл jar или другое местоположение в пути к классам, даже если он вам не нужен, в качестве заполнителя для того, чтобы кто-то помещал туда необязательный файл jar или ресурса (например, файл конфигурации журнала).

Но если вам нужна серьезная загрузка динамического класса и особенно разгрузка во время работы приложения, требуется реализация Classloader.

Ответ 3

@Jen Я не думаю, что ваш эксперимент может подтвердить вашу теорию, потому что это больше о создании объекта: ваша типография происходит, когда объект этого класса создается, но не обязательно говорит, что JVM знает ваш код, класс, просто когда он создается.

Мое мнение состоит в том, что все Java-классы загружаются при запуске JVM, и можно подключить больше классов к JVM во время его запуска: этот метод называется: Горячее развертывание.

Ответ 4

Внизу: можно добавить записи в системный путь к классам во время выполнения, и показано, как это сделать. Это, однако, имеет необратимые побочные эффекты и опирается на детали реализации Sun JVM.


Путь к классу final, в самом буквальном смысле:

загрузчик системного класса (тот, который загружается из основного пути класса) есть sun.misc.Launcher$AppClassLoader в rt.jar.

rt.jar:sun/misc/Launcher.class (источники создаются с помощью Java Decompiler):

public class Launcher
{
 <...>
 static class AppClassLoader
    extends URLClassLoader
  {
    final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
<...>

rt.jar:sun/misc/URLClassLoader.class:

protected Class<?> findClass(final String paramString)
    throws ClassNotFoundException
  {
    <...>
          String str = paramString.replace('.', '/').concat(".class");
          Resource localResource = URLClassLoader.this.ucp.getResource(str, false);
  <...>

Но даже если поле является окончательным, это не означает, что мы не можем мутировать сам объект, если мы каким-то образом получим доступ к нему. Поле не имеет модификатора доступа - это означает, что мы можем получить к нему доступ, если только мы делаем вызов из одного и того же пакета. (ниже IPython с JPype; команды достаточно читаемы, чтобы легко получить свои Java-копии)

#jpype doesn't automatically add top-level packages except `java' and `javax'
In [28]: jpype.sun=jpype._jpackage.JPackage("sun")

In [32]: jpype.sun.misc.Launcher
Out[32]: jpype._jclass.sun.misc.Launcher

In [35]: jpype.sun.misc.Launcher.getLauncher().getClassLoader()
Out[35]: <jpype._jclass.sun.misc.Launcher$AppClassLoader at 0x19e23b0>    

In [36]: acl=_

In [37]: acl.ucp
Out[37]: <jpype._jclass.sun.misc.URLClassPath at 0x19e2c90>

In [48]: [u.toString() for u in acl.ucp.getURLs()]
Out[48]: [u'file:/C:/Documents%20and%20Settings/User/']

Теперь URLClassPath имеет общедоступный метод addURL. Попробуй попробовать и посмотри, что получится:

#normally, this is done with Launcher.getFileURL but we can't call it directly
#public static URLClassPath.pathToURLs also does the same, but it returns an array
In [72]: jpype.sun.net.www.ParseUtil.fileToEncodedURL(
             jpype.java.io.File(r"c:\Ivan\downloads\dom4j-2.0.0-RC1.jar")
             .getCanonicalFile())
Out[72]: <jpype._jclass.java.net.URL at 0x1a04b50>

In [73]: _.toString()
Out[73]: u'file:/C:/Ivan/downloads/dom4j-2.0.0-RC1.jar'

In [74]: acl.ucp.addURL(_72)

In [75]: [u.toString() for u in acl.ucp.getURLs()]
Out[75]:
[u'file:/C:/Documents%20and%20Settings/User/',
 u'file:/C:/Ivan/downloads/dom4j-2.0.0-RC1.jar']

Теперь попробуйте загрузить некоторый класс из .jar:

In [78]: jpype.org=jpype._jpackage.JPackage("org")

In [79]: jpype.org.dom4j.Entity
Out[79]: jpype._jclass.org.dom4j.Entity 

Успех!

Вероятно, это произойдет из песочницы или такого, где есть пользовательские загрузчики классов или параметры безопасности на пути (AppClassLoader.loadClass проверяет безопасность перед вызовом super).

Дальнейшая проверка кода показывает, что addURL также отключает кеш-поиск URLClassPath (реализован в нескольких методах native), и это необратимо. Первоначально флаг lookupCacheEnabled устанавливается в значение системного свойства sun.cds.enableSharedLookupCache.

Интерфейс не дает возможности редактировать записи. URL-адреса добавляются в URLClassPath private ArrayList path и Stack urls. urls доступен, но, оказывается, он временно удерживает записи временно, прежде чем он попытается загрузить из него, после чего информация переместится на HashMap lmap и ArrayList loaders. getURLs() возвращает копию path. Таким образом, теоретически можно отредактировать его, взломав доступные поля, но он нигде не является надежным и не повлияет на результат getURLs.

Ответ 5

Я могу только прокомментировать из того, что я помню, о собственном опыте взлома JVM, не относящегося к солнцу, десять лет назад, но он сканировал весь путь к классу при запуске, как показатель эффективности. Имена всех найденных классов были добавлены во внутреннюю хэш-таблицу вместе с их местоположениями (каталог или файл zip/jar). Тем не менее, это было десять лет назад, и я не могу не задаться вопросом, будет ли это все еще разумным делом в большинстве настроек, учитывая, как развивались архитектуры дисков и памяти.

Ответ 6

Я считаю, что classpath считается статическим, а результат изменения файлов - undefined.

Если вы действительно хотите иметь возможность добавлять и удалять классы во время выполнения, подумайте об этом в своем собственном загрузчике классов. Это то, что делают веб-контейнеры.

Ответ 7

Поэтому не следует добавлять JAR или *.class в путь к классам после запуска JVM...

Вы добавляете банки и каталоги в путь к классам, а не классы. Классы находятся либо в каталоге, либо в банке.

И если нет, значит ли это, что classpath выполняется при запуске JVM и все полностью квалифицированные имена найденные классы кэшируются в внутренний "список"?

Это может быть легко протестировано: установите путь к классам, запустите свою программу, переместите новый класс в CP, вызовите "Class.forName" ( "NewClass" ) из вашей программы. Находит ли он новый класс?