Как стать автором
Обновить

Комментарии 21

А-а-а! Зачем модифицировать приватные поля через Reflection?
С выходом Java 9 — сразу до свидания!

Не надо модифицировать java.library.path. Про System.load() слышали?
Дергать через reflection внутренние поля мне не доставляет никакого удовольствия. Поэтому я по максимуму стараюсь их обойти. Использую только когда не нашел другого способа.

Про System.load() хорошая идея. Это как раз то, что можно было бы сделать в JDBCDriverProxyImpl. Я пробовал экспериментировать с System.load(), но ничего не получилось, из-за того, что библиотеку грузил в разных класслоадерах, а добавить JDBCDriverProxyImpl, придумал уже позже, когда уперся в DriverManager.

Надо к этой функции вернуться.

Тут есть еще такой момент, сторонняя библиотека подгружает dll не всегда, а только при определенных параметрах, я проконтролировать это не могу. Но тут можно сделать превентивную загрузку, даже если на самом деле библиотека не понадобится.
Ну просто не вы же первый такое придумали с загрузкой. Обычно распаковывают молча куда-нибудь внутрь условного home нужную dll/so и делают System.load. Для выгрузки всегда считалось, что если загрузивший classloader не используется и собран сборщиком, то библиотека должна отгрузиться. Но как на самом деле — я лично не проверял) Так что в целом инфа интересная.
С выходом Java 9 — сразу до свидания!

Насколько мне известно, в Java 9 ломается доступ к классам в недоступных пакетах. Если пакет доступен, то setAccessible работает как раньше. Во-всяком случае, этот тест прекрасно работает в 9-ea+138:


import java.lang.reflect.*;
import java.util.*;

public class Test {
  public static void main(String[] args) throws Exception {
    Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths");
    usrPathsField.setAccessible(true);
    String[] paths = (String[]) usrPathsField.get(null);
    System.out.println(Arrays.toString(paths));
  }
}
Всё верно. Со страшилками про Java 9 я переборщил :) В данном конкретном случае будет работать. ПОКА будет. Детали реализации, такие как приватные поля, могут измениться в любой момент, даже с минорным апдейтом.

Кое-что в класслоадерах уже изменили в Java 9. Например, системный ClassLoader больше не наследник URLClassLoader. Но я встречал проекты, где просто кастовали системный ClassLoader, чтобы взять у него URLClassPath. И фокус с usr_paths из той же серии.

С этим не поспоришь. Закладываясь на недокументированные детали реализации, мы подписываемся на постоянную их поддержку. Либо надо мониторить все изменения в этом классе и вовремя добавлять новые ветки, либо быть готовым, что всё сломается и виноват будешь только сам. Скользкая дорожка, в общем.

А чем не подошёл стандартный в таких случаях подход, когда есть маленький лаунчер, который всё копирует, куда надо, потом запускает в отдельном процессе основную программу с нужными параметрами, дожидается её завершения, после чего всё подчищает?
Это решение я тоже рассматривал. Как более быстрый workaround я выбрал Windows Task Scheduler, а когда уже появилось время на эксперименты, очень хотелось увидеть именно как то, как библиотека выгружается.
Понятно. Что касается экспериментов, любопытно поисследовать, почему gc() + runFinalization() работал в Java 7, но перестал в Java 8. Как я уже писал в другой теме, сборки, вызванные System.gc() и дампом хипа, на уровне JVM принципиально не отличаются. Скорее всего, дамп помогал по другой причине, например, из-за таймингов. Не знаю, мне самому не удалось воспроизвести проблему: в JDK 8 System.gc() стабильно работал, и финализаторы вызывались. Вот, если бы в статье было про это — было бы интересно!
НЛО прилетело и опубликовало эту надпись здесь
А то, что предлагается, не костыли? Да и сколько надо было писать и эксперементировать :)

Вот это — костыль:
System.gc();
System.runFinalization();

А если JVM оба раза откажется это делать, то что? Уж лучше лаунчер.

По факту очень многие Java-приложения делают такой маленький нативный лаунчер (как минимум, все популярные IDE — IDEA, Eclipse, NetBeans). Параметры JVM записываются в какой-нибудь конфиг или передаются как-нибудь вроде -Jblahblah. Как минимум, это надёжнее: не всегда .jar на произвольной системе запутится как запускаемый файл. Сделать такой лаунчер совсем нетрудно, есть даже готовые генераторы этих лаунчеров.

Хотя этот подход применяется повсеместно, мне он не нравится тем, что это будет уже не программа на Java, а программа на C++ с примочкой на Java. Если я лаунчер на напишу на C#, то почему бы мне и в базу данных не слазить из C#? И зачем мне тогда JDBC-драйвер? Вот если бы этот лаунчер входил в JDK, тогда другое дело. В Eclipse такой плагин наверняка есть, но, видимо, он не входит в поставку JDT, потому что на глаза ни разу не попадался.
Если дело только в этом, можно и на Java лаунчер сделать. В JAR будет прописан Main-Class лаунчера, который запускает новый Java процесс с тем же JAR в classpath, но с другим Main классом.
Все же аргументы Java придется задавать отдельно, а иначе ни jmxremote, ни отладочный порт поднять не получится.

Вот магия с ThreadLocal-ами — это самый ад здесь. Мало того что суровая завязка на реализацию. Реализация ThreadLocalMap абсолютно не потокобезопасная, потому что предполагается, что в неё никогда не залезает никто из чужого потока. Если во время вашего клинапа в другом потоке записывается какой-нибудь любой другой тредлокал, может в принципе много интересного произойти. Можно ведь и на рехэшинг напороться. Или вы не затрёте все записи, какие хотите затереть, или затрёте лишнюю запись.


Вообще правильно Лёша Шипилёв говорит. Если вы сталкиваетесь с неразрешимой проблемой в Java, правильно — написать в мейлинг-листы OpenJDK, обсудить, предложить JEP, сделать патч, дождаться выхода следующей версии Java и радоваться. А хакать джаву неправильно, работоспособность вашей программы тогда гарантировать будет невозможно. Понятно, что это слишком идеалистическая картина мира, но всё-таки стоит иметь в виду такой вариант.

Про общение с командой OpenJDK поделитесь, пожалуйста, как это правильно делать. У меня с ними взаимодействие получается какое-то одностороннее. Послал баг-репорт, а в ответ — тишина. Сейчас смотрю, решили исправить в Java 9, не прошло и года. Наверное, у разработчиков есть какие-то форумы, где с ними можно нормально пообщаться.

Что такое JEP?
Про общение с командой OpenJDK поделитесь, пожалуйста, как это правильно делать

1. Идем и находим нужный mail list
2. Справшиваем там и/или ищем похожие проблемы в трекере

Что такое JEP?

JDK Enhancement-Proposal (JEP-2). Можно все там не читать. Это процесс для внесения новых фич и изменений в JDK.

lany для того, чтобы предложить JEP, необходимо иметь учетную запись в трекере, а значит быть OpenJDK автором.

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

прошу прощения за тупой вопрос — но зачем вообще надо было добиваться стопроцентной выгрузки длл-ки? просто чтобы не оставлять следов в temp каталоге?
Да.
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации