2012-03-05 3 views
6

우리가 일하고있는 곳에서는 우리 애플리케이션 중 하나에서 JVM의 힙 공간이 부족하여 문제가 발생합니다. 프로파일 러를 사용하여 힙 덤프 (heap-dump)를 보는 것을 포함하여이 문제의 원인을 찾아 보았습니다. 그러나 지금은 꽤 많이 갇혀 있습니다.Hibernate, SessionFactoryObjectFactory 및 OutOfMemoryError : java heap space

처음에는 문제의 시스템에 대해 약간의 설명을 들었습니다. 조직에 대한 기록을 유지하기 위해 Spring과 Hibernate를 사용하는 Java 응용 프로그램입니다. 이 시스템은 이러한 유형의 데이터를 담당하는 정부 기관에서 조직에 대한 데이터를 검색하는 데 사용되는 일련의 웹 서비스 클라이언트로 구성됩니다. 또한 시스템은 웹 서비스 호출의 캐시 역할을하는 로컬 데이터베이스를 유지하므로 조직에 대한 최초 정보가 요청되고 로컬 관계형 데이터베이스에 저장되며 검색에 사용됩니다 다음 요청에 대한 데이터 Hibernate는이 데이터베이스와 통신하기 위해 사용된다.

앞에서 설명한 문제는 일정 기간이 지나면 응용 프로그램이 OutOfMemoryError : java heap space로 중단되기 시작한다는 것입니다. 필자는 Eclipse + MAT를 사용하여 힙 덤프를 살펴본 결과, 할당 된 메모리의 약 85 %를 차지하는 Hibernate의 SessionFactoryObjectFactory로 확인했다. 나는 정확히 어떤 유형의 객체가이 안에 유지되는지를 식별하는 것이 약간 어렵다는 것을 알았다. 최상위 레벨에는 org.hibernate.impl.SessionFactoryObjectFactory가 포함 된 Glassfish WebappClassLoader가 있습니다. org.hibernate.util.FastHashMap은 java.util.HashMap을 포함하고있다. 여기에는 각각 HashMap-entry, org.hibernate.impl.SessionFactoryImpl 및 String을 포함하는 여러 항목이 들어 있습니다. HashMap 엔트리는 HashMap 엔트리, SessionFactoryImpl 및 String과 같은 세 개의 객체를 포함하며,이 구조는 여러 번 반복됩니다. SessionFactoryImpls는 다수의 객체들을 포함하고 있습니다. 특히 org.hibernate.persister.entity.SingleTableEntityPersister는 많은 String과 HashMap을 포함하고 있습니다. 문자열 중 일부는 도메인 객체의 변수를 참조하고 일부는 SQL 문을 포함합니다.

언뜻 보면이 개체가 불필요한 메모리 양을 차지하고있는 것처럼 보였습니다 (덤프 파일은 800MB 였고이 중 650MB는 SessionFactoryObjectFactory에 의해 점유되었습니다). 그래서 개체 로딩과 언로드 로깅을 활성화하고 시스템에 대한 데이터 (다른 시스템의 웹 서비스 호출을 통해). 여기서 주목 한 것은 객체 로딩에 대한 많은 메시지가 있었지만 언로드 된 객체 (라이브러리 객체를 언로드하는 유일한 객체)에 대해서는 거의 없었습니다. 이것은 한때 객체 (예를 들어 조직)가 메모리에로드되면 언로드되지 않는다는 것을 의미합니다. 즉, 시간이 지남에 따라 시스템의 메모리가 부족하게됩니다. (이것은 로그에서 발견 된 내용을 기반으로 한 공정한 가정입니까?)

