FactoryFinder 성능 / 잘못된 캐싱


9

많은 XML 처리를 수행하는 거대한 클래스 경로가있는 다소 큰 Java ee 응용 프로그램이 있습니다. 현재 샘플링 기능을 통해 일부 기능의 속도를 높이고 느린 코드 경로를 찾으려고합니다.

내가 알았던 한 가지는 특히 코드가 호출되는 코드의 일부 TransformerFactory.newInstance(...)가 필연적으로 느리다는 것입니다. 나는 항상 새로운 인스턴스를 만드는 FactoryFinder방법으로 이것을 추적했습니다 . 에서 의 javadoc 나는 캐싱에 대해 다음과 같은 메모를 발견 :findServiceProviderServiceLoaderServiceLoader

공급자는 느리게 (즉, 요청시) 인스턴스화되고 인스턴스화됩니다. 서비스 로더는 지금까지로드 된 제공자의 캐시를 유지 보수합니다. iterator 메소드를 호출 할 때마다 먼저 캐시의 모든 요소를 ​​인스턴스화 순서로 생성 한 다음 나머지 제공자를 느리게 찾아서 인스턴스화하여 각 제공자를 캐시에 차례로 추가합니다. 캐시는 reload 메소드를 통해 지울 수 있습니다.

여태까지는 그런대로 잘됐다. 이것은 OpenJDK 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. 나는 이미 TransformerFactory.newInstance호출 된 모든 장소를 리팩터링 하고 TransformerFactory를 캐시합니다. 그러나 코드를 리팩터링 할 수없는 다양한 종속성이 있습니다.

내 질문은 : FactoryFinder가 ServiceLoader를 재사용하지 않는 이유는 무엇입니까? 시스템 속성을 사용하는 것 외에이 전체 ServiceLoader 프로세스의 속도를 높일 수있는 방법이 있습니까? FactoryFinder가 ServiceLoader 인스턴스를 재사용 할 수 있도록 JDK에서이를 변경할 수 없습니까? 또한 이것은 단일 FactoryFinder에만 국한되지 않습니다. 이 bahaviour는 javax.xml지금까지 본 패키지의 모든 FactoryFinder 클래스에 대해 동일합니다 .

OpenJDK 8/11을 사용하고 있습니다. 내 응용 프로그램은 Tomcat 9 인스턴스에 배포됩니다.

편집 : 자세한 내용 제공

단일 XMLInputFactory.newInstance 호출에 대한 호출 스택은 다음과 같습니다. 여기에 이미지 설명을 입력하십시오

대부분의 리소스가 사용되는 위치는입니다 ServiceLoaders$LazyIterator.hasNextService. 이 메소드 getResourcesMETA-INF/services/javax.xml.stream.XMLInputFactory파일 을 읽기 위해 ClassLoader를 호출 합니다. 그 호출만으로도 매번 약 35ms가 걸립니다.

Tomcat에게 이러한 파일을 더 잘 캐시하여 더 빠르게 제공하도록 지시하는 방법이 있습니까?


FactoryFinder.java에 대한 귀하의 평가에 동의합니다. ServiceLoader를 캐싱해야합니다. openjdk 소스를 다운로드하여 빌드 해 보셨습니까? 나는 큰 일처럼 들리지만 그렇지 않을 수도 있습니다. 또한 FactoryFinder.java에 대해 문제를 작성하고 누군가가 문제를 선택하고 해결책을 제공하는지 확인하는 것이 좋습니다.
djhallx

프로세스에 -D플래그를 사용하여 속성을 설정하려고 했습니까 Tomcat? 예를 들어 -Djavax.xml.transform.TransformerFactory=<factory class>.다른 앱의 속성을 재정의해서는 안됩니다. 귀하의 게시물이 잘 설명되어 있으며 아마도 시도했지만 확인하고 싶습니다. 참조 javax.xml.transform.TransformerFactory의 시스템 속성을 설정하는 방법 , 어떻게 톰캣에 HeapMemory 또는 JVM 인수를 설정
마이클 Ziober에게

답변:


1

디스크 액세스 시간이 관련된 것처럼 35ms가 들리고 OS 캐시에 문제가 있음을 나타냅니다.

클래스 경로에 디렉토리 / 비 jar 항목이 있으면 속도가 느려질 수 있습니다. 또한 확인 된 첫 번째 위치에 자원이없는 경우.

ClassLoader.getResource구성 (수년 동안 바람둥이를 만지지 않은)을 통해 또는 스레드 컨텍스트 클래스 로더를 설정할 수있는 경우 재정의 할 수 있습니다 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의 기본값은 5000ms입니다. 내 요청 사이에 5 초 이상 (그래프와 물건을 보는) 시간이 있었기 때문에 성능 테스트를 수행하는 동안 나를 속였습니다. 그렇기 때문에 모든 요청이 기본적으로 캐시없이 실행되어 ZipFile.open매번 이 무거운 것을 트리거했습니다 .

따라서 Tomcat 구성에 대한 경험이 많지 않으므로 여기에 올바른 솔루션이 무엇인지 아직 확실하지 않습니다. cacheTTL을 늘리면 캐시는 더 오래 유지되지만 장기적으로 문제를 해결하지는 않습니다.

요약

실제로 여기에는 두 가지 범인이 있다고 생각합니다.

  1. FactoryFinder 클래스는 ServiceLoader를 재사용하지 않습니다. 그들이 재사용하지 않는 타당한 이유가있을 수 있습니다-나는 정말로 생각할 수는 없습니다.

  2. 웹 응용 프로그램 리소스 ( ServiceLoader구성 과 같은 클래스 경로의 파일)에 대해 일정 시간이 지난 후 캐시를 제거하는 Tomcat

이것을 ServiceLoader 클래스에 대한 시스템 속성을 정의하지 않은 상태로 결합하면 매초마다 느린 FactoryFinder 호출이 발생합니다 cacheTtl.

지금은 cacheTtl을 더 길게 늘릴 수 있습니다. 나는 Classloader.getResources이것이 성능 병목 현상을 제거하는 가혹한 방법이라고 생각하더라도 Tom Hawtins의 재정의 제안을 살펴볼 수도 있습니다 . 그래도 볼만한 가치가 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.