Saya punya aplikasi java ee agak besar dengan classpath besar melakukan banyak pemrosesan xml. Saat ini saya mencoba untuk mempercepat beberapa fungsi saya dan menemukan jalur kode lambat melalui sampling profiler.
Satu hal yang saya perhatikan adalah bahwa terutama bagian dari kode kita di mana kita memiliki panggilan seperti TransformerFactory.newInstance(...)
sangat lambat Saya melacak ini ke FactoryFinder
metode yang findServiceProvider
selalu membuat ServiceLoader
contoh baru . Di ServiceLoader
javadoc saya menemukan catatan berikut tentang caching:
Penyedia ditempatkan dan dipakai secara malas, yaitu, sesuai permintaan. Pemuat layanan menyimpan cache dari penyedia yang telah dimuat sejauh ini. Setiap permohonan metode iterator mengembalikan iterator yang pertama-tama menghasilkan semua elemen cache, dalam urutan instantiation, dan kemudian dengan malas menemukan dan membuat instantiate penyedia yang tersisa, menambahkan masing-masing ke cache pada gilirannya. Cache dapat dihapus melalui metode reload.
Sejauh ini bagus. Ini adalah bagian dari FactoryFinder#findServiceProvider
metode 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) {
...
}
}
Setiap panggilan ke findServiceProvider
panggilan ServiceLoader.load
. Ini menciptakan yang baru ServiceLoader setiap kali. Dengan cara ini tampaknya tidak ada penggunaan mekanisme caching ServiceLoaders sama sekali. Setiap panggilan memindai classpath untuk ServiceProvider yang diminta.
Apa yang sudah saya coba:
- Saya tahu Anda dapat mengatur properti sistem ingin
javax.xml.transform.TransformerFactory
menentukan implementasi tertentu. Dengan cara ini FactoryFinder tidak menggunakan proses ServiceLoader dan sangat cepat. Sayangnya ini adalah properti jvm lebar dan mempengaruhi proses java lainnya yang berjalan di jvm saya. Misalnya aplikasi saya dikirim dengan Saxon dan harus menggunakancom.saxonica.config.EnterpriseTransformerFactory
saya punya aplikasi lain yang tidak dikirimkan dengan Saxon. Segera setelah saya mengatur properti sistem, aplikasi saya yang lain gagal untuk memulai, karena tidak adacom.saxonica.config.EnterpriseTransformerFactory
di classpath-nya. Jadi ini sepertinya tidak menjadi pilihan bagi saya. - Saya sudah refactored setiap tempat di mana a
TransformerFactory.newInstance
dipanggil dan menyimpan cache TransformerFactory. Tetapi ada berbagai tempat di dependensi saya di mana saya tidak bisa memperbaiki kode.
Pertanyaan saya adalah: Mengapa FactoryFinder tidak menggunakan kembali ServiceLoader? Apakah ada cara untuk mempercepat seluruh proses ServiceLoader ini selain menggunakan properti sistem? Tidak bisakah ini diubah di JDK sehingga FactoryFinder menggunakan instance ServiceLoader? Juga ini tidak spesifik untuk FactoryFinder tunggal. Bahaviour ini sama untuk semua kelas FactoryFinder dalam javax.xml
paket yang telah saya lihat sejauh ini.
Saya menggunakan OpenJDK 8/11. Aplikasi saya digunakan dalam instance Tomcat 9.
Sunting: Memberikan rincian lebih lanjut
Berikut adalah tumpukan panggilan untuk panggilan XMLInputFactory.newInstance tunggal:
Di mana sebagian besar sumber daya digunakan ServiceLoaders$LazyIterator.hasNextService
. Metode ini memanggil getResources
ClassLoader untuk membaca META-INF/services/javax.xml.stream.XMLInputFactory
file. Panggilan itu sendiri membutuhkan sekitar 35 ms setiap kali.
Apakah ada cara untuk menginstruksikan Tomcat untuk melakukan cache yang lebih baik pada file-file ini sehingga dilayani lebih cepat?
sumber
-D
bendera keTomcat
proses Anda ? Misalnya:-Djavax.xml.transform.TransformerFactory=<factory class>.
Seharusnya tidak menimpa properti untuk aplikasi lain. Posting Anda dijelaskan dengan baik dan mungkin Anda telah mencobanya tetapi saya ingin mengonfirmasi. Lihat Cara mengatur properti sistem Javax.xml.transform.TransformerFactory , Cara mengatur Argumen HeapMemory atau JVM di TomcatJawaban:
35 ms terdengar seperti ada waktu akses disk yang terlibat, dan itu menunjukkan masalah dengan cache OS.
Jika ada direktori / entri non-jar di classpath yang dapat memperlambat segalanya. Juga jika sumber daya tidak ada di lokasi pertama yang diperiksa.
ClassLoader.getResource
dapat ditimpa jika Anda dapat mengatur loader kelas konteks thread, baik melalui konfigurasi (saya belum menyentuh kucing hutan selama bertahun-tahun) atau hanyaThread.setContextClassLoader
.sumber
Saya bisa mendapatkan 30 menit untuk men-debug ini dan melihat bagaimana Tomcat melakukan Caching Sumber Daya.
Khususnya
CachedResource.validateResources
(yang dapat ditemukan di flamegraph di atas) menarik bagi saya. Itu kembalitrue
jikaCachedResource
masih valid:Sepertinya CachedResource sebenarnya memiliki waktu untuk hidup (ttl). Sebenarnya ada cara di Tomcat untuk mengkonfigurasi cacheTtl tetapi Anda hanya dapat meningkatkan nilai ini. Konfigurasi caching sumber daya tidak terlalu fleksibel sepertinya.
Jadi Tomcat saya memiliki nilai default 5.000 ms yang dikonfigurasi. Ini menipu saya saat melakukan pengujian kinerja karena saya memiliki sedikit lebih dari 5 detik antara permintaan saya (melihat grafik dan lainnya). Itu sebabnya semua permintaan saya pada dasarnya berjalan tanpa cache dan memicu ini berat
ZipFile.open
setiap kali.Jadi karena saya tidak terlalu berpengalaman dengan konfigurasi Tomcat, saya belum yakin apa solusi yang tepat di sini. Meningkatkan cacheTTL membuat cache lebih lama tetapi tidak memperbaiki masalah dalam jangka panjang.
Ringkasan
Saya pikir sebenarnya ada dua penyebab di sini.
Kelas FactoryFinder tidak menggunakan kembali ServiceLoader. Mungkin ada alasan yang sah mengapa mereka tidak menggunakan kembali - saya tidak bisa memikirkannya.
Tomcat mengusir cache setelah waktu yang tetap untuk sumber daya aplikasi web (file di classpath - seperti
ServiceLoader
konfigurasi)Gabungkan ini dengan tidak menetapkan Properti Sistem untuk kelas ServiceLoader dan Anda mendapatkan panggilan FactoryFinder lambat setiap
cacheTtl
detik.Untuk saat ini saya dapat hidup dengan meningkatkan cacheTtl ke waktu yang lebih lama. Saya juga mungkin melihat saran Tom Hawtins untuk mengesampingkan
Classloader.getResources
meskipun saya agak berpikir ini adalah cara yang keras untuk menghilangkan hambatan kinerja ini. Mungkin layak untuk dilihat.sumber