2012-03-05 17 views
6

Ở nơi tôi làm việc, chúng tôi đang gặp sự cố với JVM hết dung lượng lưu trữ trong một trong các ứng dụng của chúng tôi. Tôi đã thực hiện một số tìm kiếm nguyên nhân của điều này, bao gồm cả nhìn vào một đống-dump với một profiler, nhưng bây giờ tôi khá nhiều khó khăn.Hibernate, SessionFactoryObjectFactory và OutOfMemoryError: không gian java heap

Đầu tiên, một chút về hệ thống được đề cập: Đây là một ứng dụng Java, sử dụng Spring và Hibernate, để lưu giữ hồ sơ về các tổ chức. Hệ thống này bao gồm một bộ các máy khách webservice được sử dụng để truy xuất dữ liệu về các tổ chức từ cơ quan chính phủ chịu trách nhiệm về loại dữ liệu này. Ngoài ra, hệ thống giữ một cơ sở dữ liệu cục bộ với dữ liệu như vậy, hoạt động như một bộ nhớ cache cho các cuộc gọi webservice, do đó thông tin lần đầu tiên về một tổ chức được yêu cầu, nó được lưu trong cơ sở dữ liệu quan hệ cục bộ. dữ liệu cho các yêu cầu sau. Hibernate được sử dụng để giao tiếp với cơ sở dữ liệu này.

Sự cố, như được chỉ ra trước đó, là sau một khoảng thời gian, ứng dụng bắt đầu gặp lỗi với OutOfMemoryError: không gian java heap. Tôi đã xem xét một đống-dump bằng cách sử dụng Eclipse + MAT, và xác định thủ phạm như là Hibernate's SessionFactoryObjectFactory, chiếm khoảng 85% bộ nhớ được cấp phát (tất cả bộ nhớ được lưu giữ). Tôi đã tìm thấy nó một chút khó khăn để xác định chính xác loại đối tượng được giữ trong này. Ở cấp cao nhất có Glassfish WebappClassLoader, chứa org.hibernate.impl.SessionFactoryObjectFactory. Có trong đây là một org.hibernate.util.FastHashMap, mà lần lượt chứa một java.util.HashMap. Điều này chứa một số mục, mỗi mục có chứa một mục nhập HashMap, một org.hibernate.impl.SessionFactoryImpl và một String. HashMap-entry lần lượt chứa cùng ba đối tượng, một HashMap-entry, một SessionFactoryImpl và một String, và cấu trúc này lặp lại chính nó một số lần. SessionFactoryImpls chứa một số đối tượng, đáng chú ý nhất là org.hibernate.persister.entity.SingleTableEntityPersister, chứa một số chuỗi và HashMaps. Một số chuỗi tham chiếu đến các biến trong các đối tượng miền và một số chứa các câu lệnh sql. Có vẻ như cái nhìn đầu tiên là đối tượng này đang chiếm một lượng bộ nhớ không cần thiết (tệp kết xuất là 800MB, trong đó 650MB đã bị SessionFactoryObjectFactory chiếm đóng), vì vậy tôi đã bật ghi nhật ký tải và xếp hạng đối tượng, và thử hỏi hệ thống cho dữ liệu về một tổ chức (thông qua cuộc gọi webservice từ một hệ thống khác). Những gì tôi nhận thấy ở đây là có rất nhiều tin nhắn để tải các đối tượng, nhưng rất ít thông tin về các đối tượng không tải (các đối tượng duy nhất ở đó đã được dỡ bỏ các đối tượng thư viện). Điều này khiến tôi tin rằng một khi một đối tượng (nói một tổ chức) đã được nạp vào bộ nhớ, nó không bao giờ được tải xuống, có nghĩa là theo thời gian, hệ thống sẽ hết bộ nhớ. (Đây có phải là giả định hợp lý dựa trên những gì được tìm thấy trong nhật ký không?)

