Thứ 2 - Chủ Nhật, 8h - 21h.
Hãy gọi: 0906 12 56 56

Các cách tiếp cận và các kỹ thuật gỡ lỗi

Việc đầu tiên cần làm khi phải đối mặt với một java.lang.OutOfMemoryError hoặc một thông báo lỗi về thiếu bộ nhớ là xác định loại bộ nhớ nào đã cạn kiệt. Cách dễ nhất để làm điều này là trước tiên kiểm tra xem vùng heap java đã đầy chưa. Nếu vùng heap Java không gây ra tình trạng OutOfMemory (thiếu bộ nhớ), thì bạn nên phân tích cách sử dụng vùng heap riêng.

Kiểm tra vùng heap Java


Phương thức để kiểm tra việc sử dụng vùng heap cũng khác nhau giữa các việc triển khai thực hiện Java. Trong việc triển khai thực hiện các bản Java 5 và 6 của IBM, tệp javacore được tạo ra khi có lỗi OutOfMemoryError sẽ báo cho bạn biết về sử dụng vùng heap. Tệp javacore thường được tạo ra trong thư mục làm việc của tiến trình Java và có tên theo dạng javacore.date.time.pid.txt. Nếu bạn mở tệp này trong một trình soạn thảo văn bản, bạn có thể tìm thấy một phần trông như sau:

 

 0SECTION MEMINFO subcomponent dump routine NULL
================================= 1STHEAPFREE Bytes of Heap Space Free: 416760
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
1STHEAPALLOC Bytes of Heap Space Allocated: 1344800

 

Phần này cho thấy vùng heap Java còn chưa sử dụng là bao nhiêu khi javacore được tạo ra. Lưu ý rằng các giá trị số theo định dạng hệ đếm 16 (hexadecimal). Nếu OutOfMemoryError đã được đưa ra vì không đáp ứng được một việc cấp phát, thì sau đó phần dấu vết của GC sẽ hiển thị như sau:

 


1STGCHTYPE GC History 3STHSTTYPE 09:59:01:632262775 GMT j9mm.80 -
J9AllocateObject() returning NULL! 32 bytes requested for object of class 00147F80
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|

J9AllocateObject() returning NULL! (J9AllocateObject () trả về NULL)! có nghĩa là thường trình cấp phát vùng heap Java không thành công và một lỗi OutOfMemoryError sẽ được đưa ra.

 


Cũng có khả năng một lỗi OutOfMemoryError được đưa ra vì các bộ dọn dữ liệu rác hoạt động quá thường xuyên (một dấu hiệu cho thấy vùng heap đã đầy và ứng dụng Java sẽ tiến triển rất ít hoặc không tiến triển). Trong trường hợp này, bạn sẽ thấy giá trị vùng heap chưa sử dụng (Heap Space Free) rất nhỏ và dấu vết GC sẽ hiển thị một trong các thông báo này:

 


1STGCHTYPE GC History 3STHSTTYPE 09:59:01:632262775 GMT j9mm.83 -
Forcing J9AllocateObject() to fail due to excessive GC

1STGCHTYPE GC History 3STHSTTYPE 09:59:01:632262775 GMT j9mm.84 -
Forcing J9AllocateIndexableObject() to fail due to excessive GC

 

Khi việc triển khai thực hiện bản Java của Sun sử dụng hết bộ nhớ của vùng heap Java, nó sử dụng thông báo trường hợp ngoại lệ để chỉ báo rằng vùng heap Java đã cạn kiệt.

 


Exception in thread "main" java.lang.OutOfMemoryError: Java heap
space

 

Các việc triển khai thực hiện Java của IBM và Sun, cả hai đều có một tùy chọn hiển thị mọi thông báo GC (verbose GC), tùy chọn này sinh ra dữ liệu theo vết, cho biết vùng heap đầy đến mức nào trong mỗi chu kỳ GC. Thông tin này có thể được vẽ thành biểu đồ bằng một công cụ ví dụ như là Bộ công cụ giám sát và chẩn đoán cho Java của IBM (IBM Monitoring and Diagnostic Tools for Java)- Trình giám sát bộ nhớ và thu dọn dữ liệu rác (Garbage Collection and Memory Visualizer-GCMV) để hiển thị xem vùng heap Java có đang tăng lên không

 

Đo việc sử dụng vùng heap riêng

 

Nếu bạn đã xác định rằng tình trạng thiếu bộ nhớ của bạn không phải do sự cạn kiệt vùng heap Java gây ra, thì giai đoạn tiếp theo là mô tả sơ lược việc sử dụng bộ nhớ riêng của bạn.

 


Công cụ PerfMon được cung cấp với Windows cho phép bạn giám sát và ghi lại nhiều số đo về hệ điều hành và tiến trình, bao gồm việc sử dụng bộ nhớ riêng. Nó cho phép các bộ đếm được theo dõi trong thời gian thực hoặc được lưu trữ trong một tệp ghi nhật ký (log) để xem lại khi ngoại tuyến (offline). Hãy sử dụng bộ đếm các Byte riêng (Private Bytes) để cho thấy cách sử dụng toàn bộ vùng địa chỉ. Nếu lượng sử dụng đạt đến gần giới hạn của vùng người dùng (từ 2 đến 3GB như thảo luận trước đây), bạn sẽ có nhiều khả năng gặp tình trạng thiếu bộ nhớ riêng.

 

Linux không có công cụ PerfMon tương đương, nhưng bạn có một vài lựa chọn. Các công cụ dòng lệnh như ps, top và pmap có thể hiển thị dấu vết bộ nhớ riêng của ứng dụng. Mặc dù nhận được một ảnh chụp nhanh về cách sử dụng bộ nhớ của một tiến trình rất có ích, bạn sẽ hiểu biết nhiều hơn về bộ nhớ riêng đang được sử dụng như thế nào bằng cách vẽ biểu đồ việc sử dụng bộ nhớ theo thời gian. Một cách để làm điều này là sử dụng GCMV.

 

Trình giám sát bộ nhớ và thu dọn dữ liệu rác (GCMV) ban đầu được viết để vẽ biểu đồ dựa trên các bản ghi nhật ký GC đầy đủ thông báo, cho phép người dùng xem các thay đổi trong cách sử dụng vùng heap Java và hiệu năng của GC khi điều chỉnh bộ thu gom dữ liệu rác. GCMV sau đó được mở rộng để cho phép nó vẽ biểu đồ cả các nguồn dữ liệu khác, bao gồm dữ liệu bộ nhớ riêng của Linux và AIX. GCMV được phân phối kèm như là một trình cắm thêm (plug-in) cho Trợ lý Hỗ trợ IBM (IBM Support Assistant-ISA).

 

Để vẽ biểu đồ hiện trạng bộ nhớ riêng của Linux bằng GCMV, trước tiên bạn phải thu gom dữ liệu bộ nhớ riêng bằng cách sử dụng một kịch bản lệnh. Trình phân tích (parser) bộ nhớ riêng Linux của GCMV đọc kết quả đầu ra từ lệnh ps của Linux có xen lẫn với các dấu thời gian. Một kịch bản lệnh được cung cấp trong tài liệu trợ giúp GCMV để thu gom dữ liệu theo đúng khuôn dạng. Để tìm kịch bản lệnh:

 

 

  1. Hãy tải về và cài đặt ISA Phiên bản 4 (hoặc cao hơn) và cài đặt trình cắm thêm của công cụ GCMV.
  2. Khởi động ISA.
  3. Khởi động Help >> Help Contents từ thanh trình đơn để mở trình đơn trợ giúp ISA.
  4. Tìm các chỉ thị cho bộ nhớ riêng của Linux ở ô bên trái, trong mục Tool:IBM Monitoring and Diagnostic Tools for Java - Garbage Collection and Memory Visualizer >> Using the Garbage Collection and Memory Visualizer >> Supported Data Types >> Native memory >> Linux native memory.

 

Hình 5 cho thấy vị trí của kịch bản lệnh trong tệp trợ giúp ISA. Nếu bạn không có mục GCMV Tool (Công cụ GCMV) trong tệp trợ giúp của bạn, nhiều khả năng là bạn chưa cài đặt trình cắm thêm GCMV.

 

Hình 5. Vị trí của kịch bản lệnh nắm bắt dữ liệu bộ nhớ riêng Linux trong hộp thoại trợ giúp ISA

 

 

Kịch bản lệnh được cung cấp trong trợ giúp GCMV sử dụng một lệnh ps chỉ làm việc với phiên bản gần đây của ps. Trên một số bản phân phối Linux cũ hơn, lệnh này trong tệp trợ giúp sẽ không đưa ra thông tin chính xác. Để kiểm tra hành vi của bản phân phối Linux của bạn, hãy thử chạy ps -o pid,vsz=VSZ,rss=RSS. Nếu phiên bản ps của bạn hỗ trợ cú pháp đối số dòng lệnh mới, kết quả đầu ra sẽ như sau:

 

