Как подключать native библиотеки в jar файл при сборке?
При сборке jar файла когда сторонние библеотеки добавляешь надо .dll и .so в манифесте указывать? И в манифесте в строке Class-Path: можно указать просто папку с библиотеками или надо каждый jar файл прописывать? И если кому не сложно можно сразу скинуть пример манифеста скинуть с пояснениями. Заранее спасибо) всем + к карме UPD:надо ли папку с ресурсами (./res) указывать?
Отслеживать
1,250 9 9 серебряных знаков 17 17 бронзовых знаков
задан 10 июл 2017 в 8:11
349 1 1 серебряный знак 18 18 бронзовых знаков
2 ответа 2
Сортировка: Сброс на вариант по умолчанию
Вам надо пользоваться каким нибудь фреймворком для автоматизации сборки проектов. Например, как maven или gradle.
Они очень облегчают задачу сборки проектов. Так как у них есть репозиторий (хранилище) библиотек, и Вам надо будет просто указать их в файле сборки проекта, в pom.xml в случае maven или build.gradle в случае gradle.
Но если вы все-таки хотите собрать проект или jar файл без них, то в качестве примера я посоветовал бы Вам заглянуть в исходниках библиотеки SQLite. Как раз они в папке resources добавляют native библиотеки. Там можно даже в zip архиве скачать если не хотите заморачиваться git’ом.
Отслеживать
ответ дан 10 июл 2017 в 8:13
1,250 9 9 серебряных знаков 17 17 бронзовых знаков
оказывается это гипер сложно для того овоща как я, мб есть другой способ))
10 июл 2017 в 8:47
На сегодняшний день проекты на java трудно представить без них. Если вы в дальнейшем хотите создавать проекты на java, то Вам обязательно надо будет их изучать. Для начала основ будет достаточно чтобы вы могли создать и собрать какие-то проекты.
10 июл 2017 в 8:59
@naughtyBit А вообще вы как импортируете .dll или .so библиотеки на java классы сейчас?
10 июл 2017 в 9:09
я на линуксе, так что через .dll, но я делаю в команде (синхронизация через плагин) и в проекте так же присутствуют .so
10 июл 2017 в 9:12
Ну на самом деле в Intelij idea все легко делается: 1. жмем ctrl+alt+shift+s 2. Artefacts 3. жмем на плюсик, jar, from modules. 4. указываем главный класс и путь где появится jar 5. Во вкладки Build жмем на Build Artefacts, ниже должно появиться маленькое окошко, там и выбираем созданный артефакт и жмем build. 6. Если хотим подключить библиотеку или папку с ресурсами и тд, закидываем .dll или .so файлы в папку с jar файлом (это для библиотек), и папку res тоже закидываем к jar (это для ресуросв) 7. для запуска на linux создаем файлик с .sh расширением (например start.sh) и пишем туда: #!usr/bash java -Djava.library.path=»./path_to_libs/» -jar name_of_jar_file.jar для запуска на win создаем start.bat и записываем java -Djava.library.path=»./path_to_libs/» -jar name_of_jar_file.jar 8.Запускаем!
Отслеживать
ответ дан 12 июл 2017 в 8:12
349 1 1 серебряный знак 18 18 бронзовых знаков
- java
- intellij-idea
- jar
-
Важное на Мете
Похожие
Подписаться на ленту
Лента вопроса
Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.
Дизайн сайта / логотип © 2024 Stack Exchange Inc; пользовательские материалы лицензированы в соответствии с CC BY-SA . rev 2024.1.17.3574
no lwjgl64 in java.library.path
Просто несколько раз подряд выполни одну и ту же операцию «genEclipseRuns», не удивлюсь что и часть текстур могла неверно загрузиться. Такое бывает когда градл пытается скачать все и сразу во много потоков, а твое интернет соединение не вывозит. Был даже какой то ключик в gradle.properties чтобы не качал так активно.
И, если вдруг, так пишет вообще про все asset’ы и/или ошибок не становится меньше, то у тебя проблема с подключением к серверу можанга, собственно от туда и берет градл.
Plasticable
292 14 160
Если это исключение у тебя вылазит при запуске майна в IDE, то в параметрах запуска в VM options допиши
-Djava.library.path= и путь к папке natives относительно папки, в которой запускается кубач. Зачастую, просто natives . Саму папку можешь взять из какого-нибудь клиента.
![]()
Samual
Нет у меня eclipse
Samual
![]()
Я возвращался после долгого перерыва к моддингу. Да ещё и на 1.12.2 решил перейти. Я устанавливал почти всё заново, может я что-то намутил там
Plasticable
292 14 160
Эклипс вместо среды разработки, это, конечно, плохо. То, что я указал, в эклипсе вводи сюда:

