Mam dość dużą aplikację Java ee z ogromną ścieżką klasy, która wykonuje wiele przetwarzania XML. Obecnie próbuję przyspieszyć niektóre z moich funkcji i zlokalizować wolne ścieżki kodu za pomocą profilerów próbkujących.
Zauważyłem, że szczególnie części naszego kodu, w których mamy wywołania, TransformerFactory.newInstance(...)
są desperacko wolne. Śledziłem to FactoryFinder
metodą, która findServiceProvider
zawsze tworzy nową ServiceLoader
instancję. W ServiceLoader
javadoc znalazłem następującą notatkę o buforowaniu:
Dostawcy są zlokalizowani i utworzeni leniwie, czyli na żądanie. Program ładujący utrzymuje pamięć podręczną dostawców, którzy zostali załadowani do tej pory. Każde wywołanie metody iteratora zwraca iterator, który najpierw zwraca wszystkie elementy pamięci podręcznej w kolejności tworzenia instancji, a następnie leniwie lokalizuje i tworzy instancję pozostałych dostawców, dodając kolejno każdego z nich do pamięci podręcznej. Pamięć podręczną można wyczyścić za pomocą metody przeładowania.
Na razie w porządku. Jest to część FactoryFinder#findServiceProvider
metody OpenJDK :
private static <T> T findServiceProvider(final Class<T> type)
throws TransformerFactoryConfigurationError
{
try {
return AccessController.doPrivileged(new PrivilegedAction<T>() {
public T run() {
final ServiceLoader<T> serviceLoader = ServiceLoader.load(type);
final Iterator<T> iterator = serviceLoader.iterator();
if (iterator.hasNext()) {
return iterator.next();
} else {
return null;
}
}
});
} catch(ServiceConfigurationError e) {
...
}
}
Każde połączenie do findServiceProvider
połączeń ServiceLoader.load
. Tworzy to za każdym razem nowy ServiceLoader. W ten sposób wydaje się, że mechanizm buforowania ServiceLoaders w ogóle nie jest używany. Każde połączenie skanuje ścieżkę klasy w poszukiwaniu żądanego dostawcy usługi.
Co już próbowałem:
- Wiem, że możesz ustawić właściwość systemową, np.
javax.xml.transform.TransformerFactory
Określić konkretną implementację. W ten sposób FactoryFinder nie korzysta z procesu ServiceLoader i jego superszybkiego działania. Niestety jest to właściwość dla całego środowiska JVM i wpływa na inne procesy Java uruchomione w moim środowisku JVM. Na przykład moja aplikacja jest dostarczana z Saxonem i powinienem użyćcom.saxonica.config.EnterpriseTransformerFactory
Mam inną aplikację, która nie jest dostarczana z Saxonem. Gdy tylko ustawię właściwość systemową, moja inna aplikacja nie uruchamia się, ponieważ nie macom.saxonica.config.EnterpriseTransformerFactory
jej w ścieżce klasy. Więc nie wydaje mi się to rozwiązaniem. - Już dokonałem refaktoryzacji każdego miejsca, w którym
TransformerFactory.newInstance
nazywa się a, i buforuję TransformerFactory. Ale w moich zależnościach są różne miejsca, w których nie mogę refaktoryzować kodu.
Moje pytania brzmią: dlaczego FactoryFinder nie używa ponownie programu ServiceLoader? Czy istnieje sposób na przyspieszenie całego procesu ServiceLoader, inny niż użycie właściwości systemowych? Czy nie można tego zmienić w JDK, aby FactoryFinder ponownie używał instancji ServiceLoader? Nie dotyczy to również pojedynczego FactoryFinder. To zachowanie jest takie samo dla wszystkich klas FactoryFinder w javax.xml
pakiecie, na który patrzyłem do tej pory.
Używam OpenJDK 8/11. Moje aplikacje są wdrażane w instancji Tomcat 9.
Edycja: podając więcej szczegółów
Oto stos wywołań dla pojedynczego wywołania XMLInputFactory.newInstance:
Tam, gdzie wykorzystuje się większość zasobów, znajduje się ServiceLoaders$LazyIterator.hasNextService
. Ta metoda wywołuje getResources
ClassLoader w celu odczytania META-INF/services/javax.xml.stream.XMLInputFactory
pliku. Samo połączenie za każdym razem zajmuje około 35 ms.
Czy istnieje sposób, aby poinstruować Tomcat, aby lepiej buforował te pliki, aby były szybciej dostarczane?
źródło
-D
flagi do swojegoTomcat
procesu? Na przykład:-Djavax.xml.transform.TransformerFactory=<factory class>.
nie powinno zastępować właściwości innych aplikacji. Twój post jest dobrze opisany i prawdopodobnie próbowałeś, ale chciałbym to potwierdzić. Zobacz Jak ustawić właściwość systemową Javax.xml.transform.TransformerFactory , Jak ustawić argumenty HeapMemory lub JVM w TomcatOdpowiedzi:
35 ms wydaje się, że są zaangażowane czasy dostępu do dysku, co wskazuje na problem z buforowaniem systemu operacyjnego.
Jeśli w ścieżce klasy znajdują się wpisy katalogu / nie-jar, które mogą spowolnić działanie. Również jeśli zasób nie jest obecny w pierwszej sprawdzanej lokalizacji.
ClassLoader.getResource
można zastąpić, jeśli można ustawić moduł ładujący klasy kontekstu wątku, albo przez konfigurację (nie dotykałem tomcat od lat), albo po prostuThread.setContextClassLoader
.źródło
Mogłem uzyskać kolejne 30 minut na debugowanie tego i przyjrzałem się, jak Tomcat wykonuje buforowanie zasobów.
W szczególności
CachedResource.validateResources
(które można znaleźć w powyższym flamegrafie) było dla mnie interesujące. Zwraca,true
jeśliCachedResource
nadal jest ważny:Wygląda na to, że CachedResource ma naprawdę czas na życie (ttl). W Tomcat jest sposób na skonfigurowanie cacheTtl, ale można tylko zwiększyć tę wartość. Wygląda na to, że konfiguracja buforowania zasobów nie jest tak łatwo elastyczna.
Więc mój Tomcat ma skonfigurowaną domyślną wartość 5000 ms. To mnie oszukało podczas testowania wydajności, ponieważ miałem trochę ponad 5 sekund między moimi żądaniami (patrząc na wykresy i inne rzeczy). Właśnie dlatego wszystkie moje żądania w zasadzie działały bez pamięci podręcznej i za
ZipFile.open
każdym razem powodowały to obciążenie .Ponieważ nie mam zbyt dużego doświadczenia z konfiguracją Tomcat, nie jestem jeszcze pewien, jakie jest właściwe rozwiązanie. Zwiększenie cacheTTL utrzymuje pamięć podręczną dłużej, ale nie rozwiązuje problemu na dłuższą metę.
Podsumowanie
Myślę, że w rzeczywistości są tutaj dwaj winowajcy.
Klasy FactoryFinder nie wykorzystują ponownie programu ServiceLoader. Może istnieć ważny powód, dla którego nie używają ich ponownie - nie mogę jednak wymyślić żadnego z nich.
Tomcat eksmitujący pamięć podręczną po ustalonym czasie dla zasobu aplikacji internetowej (pliki w ścieżce klas - jak
ServiceLoader
konfiguracja)Po połączeniu tego z brakiem zdefiniowania właściwości systemowej dla klasy ServiceLoader, otrzymujemy powolne wywołanie FactoryFinder co
cacheTtl
sekundę.Na razie mogę żyć z powiększaniem cacheTtl do dłuższego czasu. Mogę również rzucić okiem na sugestię Toma Hawtinsa, by zastąpić ją,
Classloader.getResources
nawet jeśli wydaje mi się, że jest to trudny sposób na pozbycie się tego wąskiego gardła. Warto jednak na to spojrzeć.źródło