Sau đó, tôi đã cố gắng tìm ra nguyên nhân, nhưng điều này khó hơn rất nhiều. Khi các đối tượng được tải bởi Hibernate sẽ sống miễn là phiên làm việc của chúng, tôi đã thử thay đổi cách các session được xử lý, bằng cách thay thế các cuộc gọi đến HibernateDaoSupport # getSession() của Spring, thành HibernateDaoSupport # getSessionFactory(). GetCurrentSession(). Điều này không ảnh hưởng đến vấn đề này. Tôi cũng đã thử thêm các cuộc gọi vào ... getCurrentSession(). Flush() và .clear() trong khối cuối cùng của một số phương thức Dao được đề cập, cũng không có hiệu ứng cố ý. (Tất cả các phương thức Dao đều được chú thích bằng @Transactional, điều này có nghĩa là một phiên chỉ nên tồn tại trong phương thức @ Transactional-method và các cuộc gọi liên tiếp đến phương thức sẽ nhận các phiên khác nhau khi gọi getCurrentSession() (?))

Vì vậy, bây giờ tôi đang khá nhiều khó khăn khi nói đến đến với các khu vực khác để kiểm tra. Có ai có một ý tưởng hoặc một số con trỏ về nơi để tìm và những gì để tìm?

Heap-dump cho thấy có rất nhiều trường hợp org.hibernate.impl.SessionFactoryImpl, đây có phải là dự kiến ​​không? (Tôi đã nghĩ rằng chỉ nên có một ví dụ của SessionFactory, hoặc một vài đỉnh.)

Cảm ơn trước vì bất kỳ câu trả lời nào.

Kính trọng,

Tobb

Edit:

Tôi nghĩ rằng tôi đã thực sự mananged để giải quyết vấn đề:

Hóa ra cách mà phụ thuộc vào các đối tượng khác được xử lý trong các lớp webservice là vấn đề. Điều này đã được giải quyết bằng cách gọi ClassPathXmlApplicationContext mới (...) trong hàm tạo của các lớp webservice. Điều này dẫn đến rất nhiều đối tượng được nạp cho mỗi yêu cầu (hoặc ít nhất mỗi phiên), mà không được tải lại (chủ yếu là SessionFactoryImpl của Hibernate). Tôi đã thay đổi các lớp WebService để chúng thay thế sự phụ thuộc của chúng, và hình thành những gì tôi đã thấy bằng cách sử dụng profilers cho đến nay, vấn đề với nhiều đối tượng SessionFactoryImpl đã được giải quyết.

Tôi nghĩ rằng vấn đề có thể đã trở nên tồi tệ hơn khi nâng cấp từ GlassFish 2.x lên GlassFish 3.x, có thể có một số khác biệt về cách các lớp webservice được khởi tạo.

Trả lời

5

tôi cũng có thể thêm các giải pháp cho vấn đề này trong một câu trả lời, thay vì chỉ trong câu hỏi bản thân:

Thủ phạm ở đây là như thế nào tải của lò xo đậu đã được thực hiện trong các đối tượng khác nhau, đáng chú ý nhất trong các lớp dịch vụ web. Điều này được thực hiện bằng cách gọi

new ClassPathXmlApplicationContext(...)

Trong từng lớp dịch vụ web riêng lẻ. Làm điều này có tác dụng phụ khó chịu của các đối tượng được tải tránh để được thu gom rác thải (tôi đoán vì chúng được tham chiếu bởi một số nội bộ của Spring). Có vẻ như một sự thay đổi trong phiên bản thủy tinh đã làm một điều gì đó cho sự khởi tạo của các đối tượng webservice, dẫn đến các cuộc gọi đến nhiều cuộc gọi mới hơn, và do đó nhiều đối tượng rác chiếm nhiều bộ nhớ hơn, cho đến khi nó tràn đầy và treo.

Giải pháp cho vấn đề là để di chuyển các cuộc gọi đến

new ClassPathXmlApplicationContext(...)

ra vào một lớp học khác nhau, sử dụng nhà máy-mô hình tĩnh, một cái gì đó như thế này:

public class ContextHolder { 
    private static ClassPathXmlApplicationContext context; 

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

Và kêu gọi này trong các lớp webservice, thay vì lớp mới ClassPathXmlApplicationContext.

Cập nhật:

ClassPathXmlApplicationContextCloseable/Autocloseable, vì vậy try-with-resource là một khả năng:

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

Ngoài ra nhà máy tĩnh, tôi nghĩ rằng 'ClassPathXmlApplicationContext' có một' close'-phương pháp có thể được được gọi để tránh vấn đề này. – Tobb