그런 다음이 원인을 찾으려고 시도했지만 훨씬 어려웠습니다. Hibernate에 의해 로딩 된 객체는 그들의 세션이 살아있는 동안 계속 살아남을 것이므로 Spring의 HibernateDaoSupport # getSession()에 대한 호출을 HibernateDaoSupport # getSessionFactory(). getCurrentSession()으로 대체함으로써 세션이 처리되는 방식을 변경하려고 시도했다. 이것은 문제에 아무런 영향을 미치지 않았습니다. 나는 또한 ... getCurrentSession(). flush()와 .clear()에 대한 호출을 문제의 Dao 메소드의 마지막 블록에서 추가하려고 시도했다. (Dao 메소드는 모두 @Transactional로 주석 처리됩니다. 즉, 세션은 @ Transactional-method 내에서만 살아 있어야하고, getCurrentSession() (?)을 호출 할 때 메소드 연속 호출은 다른 세션을 가져야합니다.)

그래서 지금은 다른 영역을 확인하기 위해 올 때 상당히 혼란 스럽습니다. 누구나 볼만한 곳과 찾을 곳에 대한 생각이나 조언이 있습니까?

힙 덤프는 org.hibernate.impl.SessionFactoryImpl의 인스턴스가 많다는 것을 보여 줬습니다. 예상대로입니까? (나는 SessionFactory의 인스턴스 하나 또는 몇 개의 꼭대기 만 있어야한다고 생각했을 것이다.)

미리 답변 해 주셔서 감사합니다.

감사합니다,

Tobb

편집 :

내가 실제로 문제를 해결하기 위해 mananged 것 같아요 : 그것은 다른 개체에 종속가에서 처리 된 그런 식으로 밝혀졌다

을 webservice-classes가 문제였습니다. 이것은 webservice 클래스의 생성자에서 새로운 ClassPathXmlApplicationContext (...)를 호출하여 해결되었습니다. 이것은 각 요청 (또는 적어도 각 세션)에 대해로드되는 많은 객체를 발생 시켰고, 다시로드되지 않았다 (주로 Hibernate의 SessionFactoryImpl). 나는 webservice 클래스를 변경하여 의존성을 주입하고 지금까지 프로파일 러를 사용하여 보았던 것을 형성했다. 여러 SessionFactoryImpl 객체의 문제가 해결되었다.

GlassFish 2.x에서 GlassFish 3.x로 업그레이드하면 문제가 악화되었을 수 있다고 생각합니다. 웹 서비스 클래스가 인스턴스화되는 방법에 따라 약간의 차이가있을 수 있습니다.

나뿐만 아니라 단지 문제 자체 대신에, 대답이 문제에 솔루션을 추가 할 수 있습니다

답변

5

:

범인은 여기에 봄 콩의 로딩이 현저에서, 다양한 개체에서 수행 된 방법이었다 webservice-classes. 이것은

new ClassPathXmlApplicationContext(...)

에서 각각의 webservice-classes에서 호출되었습니다. 이렇게하면 가비지 수집을 피하기 위해로드 된 객체의 부작용이 있습니다 (일부 Spring 내부에서 참조하기 때문에). 글래스 피시 버전의 변화가 webservice 객체의 인스턴스화에 어떤 영향을 미쳤다. 새로운 객체에 대한 호출이 많아지고 메모리가 차지하는 정크 객체가 가득 차서 충돌이 발생할 때까지 호출합니다.

public class ContextHolder { 
    private static ClassPathXmlApplicationContext context; 

    public static getSpringContext() { 
     if (context == null) { 
      context = new ClassPathXmlApplicationContext("applicationContext.xml"); 
     } 
     return context; 
    } 
} 

그리고이 전화 :

문제에 대한 해결책은, 이런 식으로 뭔가를 정적 공장 패턴을 사용하여, 밖으로 다른 클래스로

new ClassPathXmlApplicationContext(...)

로 통화를 이동했다 ClassPathXmlApplicationContext를 새로 작성하는 대신 webservice-classes를 사용하십시오.

업데이트 :

try (final ClassPathXmlApplicationContext applicationContext = 
      new ClassPathXmlApplicationContext("applicationContext.xml")) { 
    //do stuff 
} 
+0

가 또는 정적 공장으로, 나는'ClassPathXmlApplicationContext'이 될 수있는'close'-방법이 있다고 생각 :

ClassPathXmlApplicationContext 그렇게 try-with-resource 또 다른 가능성은, Closeable/Autocloseable입니다 이 문제를 피하기 위해 호출됩니다. – Tobb