2015-06-04 11 views
7

Tôi đã tự hỏi nếu có ai đó có thể giải thích cho tôi làm thế nào để giải thích một số bản ghi G1 GC dẫn đến một OutOfMemoryError?Cách diễn giải các bản ghi G1 GC dẫn đến lỗi OutOfMemoryError?

Tôi biết rằng một bãi chứa đống là đặt cược tốt nhất để tìm hiểu những gì đang thực sự sử dụng heap nhưng tôi không thể nhận được vì nó chứa thông tin được bảo vệ không thể rời khỏi trang web khách hàng. Tất cả những gì tôi có là các bản ghi ứng dụng (bao gồm cả ngăn xếp từ OOME) và nhật ký GC G1.

Bản ghi đầy đủ G1 GC có rất nhiều chi tiết nên tôi sẽ không đặt chúng ở đây trừ khi ai đó cần xem chúng.

Phiên bản Java cụ thể rằng những từ là:

> java -version 
java version "1.7.0_21" 
Java(TM) SE Runtime Environment (build 1.7.0_21-b11) 
Java HotSpot(TM) 64-Bit Server VM (build 23.21-b01, mixed mode) 

Và các tùy chọn GC Tôi đang sử dụng để tạo ra các bản ghi GC là:

-XX:+PrintGCTimeStamps 
-XX:+PrintGCDetails 
-Xloggc:log/gc.log 

Dưới đây là tất cả các số liệu thống kê bộ nhớ từ mỗi GC trẻ và đầy đủ trong 30 phút qua dẫn đến OOME:

INFO | jvm 1 | 2015/05/28 04:29:34 | [Eden: 1290M(1290M)->0B(1290M) Survivors: 20M->20M Heap: 2445M(3932M)->1155M(3932M)] 
INFO | jvm 1 | 2015/05/28 04:33:21 | [Eden: 1290M(1290M)->0B(1290M) Survivors: 20M->20M Heap: 2445M(3932M)->1155M(3932M)] 
INFO | jvm 1 | 2015/05/28 04:37:09 | [Eden: 1290M(1290M)->0B(1290M) Survivors: 20M->20M Heap: 2445M(3932M)->1155M(3932M)] 
INFO | jvm 1 | 2015/05/28 04:40:58 | [Eden: 1290M(1290M)->0B(1290M) Survivors: 20M->20M Heap: 2445M(3932M)->1155M(3932M)] 
INFO | jvm 1 | 2015/05/28 04:44:44 | [Eden: 1290M(1290M)->0B(1290M) Survivors: 20M->20M Heap: 2445M(3932M)->1155M(3932M)] 
INFO | jvm 1 | 2015/05/28 04:48:30 | [Eden: 1290M(1290M)->0B(1290M) Survivors: 20M->20M Heap: 2445M(3932M)->1155M(3932M)] 
INFO | jvm 1 | 2015/05/28 04:52:17 | [Eden: 1290M(1290M)->0B(1290M) Survivors: 20M->20M Heap: 2445M(3932M)->1155M(3932M)] 
INFO | jvm 1 | 2015/05/28 04:52:58 | [Eden: 639M(1290M)->0B(1295M) Survivors: 20M->15M Heap: 2278M(3932M)->1635M(3932M)] 
INFO | jvm 1 | 2015/05/28 04:52:59 | [Eden: 51M(1295M)->0B(1300M) Survivors: 15M->10M Heap: 2561M(3932M)->2505M(3932M)] 
INFO | jvm 1 | 2015/05/28 04:52:59 | [Full GC 2505M->1170M(3901M), 1.9469560 secs] 
INFO | jvm 1 | 2015/05/28 04:53:01 | [Eden: 44M(1300M)->0B(1299M) Survivors: 0B->1024K Heap: 1653M(3901M)->1610M(3901M)] 
INFO | jvm 1 | 2015/05/28 04:53:01 | [Eden: 1024K(1299M)->0B(1299M) Survivors: 1024K->1024K Heap: 1610M(3901M)->1610M(3901M)] 
INFO | jvm 1 | 2015/05/28 04:53:02 | [Full GC 1610M->1158M(3891M), 1.4317370 secs] 
INFO | jvm 1 | 2015/05/28 04:53:03 | [Eden: 112M(1299M)->0B(1296M) Survivors: 0B->1024K Heap: 1758M(3891M)->1647M(3891M)] 
INFO | jvm 1 | 2015/05/28 04:53:06 | [Eden: 49M(1296M)->0B(1360M) Survivors: 1024K->1024K Heap: 2776M(4084M)->2728M(4084M)] 
INFO | jvm 1 | 2015/05/28 04:53:06 | [Eden: 0B(1360M)->0B(1360M) Survivors: 1024K->1024K Heap: 2837M(4084M)->2836M(4084M)] 
INFO | jvm 1 | 2015/05/28 04:53:06 | [Full GC 2836M->1158M(3891M), 1.4847750 secs] 
INFO | jvm 1 | 2015/05/28 04:53:08 | [Full GC 1158M->1158M(3891M), 1.5313770 secs] 

