Theo dõi sự cố rò rỉ bộ nhớ / thu gom rác trong Java


79

Đây là vấn đề mà tôi đã cố gắng theo dõi trong vài tháng nay. Tôi có một ứng dụng java đang chạy xử lý nguồn cấp dữ liệu xml và lưu trữ kết quả trong cơ sở dữ liệu. Đã có những vấn đề về tài nguyên không liên tục mà rất khó theo dõi.

Thông tin cơ bản: Trên hộp sản xuất (nơi vấn đề đáng chú ý nhất), tôi không có quyền truy cập đặc biệt tốt vào hộp và không thể chạy Jprofiler. Hộp đó là một máy 64bit lõi tứ, 8gb chạy centos 5.2, tomcat6 và java 1.6.0.11. Nó bắt đầu với những java-opt này

JAVA_OPTS="-server -Xmx5g -Xms4g -Xss256k -XX:MaxPermSize=256m -XX:+PrintGCDetails -
XX:+PrintGCTimeStamps -XX:+UseConcMarkSweepGC -XX:+PrintTenuringDistribution -XX:+UseParNewGC"

Công nghệ như sau:

  • Centos 64-bit 5.2
  • Java 6u11
  • Tomcat 6
  • Spring / WebMVC 2.5
  • Ngủ đông 3
  • Thạch anh 1.6.1
  • DBCP 1.2.1
  • Mysql 5.0.45
  • Ehcache 1.5.0
  • (và tất nhiên là một loạt các phụ thuộc khác, đặc biệt là các thư viện jakarta-commons)

Gần nhất tôi có thể tái tạo vấn đề là máy 32 bit có yêu cầu bộ nhớ thấp hơn. Điều đó tôi có quyền kiểm soát. Tôi đã kiểm tra nó đến chết với JProfiler và khắc phục nhiều vấn đề về hiệu suất (sự cố đồng bộ hóa, biên dịch trước / bộ nhớ đệm xpath truy vấn, giảm luồng và xóa tìm nạp trước ngủ đông không cần thiết và "làm nóng bộ nhớ cache" quá mức trong quá trình xử lý).

Trong mỗi trường hợp, trình mô tả cho thấy chúng chiếm một lượng lớn tài nguyên vì lý do này hay lý do khác, và chúng không còn là ổ chứa tài nguyên chính sau khi thay đổi diễn ra.

Vấn đề: JVM dường như hoàn toàn bỏ qua cài đặt sử dụng bộ nhớ, lấp đầy bộ nhớ và không phản hồi. Đây là vấn đề đối với khách hàng đang gặp phải, những người mong đợi một cuộc thăm dò thông thường (5 phút cơ bản và 1 phút thử lại), cũng như cho các nhóm vận hành của chúng tôi, những người liên tục được thông báo rằng một hộp không phản hồi và phải khởi động lại nó. Không có gì quan trọng khác chạy trên hộp này.

Vấn đề dường như là thu gom rác. Chúng tôi đang sử dụng bộ thu ConcurrentMarkSweep (như đã lưu ý ở trên) vì bộ thu STW ban đầu đã gây ra thời gian chờ của JDBC và ngày càng trở nên chậm hơn. Các bản ghi cho thấy rằng khi mức sử dụng bộ nhớ tăng lên, điều đó bắt đầu gây ra lỗi cms và quay trở lại bộ sưu tập dừng thế giới ban đầu, sau đó dường như không được thu thập đúng cách.

Tuy nhiên, khi chạy với jprofiler, nút "Run GC" dường như dọn dẹp bộ nhớ tốt hơn là hiển thị dấu vết ngày càng tăng, nhưng vì tôi không thể kết nối jprofiler trực tiếp với hộp sản xuất và việc giải quyết các điểm nóng đã được chứng minh dường như không hoạt động. còn lại với thói quen điều chỉnh Rác thải mù mịt.