PID VSZ RSS 5826 3772 1960 5675 2492 760

 

Nếu phiên bản ps của bạn không hỗ trợ cú pháp mới, kết quả đầu ra sẽ là:

 

PID VSZ,rss=RSS 5826 3772 5674 2488

 

Nếu bạn đang chạy một phiên bản ps, hãy sửa đổi kịch bản lệnh bộ nhớ riêng bằng cách thay thế dòng

 

ps -p $PID -o
pid,vsz=VSZ,rss=RSS

 

bằng

 

ps -p $PID -o
pid,vsz,rss

 

Sao chép kịch bản lệnh từ bảng trợ giúp vào một tệp (trong ví dụ này, tệp có tên là memscript.sh), tìm mã nhận dạng (id) tiến trình (PID) của tiến trình Java mà bạn muốn giám sát (trong ví dụ này là 1234) và chạy:

 

./memscript.sh 1234 > ps.out

 

Thao tác này sẽ viết bản ghi nhật ký của bộ nhớ riêng vào tệp ps.out. Để vẽ biểu đồ cách sử dụng bộ nhớ:

 

  1. Trong ISA, chọn Analyze Problem (Phân tích vấn đề) từ trình đơn thả xuống Launch Activity (Hành động khởi chạy).
  2. Chọn tab Tools (Các công cụ) ở gần đỉnh của bảng Analyze Problem.
  3. Chọn IBM Monitoring and Diagnostic Tools for Java - Garbage Collection and Memory Visualizer (Bộ công cụ giám sát và chẩn đoán cho Java của IBM-Trình giám sát bộ nhớ và dọn dữ liệu rác).
  4. Nhấn vào nút Launch gần đáy của bảng Tools.
  5. Nhấn vào nút Browse (Duyệt) và tìm đến tệp log. Nhấn OK để khởi chạy GCMV.

 

Một khi bạn có hiện trạng về việc sử dụng bộ nhớ riêng theo thời gian, bạn cần phải quyết định đây là lỗ rò bộ nhớ riêng hay chỉ cố gắng thực hiện quá nhiều việc trong bộ nhớ sẵn có. Dấu vết bộ nhớ riêng của một ứng dụng Java, ngay cả ứng dụng hoạt động tốt, cũng thay đổi từ lúc khởi động. Một số hệ thống thời gian chạy Java — đặc biệt là trình biên dịch JIT và các trình nạp lớp — khởi động lại theo thời gian, do đó có thể tiêu dùng bộ nhớ riêng. Biểu đồ phát triển bộ nhớ từ lúc khởi động chương trình sẽ dần dần nằm ngang, nhưng nếu kịch bản của bạn có một dấu vết bộ nhớ riêng ban đầu đạt gần tới giới hạn của vùng địa chỉ, thì giai đoạn khởi động này có thể cũng đủ để gây ra thiếu bộ nhớ riêng. Hình 6 cho thấy một ví dụ vẽ biểu đồ bộ nhớ riêng của GCMV từ một bài thử nghiệm chịu tải của Java và giai đoạn khởi động được làm nổi bật.

 

Hình 6. Ví dụ biểu đồ bộ nhớ riêng của Linux từ GCMV, cho thấy giai đoạn khởi động

 

 

 

Cũng có thể có một dấu vết riêng thay đổi theo khối lượng công việc. Nếu ứng dụng của bạn tạo ra nhiều luồng để xử lý khối lượng công việc đến hoặc cấp phát vùng lưu trữ được hậu thuẫn riêng như là các ByteBuffer trực tiếp một cách tỷ lệ với tải công việc đang được đặt lên hệ thống của bạn, có khả năng là bạn sẽ cạn bộ nhớ riêng khi tải cao.

 


Chạy hết bộ nhớ riêng do sự tăng trưởng bộ nhớ riêng trong giai đoạn khởi động JVM và sự tăng trưởng tỷ lệ theo tải công việc, là những ví dụ về việc cố gắng làm quá nhiều việc trong vùng nhớ có sẵn. Trong những kịch bản này, tùy chọn của bạn là:

 

  • Giảm sử dụng bộ nhớ riêng của bạn. Giảm kích thước vùng heap Java là điểm bắt đầu thích hợp.
  • Hạn chế sử dụng bộ nhớ riêng của bạn. Nếu sự tăng trưởng bộ nhớ riêng thay đổi theo tải, hãy tìm một cách để giảm tải hoặc tài nguyên được cấp phát vì lý do này.
  • Tăng số lượng vùng địa chỉ có sẵn cho bạn. Bạn có thể làm điều này bằng cách điều chỉnh hệ điều hành của bạn (tăng vùng người sử dụng của bạn bằng khóa chuyển đổi /3GB trên Windows hoặc dùng nhân hugemem trên Linux, ví dụ thế), việc thay đổi nền tảng (Linux thường có vùng người sử dụng nhiều hơn Windows), hoặc di chuyển đến hệ điều hành 64-bit.

 

Một lỗ rò bộ nhớ riêng chính cống sẽ hiển hiện dưới dạng sự tăng trưởng liên tục trong vùng heap riêng mà không hề giảm xuống khi đã gỡ bỏ tải hoặc khi bộ thu dọn dữ liệu rác chạy. Tỷ lệ lỗ rò bộ nhớ có thể khác nhau tùy theo tải, nhưng tổng số thất thoát bộ nhớ sẽ không giảm. Bộ nhớ rò rỉ không có khả năng được tham chiếu đến, do đó, nó có thể được trao đổi ra và duy trì trao đổi ra.

 


Khi đối mặt với một lỗ rò, các tùy chọn của bạn bị hạn chế. Bạn có thể tăng số lượng vùng người sử dụng (do đó có nhiều chỗ để rò rỉ) nhưng điều đó sẽ chỉ kéo dài thêm thời gian trước khi rốt cuộc bạn dùng hết bộ nhớ. Nếu bạn có đủ bộ nhớ vật lý và vùng địa chỉ, bạn có thể cho phép lỗ rò vẫn còn trên cơ sở là bạn sẽ khởi động lại ứng dụng của mình trước khi vùng địa chỉ tiến trình bị cạn kiệt.

 

Cái gì đang sử dụng bộ nhớ riêng của tôi?

 

Một khi bạn đã xác định bạn đang cạn kiệt bộ nhớ riêng, câu hỏi hợp lý tiếp theo là: cái gì đang sử dụng bộ nhớ đó? Việc trả lời câu hỏi này rất khó bởi vì, theo mặc định, Windows và Linux không lưu trữ thông tin về tuyến mã nào được cấp phát một đoạn bộ nhớ cụ thể.

 

Bước đầu tiên của bạn khi cố gắng hiểu bộ nhớ riêng của bạn đã chạy đi đâu là tính toán đại khái có bao nhiêu bộ nhớ riêng sẽ được sử dụng dựa trên các giá trị thiết lập Java của bạn. Một giá trị chính xác là rất khó tính được mà không có kiến thức chuyên sâu về các hoạt động của JVM, nhưng bạn có thể thực hiện một sự ước lượng gần đúng dựa trên các chỉ dẫn sau đây:

 

  • Vùng heap Java chiếm giá trị -Xmx ít nhất.
  • Mỗi luồng Java yêu cầu vùng ngăn xếp. Kích thước ngăn xếp khác nhau tùy theo các việc triển khai thực hiện, nhưng với giá trị thiết lập mặc định, mỗi luồng có thể chiếm đến 756KB của bộ nhớ riêng.
  • Các ByteBuffer trực tiếp chiếm các giá trị ít nhất cũng bằng các giá trị được cung cấp cho thường trình allocate().

 

Nếu tổng số của bạn ít hơn nhiều so với vùng người dùng tối đa của bạn, cũng không nhất thiết là bạn đã an toàn. Nhiều thành phần khác trong một thời gian chạy Java có thể cấp phát lượng bộ nhớ đủ để gây ra các vấn đề; tuy nhiên, nếu các tính toán ban đầu của bạn cho thấy bạn đã đạt gần tới vùng người dùng tối đa của bạn, nhiều khả năng là bạn sẽ gặp vấn đề về bộ nhớ riêng. Nếu bạn nghi ngờ bạn có một lỗ rò bộ nhớ riêng hoặc bạn muốn hiểu chính xác bộ nhớ của bạn chạy đi đâu, có một số công cụ có thể giúp bạn.

 