* Đây là một di định dạng fferent cho các bản ghi thô và tôi đã xóa các chi tiết thời gian để làm cho nó ngắn hơn và dễ đọc hơn.

Tôi cũng vẽ đồ thị GC liệu đăng nhập này trong GCViewer:

enter image description here

Nó có vẻ như tất cả mọi thứ đang diễn ra ok cho đến nay:

  • Các tenured sử dụng là không đổi (dòng đỏ tươi đậm ở cuối biểu đồ).
  • Tạm dừng GC trẻ xảy ra sau mỗi vài phút dọn dẹp tất cả các đối tượng trẻ (đường màu xám ở đầu biểu đồ).
  • Kích thước phân bổ cho từng thế hệ là không đổi.
  • Sử dụng đống sau khi các GC trẻ là khoảng 1155M.

Sau đó tại 2015/05/28 04:52:59 mọi thứ đã đi hình quả lê:

  • Các gạo cội cho tất cả của một gia tăng đột ngột bắt đầu.
  • Khi GC trẻ chạy ở đó chỉ có 51M trong không gian eden.
  • Bắt đầu xảy ra các GC đầy đủ.
  • 3 GC đầy đủ đầu tiên có vẻ ổn, chúng giảm mức sử dụng vùng heap xuống 1158M-1170M (rất gần với 1155M bình thường).
  • GC đầy đủ cuối cùng bắt đầu với 1158M được sử dụng và vẫn còn sau 1158M.

Tab Memory trong ảnh chụp màn hình cho thấy:

Tenured heap (usage/alloc. max) 2,836 (104.1%)/2,723M 
Total promotion          2,048K 

Bây giờ để giải thích ngắn gọn những gì xảy ra tại 2015/05/28 04:52:59. Tại thời điểm này, một loạt các đối tượng cấu hình đã được tuần tự hóa thành một định dạng tùy chỉnh bằng cách sử dụng một StringBuilder.Điều này dẫn đến một loạt các bản sao mảng mà cuối cùng dẫn đến sự ngoại lệ sau đây tại 2015/05/28 04:53:09:

java.lang.OutOfMemoryError: Java heap space 
    at java.util.Arrays.copyOf(Arrays.java:2367) 
    at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:130) 
    at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:114) 
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:587) 
    at java.lang.StringBuilder.append(StringBuilder.java:214) 
    ... 

Có một vài điều tôi không thể giải thích:

  1. đâu trong các bản ghi GC bạn sẽ tìm thấy bộ nhớ đã qua sử dụng?
  2. Điều gì sẽ gây ra sự tăng đột biến đáng kể trong bộ nhớ đã sử dụng đã sử dụng để gây ra GC? Chỉ có 20 triệu người sống sót vì vậy sẽ không phải trong trường hợp xấu nhất mà tất cả đều phải chịu đựng và không còn nữa?
  3. Điều này có lẽ có thể được giải thích bằng cách phân bổ đối tượng không?
  4. Tại sao GC đầy đủ cuối cùng được kích hoạt khi có (dường như) rất ít sử dụng đống và sau đó nó làm sạch không có gì?
  5. Nếu có 3891M heap được phân bổ và chỉ sử dụng 1158M thì tại sao lại có một OOME?
+0

Bạn nên chạy bằng '-XX: + PrintGCDetails' để biết thêm thông tin mà GCViewer có thể xử lý – the8472

+0

@ the8472 Yup đó là những gì tôi đã làm. Tôi đã thêm những chi tiết đó vào câu hỏi. – Steiny

+0

Tôi sẽ thử với tùy chọn '-XX: + PrintAdaptiveSizePolicy' để xem nó có hiển thị một phân bổ đối tượng khổng lồ hay không. – Steiny

Trả lời

1

Việc hết bộ nhớ của bạn xảy ra trong StringBuilder.append - nhớ rằng mỗi khi bạn nối chuỗi và bộ đệm bên trong StringBuilder quá nhỏ sao cho nó mở rộng dung lượng, nó sẽ cố gắng cấp phát bộ đệm bằng cách tăng gấp đôi độ dài hiện tại của Chuỗi trong trình tạo cộng với 2 hoặc độ dài mới nếu lớn hơn. (Xem mã nguồn cho AbstractStringBuilder.java)

Ví dụ, nếu người xây dựng chuỗi của bạn đã có 100 ký tự và có đầy đủ, sau đó bạn nối thêm 10 nhân vật với nó, nó sẽ mở rộng theo:

100 * 2 + 2 = 202, lớn hơn 10.

Vì vậy, nếu bạn đã có chuỗi dài thực sự (10MB), nó sẽ cố gắng tạo bộ đệm 20MB và v.v.

Kiểm tra mã của bạn và đảm bảo bạn không tạo chuỗi lớn trong trình tạo.

Các vấn đề liên quan