Những gì tôi đã thử:

  • Lập hồ sơ và sửa các điểm phát sóng.
  • Sử dụng bộ thu gom rác STW, Parallel và CMS.
  • Chạy với kích thước đống tối thiểu / tối đa ở mức tăng 1 / 2,2 / 4,4 / 5,6 / 6.
  • Chạy với không gian permgen với gia số 256M lên đến 1Gb.
  • Nhiều sự kết hợp của những điều trên.
  • Tôi cũng đã tham khảo JVM [tham chiếu điều chỉnh] (http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html), nhưng thực sự không thể tìm thấy bất kỳ điều gì giải thích hành vi này hoặc bất kỳ ví dụ nào về _which_ điều chỉnh tham số để sử dụng trong tình huống như thế này.
  • Tôi cũng (không thành công) đã thử jprofiler ở chế độ ngoại tuyến, kết nối với jconsole, visualvm, nhưng dường như tôi không thể tìm thấy bất kỳ thứ gì xen vào dữ liệu nhật ký gc của tôi.

Thật không may, vấn đề cũng xuất hiện thường xuyên, nó dường như không thể đoán trước được, nó có thể chạy trong nhiều ngày hoặc thậm chí một tuần mà không gặp bất kỳ vấn đề nào hoặc có thể thất bại 40 lần trong một ngày và điều duy nhất tôi có thể nắm bắt một cách nhất quán là việc thu gom rác đang hoạt động.

Bất cứ ai có thể đưa ra bất kỳ lời khuyên nào về:
a) Tại sao một JVM sử dụng 8 hợp đồng biểu diễn vật lý và 2 gb không gian hoán đổi khi nó được định cấu hình để đạt tối đa ở mức nhỏ hơn 6.
b) Tham chiếu đến điều chỉnh GC thực sự giải thích hoặc đưa ra các ví dụ hợp lý về thời điểm và loại cài đặt nào để sử dụng các bộ sưu tập nâng cao.
c) Tham chiếu đến các lỗi rò rỉ bộ nhớ java phổ biến nhất (tôi hiểu các tham chiếu chưa được xác nhận quyền sở hữu, nhưng ý tôi là ở cấp thư viện / khung, hoặc một thứ gì đó khác trong mạng trong cấu trúc dữ liệu, như bản đồ băm).

Cảm ơn vì bất kỳ và tất cả thông tin chi tiết bạn có thể cung cấp.

CHỈNH SỬA
Emil H:
1) Có, cụm phát triển của tôi là một bản sao của dữ liệu sản xuất, cho máy chủ phương tiện. Sự khác biệt chính là 32 / 64bit và dung lượng RAM có sẵn, mà tôi không thể sao chép rất dễ dàng, nhưng mã và truy vấn và cài đặt giống hệt nhau.

2) Có một số mã kế thừa dựa trên JaxB, nhưng trong việc sắp xếp lại các công việc để cố gắng tránh xung đột lịch trình, tôi đã loại bỏ việc thực thi đó vì nó chạy mỗi ngày một lần. Trình phân tích cú pháp chính sử dụng các truy vấn XPath gọi xuống gói java.xml.xpath. Đây là nguồn gốc của một vài điểm nóng, đối với một truy vấn không được biên dịch trước và hai tham chiếu đến chúng ở dạng chuỗi mã cứng. Tôi đã tạo một bộ đệm threadsafe (hashmap) và tính toán các tham chiếu đến các truy vấn xpath thành các Chuỗi tĩnh cuối cùng, điều này đã giảm đáng kể mức tiêu thụ tài nguyên. Truy vấn vẫn là một phần lớn của quá trình xử lý, nhưng nó phải là vì đó là trách nhiệm chính của ứng dụng.

3) Một lưu ý bổ sung, người tiêu dùng chính khác là các thao tác hình ảnh từ JAI (xử lý lại hình ảnh từ nguồn cấp dữ liệu). Tôi không quen với các thư viện đồ họa của java, nhưng từ những gì tôi nhận thấy chúng không đặc biệt bị rò rỉ.

(cảm ơn vì những câu trả lời cho đến nay, mọi người!)

CẬP NHẬT:
Tôi đã có thể kết nối với phiên bản sản xuất bằng VisualVM, nhưng nó đã tắt tùy chọn GC visualization / run-GC (mặc dù tôi có thể xem nó cục bộ). Điều thú vị: Phân bổ heap của VM đang tuân theo JAVA_OPTS và heap được phân bổ thực tế đang ngồi thoải mái ở mức 1-1,5 hợp đồng biểu diễn và dường như không bị rò rỉ, nhưng theo dõi mức hộp vẫn cho thấy một mẫu rò rỉ, nhưng nó không được phản ánh trong giám sát VM. Không có gì khác đang chạy trên hộp này, vì vậy tôi bối rối.


Bạn có sử dụng dữ liệu thế giới thực và cơ sở dữ liệu thế giới thực để thử nghiệm không? Tốt hơn là một bản sao của dữ liệu sản xuất?
Emil H,

4
+1 - đây là một trong những câu hỏi hay nhất mà tôi từng đọc. Tôi ước tôi có nhiều hơn để cung cấp về mặt trợ giúp. Tôi sẽ quay lại phần này để xem có ai có điều gì đó thông minh để nói không.
duffymo

Ngoài ra, bạn đang sử dụng trình phân tích cú pháp XML nào?
Emil H

Bạn đã xem số lượng ByteBuffers được phân bổ và ai là người phân bổ chúng?
Sean McCauliff, 09/07/09

Kiểm tra câu trả lời này: stackoverflow.com/a/35610063 , nó có thông tin chi tiết về rò rỉ bộ nhớ gốc Java.
Lari Hotari

Câu trả lời:


92

Chà, cuối cùng tôi đã tìm ra vấn đề gây ra sự cố này và tôi sẽ đăng một câu trả lời chi tiết trong trường hợp ai đó gặp phải những vấn đề này.

Tôi đã thử jmap trong khi quá trình đang hoạt động, nhưng điều này thường khiến jvm bị treo thêm và tôi sẽ phải chạy nó với --force. Điều này dẫn đến kết xuất đống dường như thiếu rất nhiều dữ liệu, hoặc ít nhất là thiếu các tham chiếu giữa chúng. Để phân tích, tôi đã thử jhat, trình bày rất nhiều dữ liệu nhưng không nhiều về cách diễn giải nó. Thứ hai, tôi đã thử công cụ phân tích bộ nhớ dựa trên nhật thực ( http://www.eclipse.org/mat/ ), cho thấy rằng heap chủ yếu là các lớp liên quan đến tomcat.

Vấn đề là jmap không báo cáo trạng thái thực của ứng dụng và chỉ bắt các lớp khi tắt máy, chủ yếu là các lớp tomcat.

Tôi đã thử thêm một vài lần nữa và nhận thấy rằng có một số đối tượng mô hình rất cao (thực tế nhiều hơn 2-3 lần so với số lượng được đánh dấu là công khai trong cơ sở dữ liệu).

Sử dụng điều này, tôi đã phân tích nhật ký truy vấn chậm và một số vấn đề hiệu suất không liên quan. Tôi đã thử tải cực kỳ lười biếng ( http://docs.jboss.org/hibernate/core/3.3/reference/en/html/performance.html ), cũng như thay thế một vài thao tác ngủ đông bằng các truy vấn jdbc trực tiếp (chủ yếu là ở đâu đang xử lý việc tải và vận hành trên các tập hợp lớn - các thay thế jdbc chỉ hoạt động trực tiếp trên các bảng nối) và thay thế một số truy vấn không hiệu quả khác mà mysql đang ghi.

Các bước này đã cải thiện một phần hiệu suất của giao diện người dùng, nhưng vẫn không giải quyết được vấn đề rò rỉ, ứng dụng vẫn không ổn định và hoạt động không thể đoán trước.

Cuối cùng, tôi tìm thấy tùy chọn: -XX: + HeapDumpOnOutOfMemoryError. Điều này cuối cùng đã tạo ra một tệp hprof rất lớn (~ 6,5GB) hiển thị chính xác trạng thái của ứng dụng. Trớ trêu thay, tệp tin quá lớn đến nỗi không thể anayl hóa nó, ngay cả trên một hộp có 16 GB ram. May mắn thay, MAT đã có thể tạo ra một số đồ thị đẹp và hiển thị một số dữ liệu tốt hơn.

Lần này, thứ bị mắc kẹt là một chuỗi thạch anh duy nhất đang chiếm 4,5 GB trong tổng số 6 GB của heap và phần lớn trong số đó là StatefulPersistenceContext ngủ đông ( https://www.hibernate.org/hib_docs/v3/api/org/hibernate /engine/StatefulPersistenceContext.html ). Lớp này được sử dụng bởi chế độ ngủ đông bên trong làm bộ nhớ đệm chính của nó (tôi đã vô hiệu hóa bộ đệm truy vấn và cấp hai được hỗ trợ bởi EHCache).

Lớp này được sử dụng để kích hoạt hầu hết các tính năng của chế độ ngủ đông, vì vậy nó không thể bị vô hiệu hóa trực tiếp (bạn có thể làm việc trực tiếp với nó, nhưng mùa xuân không hỗ trợ phiên không trạng thái) và tôi sẽ rất ngạc nhiên nếu điều này có một rò rỉ bộ nhớ lớn trong một sản phẩm trưởng thành. Vậy tại sao nó lại bị rò rỉ bây giờ?

Chà, đó là sự kết hợp của nhiều thứ: Nhóm chuỗi thạch anh khởi tạo với một số thứ nhất định là threadLocal, mùa xuân đang đưa một nhà máy phiên vào, tạo ra một phiên khi bắt đầu vòng đời của chuỗi thạch anh, sau đó được sử dụng lại để chạy các công việc thạch anh khác nhau sử dụng phiên ngủ đông. Hibernate sau đó đã được lưu vào bộ nhớ đệm trong phiên, đó là hành vi mong đợi của nó.

Vấn đề sau đó là nhóm luồng không bao giờ giải phóng phiên, do đó, hibernate vẫn thường trú và duy trì bộ đệm cho vòng đời của phiên. Vì điều này đang sử dụng hỗ trợ mẫu ngủ đông của lò xo, không có việc sử dụng rõ ràng các phiên (chúng tôi đang sử dụng dao -> trình quản lý -> trình điều khiển -> hệ thống phân cấp công việc thạch anh, dao được tiêm các cấu hình ngủ đông thông qua mùa xuân, vì vậy các hoạt động thực hiện trực tiếp trên các mẫu).

Vì vậy, phiên không bao giờ bị đóng, chế độ ngủ đông đang duy trì các tham chiếu đến các đối tượng bộ nhớ cache, vì vậy chúng không bao giờ được thu thập rác, vì vậy mỗi khi một công việc mới chạy, nó sẽ tiếp tục lấp đầy bộ nhớ cache cục bộ vào chuỗi, vì vậy thậm chí không có bất kỳ sự chia sẻ nào giữa các công việc khác nhau. Ngoài ra, vì đây là một công việc đòi hỏi nhiều ghi (đọc rất ít), bộ nhớ đệm hầu như bị lãng phí, vì vậy các đối tượng tiếp tục được tạo ra.

Giải pháp: tạo một phương thức dao gọi rõ ràng session.flush () và session.clear () và gọi phương thức đó khi bắt đầu mỗi công việc.

Ứng dụng đã chạy được vài ngày nay mà không gặp vấn đề gì về giám sát, lỗi bộ nhớ hoặc khởi động lại.

Cảm ơn sự giúp đỡ của mọi người về vấn đề này, đó là một lỗi khá phức tạp để theo dõi, vì mọi thứ đang hoạt động chính xác như những gì nó được yêu cầu, nhưng cuối cùng, phương pháp 3 dòng đã quản lý để khắc phục tất cả các vấn đề.


13
Bản tóm tắt tuyệt vời về quy trình gỡ lỗi của bạn và cảm ơn bạn đã theo dõi và đăng giải pháp.
Boris Terzic

1
Cảm ơn vì lời giải thích tốt đẹp. Tôi đã gặp sự cố tương tự trong kịch bản đọc hàng loạt (SELECT), dẫn đến StatefulPersistenceContext nhận được quá lớn. Tôi không thể chạy em.clear () hoặc em.flush () như phương thức lặp chính của tôi @Transactional(propagation = Propagation.NOT_SUPPORTED). Nó đã được giải quyết bằng cách thay đổi cách truyền thành Propagation.REQUIREDvà gọi em.flush / em.clear ().
Mohsen

3
Một điều mà tôi không hiểu: nếu phiên không bao giờ được tuôn ra, điều đó có nghĩa là không có dữ liệu thực tế nào được lưu vào DB. Không phải dữ liệu này được truy xuất một số nơi khác trong ứng dụng của bạn, để bạn có thể thấy rằng nó bị thiếu?
yair

1
Liên kết được cung cấp cho StatefulPersistenceContext bị hỏng. Hiện tại có phải docs.jboss.org/hibernate/orm/4.3/javadocs/org/hibernate/engine/… không?
Victor Stafusa

1
Liam, Cảm ơn rất nhiều. Tôi thực sự có cùng một vấn đề và MAT đang trỏ đến trạng thái ngủ đông statefulPers phù hợpContext. Tôi đoán bằng cách đọc bài viết của bạn, tôi đã có đủ gợi ý. Cảm ơn vì một thông tin tuyệt vời.
Reddymails

4

Bạn có thể chạy hộp sản xuất với JMX được bật không?

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=<port>
...

Giám sát và quản lý bằng JMX

Và sau đó đính kèm với JConsole, VisualVM ?

Có thể thực hiện một kết xuất đống với jmap không?

Nếu có, bạn có thể phân tích kết xuất heap để tìm rò rỉ bằng JProfiler (bạn đã có), jhat , VisualVM, Eclipse MAT . Ngoài ra, hãy so sánh các bãi chứa đống có thể giúp tìm ra các điểm rò rỉ / mẫu.

Và như bạn đã đề cập jakarta-commons. Có một sự cố khi sử dụng jakarta-commons-logging liên quan đến việc giữ bộ tải lớp. Để đọc tốt về séc đó

Một ngày trong cuộc đời của một thợ săn rò rỉ ký ức ( release(Classloader))


1) Tôi đã thực sự thử visualvm và một số công cụ khác ngày hôm nay, nhưng cần phải mở các cổng đúng cách. 2) Tôi đã thấy vấn đề ghi nhật ký c trong công việc cuối cùng của tôi, thực sự và vấn đề này đã nhắc nhở tôi về nó. Một dịch vụ trong toàn công ty thường xuyên gặp sự cố và nó đã được theo dõi về một sự cố rò rỉ đã biết trên mạng xã hội, tôi tin rằng nó giống với những gì bạn đã liên kết. Tôi đã cố gắng giữ phần lớn nhật ký dưới dạng log4j, nhưng tôi không có nhiều lựa chọn đối với các dự án phụ thuộc yêu cầu gói commons. Chúng tôi cũng có một vài lớp sử dụng simpleFacade, bây giờ tôi đang xem liệu tôi có thể làm cho mọi thứ ổn định hơn một chút hay không.
liam

4

Có vẻ như bộ nhớ khác ngoài heap bị rò rỉ, bạn đề cập rằng heap vẫn ổn định. Một ứng cử viên cổ điển là permgen (tạo vĩnh viễn) bao gồm 2 thứ: các đối tượng lớp được tải và các chuỗi được xen vào. Vì bạn báo cáo đã kết nối với VisualVM, bạn sẽ có thể xem số lượng các lớp đã tải, nếu có sự gia tăng liên tục của các lớp được tải (quan trọng, visualvm cũng hiển thị tổng số lượng các lớp đã từng được tải, không sao nếu điều này tăng lên nhưng số lượng các lớp được tải sẽ ổn định sau một thời gian nhất định).

Nếu nó thực sự là một sự cố rò rỉ permgen thì việc gỡ lỗi trở nên phức tạp hơn vì công cụ để phân tích permgen khá thiếu so với heap. Đặt cược tốt nhất của bạn là bắt đầu một tập lệnh nhỏ trên máy chủ lặp đi lặp lại (mỗi giờ?) Gọi:

jmap -permstat <pid> > somefile<timestamp>.txt

jmap với tham số đó sẽ tạo ra một cái nhìn tổng quan về các lớp được tải cùng với ước tính kích thước của chúng theo byte, báo cáo này có thể giúp bạn xác định nếu một số lớp nhất định không được tải xuống. (lưu ý: với ý tôi là id quy trình và phải là một số dấu thời gian được tạo để phân biệt các tệp)

Khi bạn đã xác định được một số lớp nhất định đang được tải và không được tải xuống, bạn có thể tìm hiểu về nơi mà chúng có thể được tạo ra, nếu không, bạn có thể sử dụng jhat để phân tích kết xuất được tạo bằng jmap -dump. Tôi sẽ giữ thông tin đó để cập nhật trong tương lai nếu bạn cần thông tin.


Gợi ý tốt. Tôi sẽ thử điều đó vào chiều nay.
liam

jmap không giúp được gì, nhưng đã đóng. xem câu trả lời đầy đủ để giải thích.
liam

2

Tôi sẽ tìm kiếm ByteBuffer được phân bổ trực tiếp.

Từ javadoc.

Một bộ đệm byte trực tiếp có thể được tạo ra bằng cách gọi phương thức gốc của lớp này. Các bộ đệm được trả về bởi phương pháp này thường có chi phí phân bổ và phân bổ giao dịch cao hơn một chút so với các bộ đệm không trực tiếp. Nội dung của bộ đệm trực tiếp có thể nằm bên ngoài đống rác được thu thập thông thường và do đó tác động của chúng lên vùng bộ nhớ của một ứng dụng có thể không rõ ràng. Do đó, khuyến nghị rằng các bộ đệm trực tiếp được cấp phát chủ yếu cho các bộ đệm lớn, có tuổi thọ cao tuân theo các hoạt động I / O gốc của hệ thống cơ bản. Nói chung, tốt nhất là chỉ phân bổ bộ đệm trực tiếp khi chúng mang lại mức tăng có thể đo được trong hiệu suất chương trình.

Có lẽ mã Tomcat sử dụng thao tác này với I / O; cấu hình Tomcat để sử dụng một trình kết nối khác.

Không thành công mà bạn có thể có một chuỗi thực thi định kỳ System.gc (). "-XX: + ExplicitGCInvokesConcurrent" có thể là một lựa chọn thú vị để thử.


1) Khi bạn nói trình kết nối, bạn đang đề cập đến Trình kết nối DB hay một lớp liên kết IO khác? Cá nhân tôi không muốn cố gắng giới thiệu một nhóm kết nối mới, ngay cả khi c3p0 là một kết hợp chặt chẽ, nhưng tôi không nên đưa nó vào. 2) Tôi chưa bắt gặp cờ GC rõ ràng, nhưng tôi chắc chắn sẽ xem xét nó. Tuy nhiên, nó cảm thấy hơi khó hiểu và với một cơ sở mã cũ có kích thước như vậy, tôi đang cố gắng tránh xa cách tiếp cận đó. (ví dụ: một vài tháng trước, tôi đã phải theo dõi một số điểm chỉ sinh ra các chủ đề như là tác dụng phụ. Các chủ đề đang được hợp nhất bây giờ).
liam

1) Đã một thời gian kể từ khi tôi cấu hình tomcat. Nó có một khái niệm được gọi là Connector để bạn có thể cấu hình nó để lắng nghe các yêu cầu từ Apache httpd hoặc lắng nghe HTTP trực tiếp. Tại một số thời điểm đã có trình kết nối NIO http và trình kết nối HTTP cơ bản. Bạn có thể thấy những tùy chọn cấu hình nào có sẵn cho trình kết nối NIO HTTP hoặc xem liệu trình kết nối cơ bản duy nhất có sẵn hay không. 2) Bạn chỉ cần trên chuỗi định kỳ gọi System.gc () hoặc bạn có thể sử dụng lại một chuỗi thời gian. Vâng, nó hoàn toàn là hackish.
Sean McCauliff

Xem stackoverflow.com/questions/26041117/… để gỡ lỗi rò rỉ bộ nhớ gốc.
Lari Hotari

1

Bất kỳ JAXB? Tôi thấy rằng JAXB là một trình nhồi không gian cố định.

Ngoài ra, tôi thấy rằng visualgc , hiện được vận chuyển với JDK 6, là một cách tuyệt vời để xem những gì đang xảy ra trong bộ nhớ. Nó cho thấy không gian eden, thế hệ và hoán vị và hành vi nhất thời của GC một cách tuyệt vời. Tất cả những gì bạn cần là PID của quy trình. Có thể điều đó sẽ hữu ích khi bạn làm việc trên JProfile.

Và những gì về các khía cạnh theo dõi / ghi nhật ký Spring? Có thể bạn có thể viết một khía cạnh đơn giản, áp dụng nó một cách công khai và làm theo cách đó của một người nghèo.


1) Tôi đang làm việc với SA để cố gắng mở một cổng từ xa và tôi sẽ thử các công cụ dựa trên java / jmx gốc (Tôi đã thử một vài công cụ, bao gồm cả jprofiler - công cụ tuyệt vời! - nhưng quá khó mức hệ thống thích hợp nằm ở đó). 2) Tôi khá cảnh giác với bất cứ thứ gì Định hướng theo khía cạnh, kể cả từ mùa xuân. Theo kinh nghiệm của tôi, thậm chí có sự phụ thuộc vào đó khiến mọi thứ trở nên khó hiểu hơn và khó cấu hình hơn. Tôi sẽ ghi nhớ nó nếu không có gì khác hoạt động.
liam

1

"Thật không may, vấn đề cũng xuất hiện thường xuyên, nó dường như không thể đoán trước được, nó có thể chạy trong nhiều ngày hoặc thậm chí một tuần mà không gặp bất kỳ sự cố nào hoặc có thể thất bại 40 lần trong một ngày, và điều duy nhất tôi có thể nắm bắt một cách nhất quán là việc thu gom rác đang hoạt động. "

Có vẻ như, điều này bị ràng buộc với một ca sử dụng được thực hiện tối đa 40 lần một ngày và sau đó không còn nữa trong nhiều ngày. Tôi hy vọng, bạn không chỉ theo dõi các triệu chứng. Đây phải là thứ mà bạn có thể thu hẹp bằng cách theo dõi hành động của các tác nhân của ứng dụng (người dùng, công việc, dịch vụ).

Nếu điều này xảy ra bởi quá trình nhập XML, bạn nên so sánh dữ liệu XML của 40 ngày sự cố với dữ liệu được nhập vào một ngày không có sự cố. Có thể đó là một loại vấn đề logic nào đó mà bạn không tìm thấy bên trong mã của mình.


1

Tôi đã gặp vấn đề tương tự, với một vài điểm khác biệt ..

Công nghệ của tôi là như sau:

grails 2.2.4

tomcat7

quartz-plugin 1.0

Tôi sử dụng hai nguồn dữ liệu trên ứng dụng của mình. Đó là một yếu tố đặc biệt quyết định đến nguyên nhân lỗi ..

Một điều khác cần xem xét là plugin thạch anh, đưa phiên ngủ đông vào các chuỗi thạch anh, giống như @liam nói, và các chuỗi thạch anh vẫn còn sống, cho đến khi tôi hoàn thành ứng dụng.

Vấn đề của tôi là một lỗi trên grails ORM kết hợp với cách plugin xử lý phiên và hai nguồn dữ liệu của tôi.

Plugin Quartz có một trình lắng nghe để bắt đầu và phá hủy các phiên ngủ đông

public class SessionBinderJobListener extends JobListenerSupport {

    public static final String NAME = "sessionBinderListener";

    private PersistenceContextInterceptor persistenceInterceptor;

    public String getName() {
        return NAME;
    }

    public PersistenceContextInterceptor getPersistenceInterceptor() {
        return persistenceInterceptor;
    }

    public void setPersistenceInterceptor(PersistenceContextInterceptor persistenceInterceptor) {
        this.persistenceInterceptor = persistenceInterceptor;
    }

    public void jobToBeExecuted(JobExecutionContext context) {
        if (persistenceInterceptor != null) {
            persistenceInterceptor.init();
        }
    }

    public void jobWasExecuted(JobExecutionContext context, JobExecutionException exception) {
        if (persistenceInterceptor != null) {
            persistenceInterceptor.flush();
            persistenceInterceptor.destroy();
        }
    }
}

Trong trường hợp của tôi, các persistenceInterceptortrường hợp AggregatePersistenceContextInterceptorvà nó có một Danh sách HibernatePersistenceContextInterceptor. Một cho mỗi nguồn dữ liệu.

Mọi hoạt động thực hiện với AggregatePersistenceContextInterceptornó đều được chuyển đến HibernatePersistence, mà không có bất kỳ sửa đổi hoặc xử lý nào.

Khi chúng tôi gọi init()về HibernatePersistenceContextInterceptorông tăng biến tĩnh bên dưới

private static ThreadLocal<Integer> nestingCount = new ThreadLocal<Integer>();

Tôi không biết mục đích của số lượng tĩnh đó. Tôi chỉ biết anh ấy nó tăng lên hai lần, một lần cho mỗi nguồn dữ liệu, do việc AggregatePersistencetriển khai.

Cho đến đây tôi chỉ giải thích cenario.

Vấn đề đến bây giờ ...

Khi công việc thạch anh của tôi hoàn thành, plugin gọi trình lắng nghe để xóa và hủy các phiên ngủ đông, giống như bạn có thể thấy trong mã nguồn của SessionBinderJobListener.

Quá trình xả xảy ra hoàn hảo, nhưng việc hủy không xảy ra, bởi vì HibernatePersistence, hãy thực hiện một xác thực trước khi đóng phiên ngủ đông ... Nó kiểm tra nestingCountxem giá trị có lớn hơn 1. Nếu câu trả lời là có, anh ta không đóng phiên.

Đơn giản hóa những gì đã làm bởi Hibernate:

if(--nestingCount.getValue() > 0)
    do nothing;
else
    close the session;

Đó là cơ sở của việc rò rỉ bộ nhớ của tôi .. Các chuỗi thạch anh vẫn tồn tại với tất cả các đối tượng được sử dụng trong phiên, bởi vì grails ORM không đóng phiên, do một lỗi gây ra bởi vì tôi có hai nguồn dữ liệu.

Để giải quyết vấn đề đó, tôi tùy chỉnh trình lắng nghe, gọi xóa trước khi hủy và gọi hủy hai lần, (một lần cho mỗi nguồn dữ liệu). Đảm bảo phiên của tôi rõ ràng và bị phá hủy, và nếu việc phá hủy không thành công, ít nhất anh ta cũng rõ ràng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.