Microsoft cung cấp công cụ UMDH (user-mode dump heap – heap kết xuất của chế độ người sử dụng) và công cụ LeakDiag để gỡ lỗi tăng bộ nhớ riêng trên Windows (xem Tài nguyên). Cả hai đều làm việc theo cùng cách tương tự như nhau: ghi lại tuyến mã nào được cấp phát cho một vùng bộ nhớ cụ thể và cung cấp một cách để xác định vị trí đoạn mã cấp phát bộ nhớ mà sau này không được giải phóng. Tôi giới thiệu cho bạn bài viết "Umdhtools.exe: Sử dụng Umdh.exe như thế nào để tìm các lỗ rò bộ nhớ trên Windows" để tìm chỉ dẫn về cách sử dụng UMDH (xem Tài nguyên). Trong bài này, tôi sẽ tập trung vào kết quả đầu ra của UMDH trông thế nào khi chạy nó với một ứng dụng JNI có lỗ rò.

 

Gói các ví dụ mẫu cho bài viết này có chứa một ứng dụng Java được gọi là LeakyJNIApp; nó chạy trong một vòng lặp gọi một phương thức JNI có lỗ rò bộ nhớ riêng. Lệnh UMDH chụp ảnh nhanh về vùng heap riêng hiện tại cùng với dấu vết ngăn xếp riêng của các tuyến mã được cấp phát cho từng vùng của bộ nhớ. Bằng cách chụp hai ảnh nhanh và sử dụng công cụ UMDH để phân tích sự khác biệt, bạn nhận được một báo cáo về sự tăng trưởng của vùng heap giữa hai ảnh chụp nhanh.

 

Đối với LeakyJNIApp, tệp khác có chứa thông tin này:

 

// _NT_SYMBOL_PATH set by default to C:\WINDOWS\symbols // // Each
log entry has the following syntax: // // + BYTES_DELTA (NEW_BYTES - OLD_BYTES)
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
NEW_COUNT allocs BackTrace TRACEID // + COUNT_DELTA (NEW_COUNT - OLD_COUNT)
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
BackTrace TRACEID allocations // ... stack trace ... // // where: // // BYTES_DELTA
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
- increase in bytes between before and after log // NEW_BYTES - bytes in after log
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
// OLD_BYTES - bytes in before log // COUNT_DELTA - increase in allocations between
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
before and after log // NEW_COUNT - number of allocations in after log // OLD_COUNT
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
- number of allocations in before log // TRACEID - decimal index of the stack trace
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
in the trace database // (can be used to search for allocation instances in the
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
original // UMDH logs). // + 412192 ( 1031943 - 619751) 963 allocs BackTrace00468
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
Total increase == 412192

 

Dòng quan trọng là + 412192 ( 1031943 - 619751) 963 allocs BackTrace00468. Nó cho thấy rằng một lệnh dò vết ngăn xếp (backtrace) đã thực hiện 963 lần cấp phát chưa được giải phóng — dùng hết 412.192 byte của bộ nhớ. Bằng cách nhìn vào một trong những tệp ảnh chụp nhanh, bạn có thể kết hợp BackTrace00468 với đường tuyến mã có ý nghĩa. Tìm kiếm BackTrace00468 trong ảnh chụp đầu tiên cho thấy:

 

000000AD bytes in 0x1 allocations (@ 0x00000031 + 0x0000001F) by:
BackTrace00468 ntdll!RtlpNtMakeTemporaryKey+000074D0
ntdll!RtlInitializeSListHead+00010D08 ntdll!wcsncat+00000224
leakyjniapp!Java_com_ibm_jtc_demos_LeakyJNIApp_nativeMethod+000000D6

 

Điều này cho thấy lỗ rò đến từ mô đun leakyjniapp.dll trong hàm Java_com_ibm_jtc_demos_LeakyJNIApp_nativeMethod.

 

