FactoryFinderのパフォーマンス/悪いキャッシュ


9

多くのxml処理を実行する巨大なクラスパスを備えた、かなり大きなjava eeアプリケーションを持っています。現在、一部の機能を高速化し、サンプリングプロファイラーを介して遅いコードパスを見つけようとしています。

私が気づいたことの1つは、特に呼び出しのあるコードの一部TransformerFactory.newInstance(...)が必死に遅いことです。私はこれを常に新しいインスタンスを作成するFactoryFinderメソッドまで追跡しfindServiceProviderましたServiceLoader。でServiceLoader javadocの私は、キャッシングについては、以下の注記が見つかりました:

プロバイダーは、遅延して、つまりオンデマンドで配置およびインスタンス化されます。サービスローダーは、これまでにロードされたプロバイダーのキャッシュを維持します。イテレータメソッドを呼び出すたびに、インスタンス化の順序で最初にキャッシュのすべての要素を生成するイテレータが返されます。次に、残りのプロバイダを遅延検索してインスタンス化し、それぞれを順番にキャッシュに追加します。キャッシュはreloadメソッドでクリアできます。

ここまでは順調ですね。これはOpenJDKs FactoryFinder#findServiceProviderメソッドの一部です:

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) {
            ...
        }
    }

通話へのすべてのfindServiceProvider呼び出しServiceLoader.load。これにより、毎回新しい ServiceLoader が作成されます。このように、ServiceLoadersキャッシングメカニズムはまったく使用されていないようです。すべての呼び出しは、要求されたServiceProviderのクラスパスをスキャンします。

私がすでに試したこと:

  1. javax.xml.transform.TransformerFactory特定の実装を指定するようなシステムプロパティを設定できることは知っています。このように、FactoryFinderはServiceLoaderプロセスとその超高速を使用しません。残念ながら、これはjvm全体のプロパティであり、私のjvmで実行されている他のJavaプロセスに影響します。たとえば、私のアプリケーションはSaxonに付属しており、Saxonに付属しcom.saxonica.config.EnterpriseTransformerFactoryていない別のアプリケーションを使用する必要があります。システムプロパティを設定するとすぐに、他のアプリケーションが起動しませんcom.saxonica.config.EnterpriseTransformerFactory。そのクラスパスに何もないためです。したがって、これは私の選択肢ではないようです。
  2. a TransformerFactory.newInstanceが呼び出され、TransformerFactoryをキャッシュするすべての場所をすでにリファクタリングしています。しかし、依存関係にはコードをリファクタリングできないさまざまな場所があります。

私の質問は、FactoryFinderがServiceLoaderを再利用しないのはなぜですか?システムプロパティを使用する以外に、このServiceLoaderプロセス全体を高速化する方法はありますか?FactoryFinderがServiceLoaderインスタンスを再利用するように、これをJDKで変更できませんでしたか?また、これは単一のFactoryFinderに固有のものではありません。この動作は、これまでに確認したjavax.xmlパッケージ内のすべてのFactoryFinderクラスで同じです。

OpenJDK 8/11を使用しています。アプリケーションはTomcat 9インスタンスにデプロイされています。

編集:詳細を提供する

次に、単一のXMLInputFactory.newInstance呼び出しの呼び出しスタックを示します。 ここに画像の説明を入力してください

ほとんどのリソースが使用される場所はですServiceLoaders$LazyIterator.hasNextService。このメソッドはgetResources、ClassLoaderを呼び出してMETA-INF/services/javax.xml.stream.XMLInputFactoryファイルを読み取ります。その呼び出しだけで、毎回約35ミリ秒かかります。

これらのファイルをより速くキャッシュするようにTomcatに指示する方法はありますか?


FactoryFinder.javaの評価に同意します。ServiceLoaderをキャッシュしているようです。openjdkソースをダウンロードしてビルドしてみましたか?それは大きな仕事のように聞こえますが、そうではないかもしれません。また、FactoryFinder.javaに対する問題を記述し、誰かが問題を見つけて解決策を提供するかどうかを確認することも価値があります。
djhallx

プロセスに-Dフラグを使用してプロパティを設定しようとしましたTomcatか?例:-Djavax.xml.transform.TransformerFactory=<factory class>.他のアプリのプロパティをオーバーライドしないでください。あなたの投稿はよく説明されており、おそらくあなたはそれを試しましたが、確認したいと思います。参照はjavax.xml.transform.TransformerFactoryシステムプロパティを設定する方法Tomcatの中HeapMemoryまたはJVM引数を設定する方法
のMichałZiober

回答:


1

35 msは、ディスクアクセス時間が関係しているように聞こえ、OSキャッシュの問題を示しています。

物事を遅くする可能性があるクラスパスにディレクトリ/非jarエントリがある場合。また、チェックされる最初の場所にリソースが存在しない場合。

ClassLoader.getResource構成(私は何年もTomcatに触れていません)または単にスレッドコンテキストクラスローダーを設定できる場合は、オーバーライドできますThread.setContextClassLoader


このような音はうまくいくかもしれません。私はこれを遅かれ早かれ見ます。ありがとうございました!
ワーグナーマイケル

1

これをデバッグするのにさらに30分かかる可能性があり、Tomcatがどのようにリソースキャッシングを行うかを調べました。

特にCachedResource.validateResources(上記のフレームグラフにあります)、私は興味を持っていました。がまだ有効であるtrue場合に戻りCachedResourceます。

protected boolean validateResources(boolean useClassLoaderResources) {
        long now = System.currentTimeMillis();
        if (this.webResources == null) {
            ...
        }

        // TTL check here!!
        if (now < this.nextCheck) {
            return true;
        } else if (this.root.isPackedWarFile()) {
            this.nextCheck = this.ttl + now;
            return true;
        } else {
            return false;
        }
    }

ように思えるCachedResourceが実際に生存時間(TTL)を持っています。Tomcatには実際にcacheTtlを設定する方法がありますが、この値を増やすことしかできません。リソースキャッシングの構成は、実際にはそれほど柔軟ではないようです。

したがって、Tomcatのデフォルト値は5000ミリ秒に設定されています。リクエストの間隔が5秒を少し超えていたため(グラフなどを見て)、パフォーマンステストの実行中に私をだました。そのため、私のすべてのリクエストは基本的にキャッシュなしで実行され、ZipFile.open毎回この重い負荷を引き起こしていました。

したがって、Tomcatの設定についてはあまり経験がないので、ここでの正しい解決策はまだわかりません。cacheTTLを増やすと、キャッシュが長く維持されますが、長期的には問題が解決されません。

概要

ここには2つの犯人がいると思います。

  1. ServiceLoaderを再利用しないFactoryFinderクラス。彼らがそれらを再利用しない正当な理由があるかもしれません-私は本当にそれを考えることはできません。

  2. TomcatがWebアプリケーションリソース(クラスパス内のファイル- ServiceLoader構成のような)の一定時間後にキャッシュを削除する

これとServiceLoaderクラスのシステムプロパティを定義していない場合を組み合わせると、毎秒遅いFactoryFinder呼び出しが発生しますcacheTtl

とりあえず、cacheTtlを増やしてより長い時間を過ごすことができます。またClassloader.getResources、これがこのパフォーマンスのボトルネックを取り除くための過酷な方法だと思っていても、オーバーライドするというトムホーティンスの提案を確認するかもしれません。でも一見の価値があるかもしれません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.