Скрин из гугла
![]()
Samual
Мне что-то кажется что больше половины мододелов им и пользуются
Samual
Я завтра проверю работает это или нет
Samual
Я не выполнил команду setupDecompWorkspace eclipse
Samual
setupDecompWorkspace eclipse
и не получается её выполнить она не найдена
Undead
Кто удаляет сообщения, что это эклипс это IDE?
Samual
Я всё удалил и попробовал заново, но опять всё ко дну.
Я скачал forge 1.12.2 mdk (Recommended).
Создал папку с названием мода и извлёк туда всё из скачанного архива (ничего не удалял, да оно и не надо).
Открыл в этой папке cmd и дал команду setup DecompWorkspace eclipse.
В итоге произошёл Build Failed.
Было написано это:
Task ‘setupDecompWorkspace’ not found in root project ‘BlueBoxModOne’.
Samual
Кто удаляет сообщения, что это эклипс это IDE?
Не знаю, о я только сейчас заметил, что вот здесь:
Если это исключение у тебя вылазит при запуске майна в IDE
было написано IDE, а не IDEA. Ну впрочем ладно
Samual
Я всё удалил и попробовал заново, но опять всё ко дну.
Я скачал forge 1.12.2 mdk (Recommended).
Создал папку с названием мода и извлёк туда всё из скачанного архива (ничего не удалял, да оно и не надо).
Открыл в этой папке cmd и дал команду setup DecompWorkspace eclipse.
В итоге произошёл Build Failed.
Было написано это:
Task ‘setupDecompWorkspace’ not found in root project ‘BlueBoxModOne’.
Не знаю, от безысходности обновляю jre и JDK. Это геморно. Благо java и JDK можно скачать со сторонних сайтов, так как на официальном уже нельзя. Нужно создать учётку Oracle. А это невозможно. Если ты не программист (всмысле профессия).
Knob
Task ‘setupDecompWorkspace’ not found in root project ‘BlueBoxModOne’.
В новых этого нет же.
Возможно рукожопый вариант, но начиная с новых 1,12 и выше работает без нареканий.
1. Создаешь папку проекта (для вида project)
2. В ней (в project) создаешь другую папку (для вида forge) и распаковываешь туда mdk
3. Заходишь в нее (в forge) открываешь консоль и пишешь gradlew genEclipseRuns
4. Далее открываешь eclipse и выбираешь папку project
5. Выбираешь File — Import. — Gradle — Existing Gradle Project
6. В поле Project root directory выбираешь папку forge и жмешь Finish
Samual
genEclipseRuns
Спасибо, но я уже увидел это в файле README. Только всё равно Build Failed
Samual
Build Failed:
> Task :downloadAssets FAILED FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':downloadAssets'. [QUOTE] Failed to get asset: icons/minecraft.icns[/QUOTE] Failed to get asset: minecraft/icons/minecraft.icns Failed to get asset: minecraft/sounds/random/orb.ogg Failed to get asset: minecraft/sounds/random/successful_hit.ogg Some assets failed to download or validate, try running the task again. * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Get more help at https://help.gradle.org Deprecated Gradle features were used in this build, making it incompatible with Gradle 5.0. Use '--warning-mode all' to show the individual deprecation warnings. See https://docs.gradle.org/4.9/userguide/command_line_interface.html#sec:command_line_warnings BUILD FAILED in 4m 56s 6 actionable tasks: 6 executed
Samual
Build Failed
Помогите пожалуйста, я на это 2 дня убил
Knob
Ну он просто assets не докачал или с ошибкой. Очищать кэш градла не пробовал?
Можно вообще удалить папку градла где он все хранит c:\users\пользователь\.gradle
Все что надо оно само перекачает скачает, помнится у меня был случай конфликта разных версий градла, но на мой случай не похож.
Последнее редактирование: 19 Сен 2020
Samual
удалить папку градла
Да, я всегда gradle удаляю, но это не помогает
Samual
Я скачал forge 1.15.2, делал всё по официальной документации forge,
При импортировании проекта в eclipse ему что-то не удалось загрузить, какой-то fast util.
Далее нужно было выполнить enEclipseRuns gradle task, я выполнил, на этапе downloadAssets вылезла ошибка
Вот что он пишет:
Operation Task: download Assets (failures)::
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':downloadAssets'. at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:110) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:77) at org.gradle.api.internal.tasks.execution.OutputDirectoryCreatingTaskExecuter.execute(OutputDirectoryCreatingTaskExecuter.java:51) at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:59) at org.gradle.api.internal.tasks.execution.ResolveTaskOutputCachingStateExecuter.execute(ResolveTaskOutputCachingStateExecuter.java:54) at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:59) at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:101) at org.gradle.api.internal.tasks.execution.FinalizeInputFilePropertiesTaskExecuter.execute(FinalizeInputFilePropertiesTaskExecuter.java:44) at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:91) at org.gradle.api.internal.tasks.execution.ResolveTaskArtifactStateTaskExecuter.execute(ResolveTaskArtifactStateTaskExecuter.java:62) at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:59) at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:54) at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43) at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:34) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.run(EventFiringTaskExecuter.java:51) at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:301) at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:293) at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:175) at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:91) at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:46) at org.gradle.execution.taskgraph.LocalTaskInfoExecutor.execute(LocalTaskInfoExecutor.java:42) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareWorkItemExecutor.execute(DefaultTaskExecutionGraph.java:277) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareWorkItemExecutor.execute(DefaultTaskExecutionGraph.java:262) at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$ExecutorWorker$1.execute(DefaultTaskPlanExecutor.java:135) at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$ExecutorWorker$1.execute(DefaultTaskPlanExecutor.java:130) at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$ExecutorWorker.execute(DefaultTaskPlanExecutor.java:200) at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$ExecutorWorker.executeWithWork(DefaultTaskPlanExecutor.java:191) at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$ExecutorWorker.run(DefaultTaskPlanExecutor.java:130) at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63) at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55) at java.lang.Thread.run(Thread.java:748) Caused by: java.lang.RuntimeException: Failed to get asset: minecraft/sounds/mob/polarbear/idle3.ogg Failed to get asset: minecraft/sounds/mob/polarbear_baby/idle4.ogg Some assets failed to download or validate, try running the task again. at net.minecraftforge.gradle.common.task.DownloadAssets.run(DownloadAssets.java:92) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:73) at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:46) at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:39) at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:26) at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:801) at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:768) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$1.run(ExecuteActionsTaskExecuter.java:131) at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:301) at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:293) at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:175) at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:91) at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:120) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:99) . 34 more Root cause: java.lang.RuntimeException: Failed to get asset: minecraft/sounds/mob/polarbear/idle3.ogg Failed to get asset: minecraft/sounds/mob/polarbear_baby/idle4.ogg Some assets failed to download or validate, try running the task again. at net.minecraftforge.gradle.common.task.DownloadAssets.run(DownloadAssets.java:92) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:73) at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:46) at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:39) at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:26) at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:801) at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:768) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$1.run(ExecuteActionsTaskExecuter.java:131) at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:301) at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:293) at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:175) at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:91) at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:120) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:99) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:77) at org.gradle.api.internal.tasks.execution.OutputDirectoryCreatingTaskExecuter.execute(OutputDirectoryCreatingTaskExecuter.java:51) at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:59) at org.gradle.api.internal.tasks.execution.ResolveTaskOutputCachingStateExecuter.execute(ResolveTaskOutputCachingStateExecuter.java:54) at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:59) at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:101) at org.gradle.api.internal.tasks.execution.FinalizeInputFilePropertiesTaskExecuter.execute(FinalizeInputFilePropertiesTaskExecuter.java:44) at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:91) at org.gradle.api.internal.tasks.execution.ResolveTaskArtifactStateTaskExecuter.execute(ResolveTaskArtifactStateTaskExecuter.java:62) at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:59) at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:54) at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43) at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:34) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.run(EventFiringTaskExecuter.java:51) at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:301) at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:293) at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:175) at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:91) at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:46) at org.gradle.execution.taskgraph.LocalTaskInfoExecutor.execute(LocalTaskInfoExecutor.java:42) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareWorkItemExecutor.execute(DefaultTaskExecutionGraph.java:277) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareWorkItemExecutor.execute(DefaultTaskExecutionGraph.java:262) at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$ExecutorWorker$1.execute(DefaultTaskPlanExecutor.java:135) at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$ExecutorWorker$1.execute(DefaultTaskPlanExecutor.java:130) at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$ExecutorWorker.execute(DefaultTaskPlanExecutor.java:200) at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$ExecutorWorker.executeWithWork(DefaultTaskPlanExecutor.java:191) at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$ExecutorWorker.run(DefaultTaskPlanExecutor.java:130) at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63) at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55) at java.lang.Thread.run(Thread.java:748)
Java library path как туда добавить
Профиль
Группа: Участник
Сообщений: 128
Регистрация: 29.1.2006
Где: Прага
Репутация: 1
Всего: 2
Здравствуйте, уважаемые!
Подскажите как бороться с ошибкой
java.lang.UnsatisfiedLinkError: no Java_sml_ClientInterface in java.library.path
которая возникает при запуске программы.
Файл Java_sml_ClientInterface(это длл файл) у меня имеется и я знаю где он находится (допустим C:\temp) — но не знаю как это приписать в java.library.path(достаточно ручками) и где этот path вообще находится.
PS. Гуглила по вопросу. Пыталась установиться environment variable LD_LIBRARY_PATH в C:\temp — не помогло.
Это сообщение отредактировал(а) Larrr — 2.7.2006, 12:15
Как выгрузить dll из Java-машины
Java взаимодействует с операционной системой через методы, помеченные ключевым словом native, при помощи системных библиотек, загружаемых процедурой System.loadLibrary().
Загрузить системную библиотеку очень просто, а вот чтобы ее выгрузить, как оказалось, нужно приложить немало усилий. Как именно выгружаются системные библиотеки, и зачем это нужно я постараюсь рассказать.
Предположим, мы хотим сделать небольшую утилиту, которую будут запускать пользователи на своих компьютерах в локальной сети. Нам бы хотелось избавить пользователей от проблем с установкой и настройкой программы, но нет ресурсов на развертывание и поддержку централизованной инфраструктуры. В таких случаях обычно собирают программу вместе со всеми зависимостями в единый jar-файл. Это легко сделать при помощи maven-assembly-plugin или просто экспортировать из IDE Runnable jar. Запуск программы будет осуществляться командой:
java -jar my-program.jar
К сожалению, это не работает, если одна из библиотек требует для своей работы системную динамическую библиотеку, проще говоря dll. Обычно в одном из классов такой библиотеки в статическом инициализаторе делается вызов System.loadLibrary(). Чтобы dll загрузилась, нужно положить ее в каталог, доступный через системное свойство JVM java.library.path. Как это ограничение можно обойти?
Запакуем dll внутрь jar-файла. Перед началом использования классов, требующих подгрузки dll, создадим временный каталог, извлечем библиотеку туда и добавим каталог в java.library.path. Выглядеть это будет примерно так:
prepareLibrary
private void addLibraryPath(String pathToAdd) throws ReflectiveOperationException < Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths"); usrPathsField.setAccessible(true); String[] paths = (String[]) usrPathsField.get(null); String[] newPaths = Arrays.copyOf(paths, paths.length + 1); newPaths[newPaths.length - 1] = pathToAdd; usrPathsField.set(null, newPaths); >private Path prepareLibrary() throws IOException, ReflectiveOperationException < Path dir = Files.createTempDirectory("lib"); try (InputStream input = ExampleClass.class.getResourceAsStream("custom.dll")) < if (input == null) < throw new FileNotFoundException("Can't load resource custom.dll"); >Files.copy(input, dir.resolve("custom.dll")); > addLibraryPath(dir.toAbsolutePath().toString()); return dir; >
К сожалению, приходится химичить с reflection, потому что стандартных методов расширить java.library.path Java не предоставляет.
Теперь загрузка библиотеки проходит для пользователя прозрачно, и он не должен беспокоиться о копировании файлов или настройке переменных окружения. Для работы по-прежнему достаточно просто запустить обычный скрипт. Однако после каждого запуска программы остается временный каталог с файлами. Это не очень хорошо, поэтому на выходе надо выполнить очистку.
try < . >finally
Но на Windows это не работает. Загруженная в JVM библиотека блокирует dll-файл и каталог, в котором он лежит. Таким образом, чтобы решить задачу аккуратного завершения программы, надо выгрузить из JVM системную динамическую библиотеку.
Попытка решения
Прежде всего разумно добавить в код диагностику. Если файлы удалось удалить, например. когда библиотека не использовалась, то и делать ничего не надо, а если файлы заблокированы, тогда предпринять дополнительные меры.
if (!delete(dir))
Как быстрое, но не самое красивое решение, я использовал планировщик. На выходе создаю xml-файл с заданием на выполнение через 1 минуту команды «cmd /c rd /s /q temp-dir» и загружаю задание в планировщик командой «schtasks -create taskName -xml taskFile.xml». К моменту выполнения задания программа уже завершена, и файлы никто не держит.
Самое же верное решение — это обеспечить выгрузку библиотеки средствами Java-машины. Документация говорит о том, что системная библиотека будет выгружена при удалении класса, а класс удаляется сборщиком мусора вместе с класслоадером, когда не осталось ни одного экземпляра из его классов. На мой взгляд, лучше всегда писать такой код, который полностью очищает после себя всю память и другие ресурсы. Потому что если код делает что-то полезное, рано или поздно захочется его переиспользовать и задеплоить на какой-нибудь сервер, где установлены и другие компоненты. Поэтому я решил потратить время на то, чтобы разобраться, как корректно программно выгрузить dll.
Использование класслоадера
В моей программе проблемы исходили из JDBC-драйвера, поэтому дальше я буду рассматривать пример с JDBC. Но и с другими библиотеками можно работать аналогичным образом.
Если dll загружена из системного загрузчика классов, то выгрузить ее уже не получится, поэтому необходимо создать свой класслоадер таким образом, чтобы класс, подтягивающий библиотеку, был загружен из него. Новый класслоадер должен быть связан с системным класслоадером через свойство parent, иначе в нем не будут доступны классы String, Object и другие необходимые в хозяйстве в вещи.
Загрузка класса из нового загрузчика (1)
ClassLoader parentCl = ExampleClass.class.getClassLoader(); classLoader = new URLClassLoader(new URL[0], parentCl); Class.forName("org.jdbc.CustomDriver", classLoader, true); try (Connection connection = DriverManager.getConnection(dbUrl, dbProperties)) < if (connection.getClass().getClassLoader() != classLoader) < System.out.printf("Что-то пошло не так%n"); >. >
Не работает. При загрузке класса сначала производится попытка поднять его из родительского загрузчика, поэтому наш драйвер загрузился не так, как нам нужно. Для использования нового класслоадера, нужно JDBC-драйвер из jar-файла программы удалить, чтобы он не был доступен системному загрузчику. Значит, запаковываем библиотеку в виде вложенного jar-файла, а перед использованием разворачиваем его во временном каталоге (в том же, где и dll у нас лежит).
Загрузука класса из нового загрузчика (2)
ClassLoader cl = ExampleClass.class.getClassLoader(); URL url = UnloadableDriver.class.getResource("CustomJDBCDriver.jar"); if (url == null) < throw new FileNotFoundException("Can't load resource CustomJDBCDriver.jar"); >Path dir = prepareLibrary(); try (InputStream stream = url.openStream()) < Path target = dir.resolve("CustromJDBCDriver.jar"); Files.copy(stream, target); url = target.toUri().toURL(); >ClassLoader classLoader = new URLClassLoader(new URL[] , cl); Class.forName("org.jdbc.CustomDriver", true, classLoader); try (Connection connection = DriverManager.getConnection(dbUrl, dbProperties)) < if (connection.getClass().getClassLoader() != classLoader) < System.out.printf("Что-то пошло не так%n"); >else < System.out.printf("Получилось, можно идти дальше%n"); >. >
Мы получили объект, загруженный из нашего нового загрузчика, по окончании работы нам надо позакрывать все, что мы открывали, почистить все наши переменные, и, видимо, вызвать System.gc(), после чего уже пытаться чистить файлы. В этом месте имеет смысл инкапсулировать всю логику работы с загрузчиками классов в отдельном классе с явными методами инициализации.
Скелет основного класса
public class ExampleClass implements AutoCloseable < private final Path dir; private URLClassLoader classLoader; public ExampleClass() < . >public void doWork() < . >@Override public void close() < . this.classLoader.close(); this.classloader = null; System.gc(); // где-то здесь должна выгрузиться dll if (!delete(this.dir)) < scheduleRemovalToTaskschd(this.dir); >> > public class Main < public static void main(String args[]) < try (ExampleClass example = new ExampleClass()) < example.doWork(); >catch (Throwable e) < e.printStackTrace(); >> >
Эксперименты со сборщиком мусора
Несмотря на то, что вроде бы формально все необходимое для выгрузки библиотеки сделано, фактически выгрузка не происходит. Чтение исходников из пакетка java.lang позволило определить, что удаление нативных библиотек производится в методе finalize() в одном из внутренних классов. Это огорчает и настораживает, потому что документация не дает никакого точного определения, когда выполнится данный метод и выполнится ли вообще. Т. е. успех зависит от каких-то факторов, которые могут различаться в различном окружении, в различных версиях JVM или в различных сборщиках мусора. Тем не менее имеется метод System.runFinalization(), который дает некоторую надежду.
Run finalization.
@Override public void close() < . this.classLoader.close(); this.classloader = null; System.gc(); System.runFinalization(); // где-то здесь должна выгрузиться dll if (!delete(this.dir)) < scheduleRemovalToTaskschd(this.dir); >>
Не работает. Каталог заблокирован процессом java. С этого момента я использовал такую технику:
- Ставлю на выходе System.in.read()
- Когда программа останавливается в этом месте, делаю дамп памяти из jvisualvm
- Смотрю дамп при помощи Eclipse Memory Analysis Tool или jhat
- Ищу экземпляры объектов, классы которых были загружены мои загрузчиком
- Локальные переменные
- DriverManager
- ResourceBundle
- ThreadLocals
- Исключения
Локальные переменные
Локальные переменные
Оказалось, что сборщик мусора не считает локальную переменную недостижимой, пока не будет завершена функция, содержащая эту переменную, даже если переменная вышла из области видимости.
if (needConnection) < try (Connection connection = DriverManager.connect()) < . >> // Вот здесь переменна connection еще считается живой.
Поэтому для решения задачи выгрузки класслоадера необходимо перед вызовом gc выйти из всех функций, которые используют выгружаемые классы.
DriverManager
DriverManager
JDBC-драйверы при загрузке их класса регистрируются в классе DriverManager методом registerDriver(). Судя по всему, перед выгрузкой надо вызвать метод deregisterDriver(). Пробуем.
Enumeration drivers = driverManager.getDrivers(); while (drivers.hasMoreElements()) < Driver driver = drivers.nextElement(); if (driver.getClass().getClassLoader() == classLoader) < DriverManager.deregisterDriver(driver); break; >>
Не работает. Heapdump не изменился. Смотрим в исходники класса DriverManager и обнаруживаем, что в методе deregisterDriver() стоит проверка на то, что вызов должен быть из класса, который принадлежит тому же класслоадеру, что и класс, вызвавший ранее registerDriver(). А registerDriver() вызван самим драйвером из статического инициализатора. Неожиданный поворот.
Получается, мы не можем напрямую разрегистрировать драйвер. Вместо этого мы должны попросить какой-нибудь класс из нового класслоадера, чтобы он сделал это от своего имени. Выход заключается в создании специального класса DriverManagerProxy, точнее даже двух, класса и интерфейса.
public interface DriverManagerProxy < void deregisterDriver(Driver driver) throws SQLException; >public class DriverManagerProxyImpl implements DriverManagerProxy < @Override public void deregisterDriver(Driver driver) throws SQLException < DriverManager.deregisterDriver(driver); >>
Интерфейс будет находиться в основном classpath-е, а релизация будет загружена новым загрузчиком из вспомогательного jar-файла вместе с JDBC-драйвером. Теоретически без интерфейса можно было бы обойтись, но тогда для вызова функции пришлось бы применять reflection. Используется прокси следующим образом:
Использование DriverManagerProxy
public class ExampleClass implements AutoCloseable < private final Path dir; private URLClassLoader classLoader; private DriverManagerProxy driverManager; public ExampleClass() < . this.classLoader = . ; Class.forName("org.jdbc.CustomDriver", true, classLoader); ClassdmClass = Class.forName("ru.example.DriverManagerProxyImpl", true, classLoader); this.driverManager = (DriverManagerProxy) dmClass.newInstance(); > public void doWork() < . >@Override public void close() < . Enumerationdrivers = driverManager.getDrivers(); while (drivers.hasMoreElements()) < Driver driver = drivers.nextElement(); if (driver.getClass().getClassLoader() == classLoader) < driverManager.deregisterDriver(driver); break; >> this.driverManager = null; this.classLoader.close(); this.classloader = null; System.gc(); System.runFinalization(); // где-то здесь должна выгрузиться dll if (!delete(this.dir)) < scheduleRemovalToTaskschd(this.dir); >> >
ResourceBundle
ResourceBundle
Следующая зацепка на класслоадер, который я пытался выгрузить, обнаружилась в недрах класса ResourceBundle. К счастью, в отличие от DriverManager, ResourceBundle предоставляет специальную функцию clearCache(), которой класслоадер передается в качестве параметра.
ResourceBundle.clearCache(classLoader);
Надо заметить, что, судя по исходникам, в ResourceBundle используются слабые ссылки, которые не должны препятствовать сборке мусора. Возможно, если очистить все остальные ссылки на наши объекты, то чистить этот кэш нет необходимости.
ThreadLocals
ThreadLocals
Последнее место, где обнаружились хвосты неиспользуемого драйвера, оказалось ThreadLocals. После истории с DriverManager-ом, очистка локальных поточных переменных кажется парой пустяков. Хотя тут не удалось обойтись без reflection.
private static void cleanupThreadLocals(ClassLoader cl) throws ReflectiveOperationException < int length = 1; Thread threads[] = new Thread[length]; int cnt = Thread.enumerate(threads); while (cnt >= length) < length *= 2; threads = new Thread[length]; cnt = Thread.enumerate(threads); >for (int i = 0; i < cnt; i++) < Thread thread = threads[i]; if (thread == null) < continue; >cleanupThreadLocals(thread, cl); > > private static void cleanupThreadLocals(Thread thread, ClassLoader cl) throws ReflectiveOperationException < Field threadLocalsField = Thread.class.getDeclaredField("threadLocals"); threadLocalsField.setAccessible(true); Object threadLocals = threadLocalsField.get(thread); if (threadLocals == null) < return; >Class threadLocalsClass = threadLocals.getClass(); Field tableField = threadLocalsClass.getDeclaredField("table"); tableField.setAccessible(true); Object table = tableField.get(threadLocals); Object entries[] = (Object[]) table; Class entryClass = table.getClass().getComponentType(); Field valueField = entryClass.getDeclaredField("value"); valueField.setAccessible(true); Method expungeStaleEntry = threadLocalsClass.getDeclaredMethod("expungeStaleEntry", Integer.TYPE); expungeStaleEntry.setAccessible(true); for (int i = 0; i < entries.length; i++) < Object entry = entries[i]; if (entry == null) < continue; >Object value = valueField.get(entry); if (value != null) < ClassLoader valueClassLoader = value.getClass().getClassLoader(); if (valueClassLoader == cl) < ((java.lang.ref.Reference) entry).clear(); expungeStaleEntry.invoke(threadLocals, i); > > > >
Исключения
Исключения
Мы рассчитываем на то, что код очистки можно поместить в блоке finally. На входе в этот блок у нас уже должно быть все закрыто автоматически при помощи механизма try-with-resources. Однако наш класслоадер по-прежнему не будет в этом месте удален из памяти, если из блока try выброшено исключение, класс которого загружен этим класслоадером.
Чтобы удалить из памяти нежелательный exception, его надо поймать и обработать, а если нужно ошибку все-таки выбросить наверх, то скопировать exception в другой класс. Вот как это сделал я в своей программе:
try < . >catch (RuntimeException e) < if (e.getClass().getClassLoader() == this.getClass().getClassLoader()) < throw e; >RuntimeException exception = new RuntimeException(String.format("%s: %s", e.getClass(), e.getMessage())); exception.setStackTrace(e.getStackTrace()); throw exception; > catch (SQLException e) < if (e.getClass().getClassLoader() == this.getClass().getClassLoader()) < throw e; >SQLException exception = new SQLException(String.format("%s: %s", e.getClass(), e.getMessage())); exception.setStackTrace(e.getStackTrace()); throw exception; >
Java наносит ответный удар
После очистки всех обнаруженных ссылок на выгружаемые классы получилась немного парадоксальная ситуация. Никаких объектов в памяти нет, судя по дампу памяти, количество экземпляров во всех классах равно 0. Но сами классы и их загрузчик никуда не делись, и соответственно не удалилась нативная библиотека.
Устранить проблему получилось вот таким приемом:
System.gc(); System.runFinalization(); System.gc(); System.runFinalization();
Наверное, в Java 1.7, которую я использовал, была какая-то особенность очистки объектов, которые лежат в PermGen. С настройками сборки мусора я не экспериментировал, потому что старался написать код, который будет одинаково работать в различном окружении, в том числе в серверах приложений.
После указанного приема код заработал как следует, библиотека выгружалась, каталоги удалялись. Однако после перехода на Java 8 проблема вернулась. Разобраться, в чем дело, не было времени, но судя по всему, изменилось что-то в поведении сборщика мусора.
Поэтому пришлось применить тяжелую артиллерию, а именно JMX:
Как заставить Java собрать мусор
private static void dumpHeap() < try < Classclazz = Class.forName("com.sun.management.HotSpotDiagnosticMXBean"); MBeanServer server = ManagementFactory.getPlatformMBeanServer(); Object hotspotMBean = ManagementFactory.newPlatformMXBeanProxy( server, "com.sun.management:type=HotSpotDiagnostic", clazz); Method m = clazz.getMethod("dumpHeap", String.class, boolean.class); m.invoke(hotspotMBean, "nul", true); > catch (@SuppressWarnings("unused") RuntimeException e) < return; >catch (@SuppressWarnings("unused") ReflectiveOperationException e) < return; >catch (@SuppressWarnings("unused") IOException e) < return; >>
Через HotSpotDiagnosticMXBean вызываем сохранение дампа памяти. В качестве имени файла указываем nul, что в Windows означает то же самое, что и /dev/null в Unix. Второй параметр указывает на то, что в дамп должны быть выгружены только живые объекты. Именно этот параметр заставляет JVM выполнить полную сборку мусора.
Вот после этого лайфхака проблема удаления библиотеки из временного каталога больше не возникала. Итоговый код очистки файлов выглядит так:
this.classLoader = null; System.gc(); System.runFinalization(); System.gc(); System.runFinalization(); if (!delete(this.dir)) < dumpHeap(); if (!delete(this.dir)) < scheduleRemovalToTaskschd(this.dir); >>
Проверка при помощи OSGI
Для проверки качества кода я написал свой JDBC-драйвер, который полностью убирает за собой. Он работает как обертка вокруг любого другого драйвера, подгружаемого из отдельного classpath.
UnloadableDriver
public class UnloadableDriver implements Driver, AutoCloseable < private final Path dir; // временный каталог, подлежащий удалению private URLClassLoader classLoader; private DriverManagerProxy driverManager; private Driver driver; public UnloadableDriver() throws SQLException < . >@Override public void close() < . >. >
Этот драйвер я вставил в сервис OSGI на Apache Felix.
JDBCService
public interface JDBCService < Connection getConnection(String url, Properties properties) throws SQLException; >@Service(JDBCService.class) public class JDBCServiceImpl implements JDBCService < private UnloadableDriver driver; @Activate public void activate(ComponentContext ctx) throws SQLException < this.driver = new UnloadableDriver(); >@Deactivate public void deactivate() < this.driver.close(); this.driver = null; >@Override public Connection getConnection(String url, Properties info) throws SQLException < return this.driver.connect(url, properties); >>
При старте модуля через системную консоль Apache Felix, запущенную на Java 1.8.0_102, появляется временный каталог с dll-файлом. Файл заблокирован процессом java. Как только модуль останавливается, каталог удаляется автоматически. Если же вместо UnloadableDriver использовать DriverManager и обычную библиотеку из Embedded-Artifacts, то после обновления модуля возникает ошибка java.lang.UnsatisfiedLinkError: Native Library already loaded in another classloader.
Выводы
Универсального способа выгрузить системную динамическую библиотеку из Java-машины не существует, но задача эта решаема.
В Java существует немало мест, в которых можно случайно оставить ссылки на свои классы, и это является предпосылкой к утечкам памяти.
Даже если ваш код делает все корректно, утечка может быть привнесена какой-нибудь библиотекой, которую вы используете.
Особое внимание следует обратить на случаи, когда программа что-то загружает при помощи создаваемого во время выполнения нового загрузчика классов. Если останется хотя бы одна ссылка на один из загруженных классов, то класслоадер и все его классы останутся в памяти.
Чтобы обнаружить утечку памяти, надо сделать дамп и проанализировать при помощи специальных инструментов, таких как Eclipse MAT.
При обнаружении утечки памяти в сторонней библиотеке можно попробовать устранить ее при помощи одного из описанных в статье рецептов.