Vào thời điểm viết bài này, Linux chưa có một công cụ UMDH hoặc LeakDiag tương đương. Nhưng vẫn còn có các cách để gỡ lỗi rò rỉ bộ nhớ riêng trên Linux. Nhiều trình gỡ lỗi bộ nhớ có sẵn trên Linux, thường rơi vào một trong các loại sau:

 

  • Mức bộ xử lý trước. Những trình gỡ lỗi này yêu cầu một tiêu đề (header) được biên dịch cùng với mã nguồn thử nghiệm. Có thể biên dịch lại các thư viện JNI riêng của bạn với một trong những công cụ này để theo dõi một lỗ rò bộ nhớ riêng trong mã của bạn. Trừ khi bạn có mã nguồn Java của chính thời gian chạy Java, việc này không thể tìm thấy một lỗ rò trong JVM (và rồi ngay cả khi việc kết hợp các công cụ loại này thành một dự án lớn như JVM chắc chắn sẽ rất khó khăn và tốn thời gian). Dmalloc là một ví dụ về loại công cụ này (xem Tài nguyên).
  • Mức trình liên kết. Những trình gỡ lỗi này yêu cầu các mã nhị phân trong lúc thử nghiệm để được liên kết lại với một thư viện gỡ lỗi. Một lần nữa, điều này có thể khả thi cho các thư viện JNI riêng lẻ, nhưng không nên dùng cho toàn bộ các thời gian chạy Java bởi vì ít có khả năng là nhà cung cấp thời gian chạy sẽ hỗ trợ bạn chạy các mã nhị phân đã sửa đổi. Ccmalloc là một ví dụ của loại công cụ này (xem Tài nguyên).
  • Mức trình liên kết-thời gian chạy. Những trình gỡ lỗi này sử dụng biến môi trường LD_PRELOAD để nạp trước một thư viện thay thế các thường trình bộ nhớ tiêu chuẩn bằng các phiên bản có cài công cụ. Chúng không yêu cầu biên dịch lại hoặc liên kết lại mã nguồn, nhưng nhiều trình gỡ lỗi trong số chúng không làm việc tốt với các thời gian chạy Java. Một thời gian chạy Java là một hệ thống phức tạp có thể sử dụng bộ nhớ và các luồng theo nhiều cách khác thường có thể gây lúng túng hoặc phá hỏng loại công cụ này. Nên thử nghiệm một số ít để xem chúng có làm việc trong kịch bản của bạn không. NJAMD là một ví dụ về loại công cụ này (xem Tài nguyên).
  • Dựa vào-trình mô hình hóa (Emulator). Công cụ memcheck của Valgrind là ví dụ duy nhất của kiểu trình gỡ lỗi bộ nhớ này (xem Tài nguyên). Nó mô phỏng bộ xử lý ở bên dưới theo một cách tương tự như cách một thời gian chạy Java mô phỏng JVM. Có thể chạy Java trong Valgrind, nhưng ảnh hưởng hiệu năng rất nặng nề (10-30 lần chậm hơn), có nghĩa là sẽ rất khó để chạy các ứng dụng Java lớn, phức tạp theo cách này. Valgrind hiện tại đang có trên Linux x86, AMD64, PPC 32 và PPC 64. Nếu bạn sử dụng Valgrind, hãy cố gắng thu hẹp vấn đề đến mức bài thử nghiệm nhỏ nhất mà bạn có thể (tốt hơn là cắt bớt toàn bộ thời gian chạy Java nếu có thể) trước khi sử dụng nó.

 

Với các kịch bản đơn giản có thể bỏ qua sự quá tải hiệu năng, memcheck của Valgrind là công cụ đơn giản nhất và thân thiện với người dùng trong số các công cụ miễn phí có sẵn. Nó có thể cung cấp việc theo vết ngăn xếp đầy với các tuyến mã có lỗ rò bộ nhớ theo cùng cách mà UMDH có thể làm trên Windows.

 

LeakyJNIApp đủ đơn giản để chạy trong Valgrind. Công cụ memcheck của Valgrind có thể in ra một bản tóm tắt về bộ nhớ bị rò rỉ khi kết thúc chương trình mô phỏng. Theo mặc định, chương trình LeakyJNIApp chạy vô hạn; để tắt nó sau một khoảng thời gian ấn định, chuyển thời gian chạy tính bằng giây làm đối số dòng lệnh duy nhất.

 

Một số các thời gian chạy Java sử dụng các ngăn xếp luồng và các thanh ghi của bộ xử lý theo những cách khác thường; điều này có thể làm một số công cụ gỡ lỗi lúng túng, vì các công cụ này giả định các chương trình riêng tuân theo các quy ước chuẩn về sử dụng thanh ghi và cấu trúc ngăn xếp. Khi sử dụng Valgrind để gỡ lỗi các ứng dụng JNI có lỗ rò, bạn có thể thấy rằng nhiều cảnh báo về việc sử dụng bộ nhớ được đưa ra và một số ngăn xếp luồng sẽ trông khác thường; điều này là do cách thời gian chạy Java cấu trúc dữ liệu của nó bên trong và không có gì phải lo lắng về nó.

 

Để theo dõi LeakyJNIApp bằng công cụ memcheck của Valgrind, hãy sử dụng lệnh này (trên chỉ có một dòng):

 

valgrind --trace-children=yes --leak-check=full java
-Djava.library.path=. com.ibm.jtc.demos.LeakyJNIApp 10

 

Tùy chọn --trace-children=yes cho Valgrind làm cho nó theo vết tiến trình bất kỳ được bắt đầu bởi trình khởi chạy (launcher) Java. Một số phiên bản của trình khởi chạy Java tự chạy lại chính mình (chúng tự khởi động lại từ đầu, một lần nữa thiết lập các biến môi trường để thay đổi hành vi). Nếu bạn không chỉ rõ --trace-children, bạn không thể theo vết thời gian chạy Java thực tế.

 

Tùy chọn --leak-check=full yêu cầu các dấu vết ngăn xếp đầy của các vùng mã có lỗ rò được in ra khi kết thúc, thay vì chỉ là tóm tắt trạng thái của bộ nhớ.

 

Valgrind in nhiều cảnh báo và lỗi trong khi lệnh chạy thi hành (hầu hết trong số chúng là không đáng chú ý trong ngữ cảnh này) và cuối cùng in một danh sách các ngăn xếp cuộc gọi có lỗ rò theo thứ tự tăng dần của lượng bộ nhớ bị rò rỉ. Đoạn kết của phần tóm tắt kết quả đầu ra của Valgrind cho LeakyJNIApp trên Linux x86 là:

 

==20494== 8,192 bytes in 8 blocks are possibly lost in loss record
36 of 45 ==20494== at 0x4024AB8: malloc (vg_replace_malloc.c:207) ==20494== by
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
0x460E49D: Java_com_ibm_jtc_demos_LeakyJNIApp_nativeMethod (in
/home/andhall/LeakyJNIApp/libleakyjniapp.so) ==20494== by 0x535CF56: ??? ==20494==
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
by 0x46423CB: gpProtectedRunCallInMethod (in
/usr/local/ibm-java2-i386-50/jre/bin/libj9vm23.so) ==20494== by 0x46441CF:
signalProtectAndRunGlue (in /usr/local/ibm-java2-i386-50/jre/bin/libj9vm23.so)
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
==20494== by 0x467E0D1: j9sig_protect (in
/usr/local/ibm-java2-i386-50/jre/bin/libj9prt23.so) ==20494== by 0x46425FD:
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
gpProtectAndRun (in /usr/local/ibm-java2-i386-50/jre/bin/libj9vm23.so) ==20494== by
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
0x4642A33: gpCheckCallin (in /usr/local/ibm-java2-i386-50/jre/bin/libj9vm23.so)
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
==20494== by 0x464184C: callStaticVoidMethod (in
/usr/local/ibm-java2-i386-50/jre/bin/libj9vm23.so) ==20494== by 0x80499D3: main (in
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
/usr/local/ibm-java2-i386-50/jre/bin/java) ==20494== ==20494== ==20494== 65,536
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
(63,488 direct, 2,048 indirect) bytes in 62 blocks are definitely lost in loss
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
record 42 of 45 ==20494== at 0x4024AB8: malloc (vg_replace_malloc.c:207) ==20494==
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
by 0x460E49D: Java_com_ibm_jtc_demos_LeakyJNIApp_nativeMethod (in
/home/andhall/LeakyJNIApp/libleakyjniapp.so) ==20494== by 0x535CF56: ??? ==20494==
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
by 0x46423CB: gpProtectedRunCallInMethod (in
/usr/local/ibm-java2-i386-50/jre/bin/libj9vm23.so) ==20494== by 0x46441CF:
signalProtectAndRunGlue (in /usr/local/ibm-java2-i386-50/jre/bin/libj9vm23.so)
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
==20494== by 0x467E0D1: j9sig_protect (in
/usr/local/ibm-java2-i386-50/jre/bin/libj9prt23.so) ==20494== by 0x46425FD:
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
gpProtectAndRun (in /usr/local/ibm-java2-i386-50/jre/bin/libj9vm23.so) ==20494== by
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
0x4642A33: gpCheckCallin (in /usr/local/ibm-java2-i386-50/jre/bin/libj9vm23.so)
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
==20494== by 0x464184C: callStaticVoidMethod (in
/usr/local/ibm-java2-i386-50/jre/bin/libj9vm23.so) ==20494== by 0x80499D3: main (in
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
/usr/local/ibm-java2-i386-50/jre/bin/java) ==20494== ==20494== LEAK SUMMARY:
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
==20494== definitely lost: 63,957 bytes in 69 blocks. ==20494== indirectly lost:
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
2,168 bytes in 12 blocks. ==20494== possibly lost: 8,600 bytes in 11 blocks.
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
==20494== still reachable: 5,156,340 bytes in 980 blocks. ==20494== suppressed: 0
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
bytes in 0 blocks. ==20494== Reachable blocks (those to which a pointer was found)
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
are not shown. ==20494== To see them, rerun with: --leak-check=full
--show-reachable=yes

 

Dòng thứ hai của các ngăn xếp cho thấy rằng bộ nhớ bị rò rỉ do phương thức com.ibm.jtc.demos.LeakyJNIApp.nativeMethod().

 

Một số ứng dụng gỡ lỗi độc quyền có thể gỡ lỗi các lỗ rò bộ nhớ riêng cũng có sẵn. Nhiều công cụ nữa (cả nguồn mở lẫn độc quyền) đang được phát triển không ngừng và nên nghiên cứu để nắm được trình độ hiện nay.

 

Hiện nay việc gỡ lỗi các lỗ rò bộ nhớ riêng trên Linux bằng các công cụ có sẵn miễn phí có nhiều thách thức hơn so với việc làm điều này trên Windows. Trong khi UMDH cho phép các lỗ rò riêng trên Windows được gỡ lỗi tại chỗ của nó, trên Linux có thể bạn sẽ cần làm một số việc gỡ lỗi truyền thống hơn là dựa vào một công cụ để giải quyết vấn đề cho bạn. Dưới đây là một số bước gỡ lỗi được đề xuất:

 

  • Lấy ra một trường hợp thử nghiệm. Tạo ra một môi trường độc lập mà bạn có thể tái tạo lại các lỗ rò riêng với nó. Nó sẽ làm cho việc gỡ lỗi đơn giản hơn nhiều.
  • Thu hẹp trường hợp thử nghiệm trong chừng mực có thể. Hãy thử ngắt các hàm để nhận biết các tuyến mã nào đang gây ra lỗ rò riêng. Nếu bạn có các thư viện JNI riêng của mình, hãy thử ngắt chúng hoàn toàn từng cái một để xác định xem có phải chúng đang gây ra lỗ rò hay không.
  • Giảm kích thước vùng heap Java. Vùng heap Java nhiều khả năng là kẻ tiêu dùng lớn nhất vùng địa chỉ ảo trong tiến trình. Bằng cách làm giảm vùng heap Java, bạn có thể tạo thêm nhiều vùng sẵn dùng dành cho những người dùng bộ nhớ riêng khác.
  • Tương quan với kích thước tiến trình riêng. Một khi bạn có một biểu đồ về việc sử dụng bộ nhớ riêng theo thời gian, bạn có thể so sánh nó với khối lượng công việc của ứng dụng và dữ liệu GC. Nếu tỷ lệ lỗ rò là tỷ lệ thuận với mức tải, điều này gợi ý rằng một cái gì đó gây ra lỗ rò trên đường đi của mỗi giao dịch hoặc hoạt động. Nếu kích thước tiến trình riêng giảm đáng kể khi việc thu gom rác (GC) xảy ra, điều này gợi ý rằng bạn không chứng kiến một lỗ rò — bạn đang chứng kiến sự tăng thêm các đối tượng được hậu thuẫn riêng (ví dụ như là các ByteBuffer trực tiếp). Bạn có thể làm giảm số lượng bộ nhớ bị chiếm giữ bởi đối tượng được hậu thuẫn riêng bằng cách giảm kích thước vùng heap Java (do đó buộc việc thu dọn rác xảy ra thường xuyên hơn), hoặc bằng cách tự mình quản lý chúng trong một bộ nhớ sẵn (cache) đối tượng hơn là dựa vào các bộ thu dọn dữ liệu rác để làm sạch cho bạn.

 

Nếu bạn nhận ra một lỗ rò hoặc sự tăng thêm bộ nhớ mà bạn nghĩ là bắt nguồn từ chính thời gian chạy Java, bạn có thể lôi kéo nhà cung cấp thời gian chạy của bạn vào việc tiếp tục gỡ lỗi.

 

 

Phần tiếp theo: Loại bỏ hạn chế: Đổi sang 64-bit