Gỡ lỗi thu gom rác ART

Trang này mô tả cách gỡ lỗi các vấn đề về hiệu suất và độ chính xác của tính năng thu gom rác (GC) trong Android Runtime (ART). Tài liệu này giải thích cách sử dụng các tuỳ chọn xác minh GC, xác định giải pháp cho các lỗi xác minh GC, đồng thời đo lường và giải quyết các vấn đề về hiệu suất GC.

Để làm việc với ART, hãy xem các trang trong phần ART và Dalvik này và định dạng Tệp thực thi Dalvik. Để được trợ giúp thêm về cách xác minh hành vi của ứng dụng, hãy xem bài viết Xác minh hành vi của ứng dụng trên Android Runtime (ART).

Tổng quan về GC ART

ART có một vài kế hoạch GC khác nhau bao gồm việc chạy nhiều trình thu gom rác. Kể từ Android 8 (Oreo), kế hoạch mặc định là Sao chép đồng thời (CC). Kế hoạch GC khác là Concurrent Mark Sweep (CMS).

Một số đặc điểm chính của GC sao chép đồng thời là:

  • CC cho phép sử dụng trình phân bổ con trỏ nhô lên có tên là RegionTLAB. Thao tác này sẽ phân bổ bộ đệm phân bổ cục bộ của luồng (TLAB) cho mỗi luồng ứng dụng, sau đó có thể phân bổ các đối tượng ra khỏi TLAB bằng cách đẩy con trỏ "top" (trên cùng) mà không cần đồng bộ hoá.
  • CC thực hiện việc phân mảnh vùng nhớ khối xếp bằng cách đồng thời sao chép các đối tượng mà không tạm dừng luồng ứng dụng. Điều này được thực hiện nhờ một rào cản đọc giúp chặn các lượt đọc tham chiếu từ vùng nhớ khối xếp mà không cần nhà phát triển ứng dụng can thiệp.
  • GC chỉ có một thời điểm tạm dừng nhỏ, không đổi theo thời gian liên quan đến kích thước vùng nhớ khối xếp.
  • CC mở rộng thành một GC thế hệ trong Android 10 trở lên. Tính năng này cho phép thu thập các đối tượng mới, thường không thể truy cập được khá nhanh mà không tốn nhiều công sức. Điều này giúp tăng thông lượng GC và trì hoãn đáng kể nhu cầu thực hiện GC toàn bộ vùng nhớ khối xếp.

GC khác mà ART vẫn hỗ trợ là CMS. GC này cũng hỗ trợ tính năng nén, nhưng không đồng thời. Quá trình nén sẽ bị tránh cho đến khi ứng dụng chuyển sang chế độ nền, tại thời điểm này, các luồng ứng dụng sẽ bị tạm dừng để thực hiện quá trình nén. Việc nén cũng trở nên cần thiết khi quá trình phân bổ đối tượng không thành công do phân mảnh. Trong trường hợp này, ứng dụng có thể không phản hồi trong một khoảng thời gian.

Vì CMS hiếm khi nén, nên các đối tượng trống có thể không liền nhau, CMS sử dụng trình phân bổ dựa trên danh sách trống có tên là RosAlloc. Phương thức này có chi phí phân bổ cao hơn so với RegionTLAB. Cuối cùng, do tình trạng phân mảnh nội bộ, mức sử dụng bộ nhớ cho vùng nhớ khối xếp Java có thể cao hơn đối với CMS so với CC.

Các tuỳ chọn hiệu suất và xác minh GC

Thay đổi loại GC

Nhà sản xuất thiết bị gốc (OEM) có thể thay đổi loại GC. Quá trình thay đổi liên quan đến việc thiết lập biến môi trường ART_USE_READ_BARRIER tại thời điểm tạo bản dựng. Giá trị mặc định là true, cho phép trình thu thập CC vì trình thu thập này sử dụng rào cản đọc. Đối với CMS, bạn phải đặt biến này thành false một cách rõ ràng.

Theo mặc định, trình thu thập CC chạy ở chế độ tạo sinh trong Android 10 trở lên. Để tắt chế độ tạo sinh, bạn có thể sử dụng đối số dòng lệnh -Xgc:nogenerational_cc. Ngoài ra, bạn có thể đặt thuộc tính hệ thống như sau:

adb shell setprop dalvik.vm.gctype nogenerational_cc
Trình thu thập CMS luôn chạy ở chế độ tạo sinh.

Xác minh vùng nhớ khối xếp

Xác minh vùng nhớ khối xếp có thể là tuỳ chọn GC hữu ích nhất để gỡ lỗi các lỗi liên quan đến GC hoặc vùng nhớ khối xếp bị hỏng. Việc bật tính năng xác minh vùng nhớ khối xếp sẽ khiến GC kiểm tra độ chính xác của vùng nhớ khối xếp tại một vài điểm trong quá trình thu gom rác. Tính năng xác minh vùng nhớ khối xếp có các tuỳ chọn giống như các tuỳ chọn thay đổi loại GC. Nếu được bật, tính năng xác minh vùng nhớ khối xếp sẽ xác minh các gốc và đảm bảo rằng các đối tượng có thể truy cập chỉ tham chiếu đến các đối tượng có thể truy cập khác. Bạn có thể bật tính năng xác minh GC bằng cách truyền các giá trị -Xgc sau:

  • Nếu được bật, [no]preverify sẽ thực hiện quy trình xác minh vùng nhớ khối xếp trước khi bắt đầu GC.
  • Nếu được bật, [no]presweepingverify sẽ thực hiện xác minh vùng nhớ khối xếp trước khi bắt đầu quy trình quét của trình thu gom rác.
  • Nếu được bật, [no]postverify sẽ thực hiện xác minh vùng nhớ khối xếp sau khi GC hoàn tất quá trình quét.
  • [no]preverify_rosalloc, [no]postsweepingverify_rosalloc[no]postverify_rosalloc là các tuỳ chọn GC bổ sung chỉ xác minh trạng thái của hoạt động kế toán nội bộ của RosAlloc. Do đó, các đối tượng này chỉ áp dụng được với trình thu thập CMS sử dụng bộ phân bổ RosAlloc. Những điều chính được xác minh là giá trị ma thuật khớp với hằng số dự kiến và các khối bộ nhớ trống đều được đăng ký trong bản đồ free_page_runs_.

Hiệu suất

Có hai công cụ chính để đo lường hiệu suất GC, tệp báo lỗi thời gian GC và Systrace. Ngoài ra, còn có một phiên bản Systrace nâng cao, có tên là Perfetto. Cách trực quan để đo lường các vấn đề về hiệu suất GC là sử dụng Systrace và Perfetto để xác định những GC nào đang gây ra thời gian tạm dừng dài hoặc chiếm quyền trước các luồng ứng dụng. Mặc dù GC ART đã cải thiện đáng kể theo thời gian, nhưng hành vi của trình sửa đổi xấu, chẳng hạn như phân bổ quá mức, vẫn có thể gây ra vấn đề về hiệu suất

Chiến lược thu thập

CC GC thu thập bằng cách chạy một GC mới hoặc GC vùng nhớ khối xếp đầy. Lý tưởng nhất là GC mới được chạy thường xuyên hơn. GC thực hiện các hoạt động thu thập CC mới cho đến khi thông lượng (tính bằng số byte được giải phóng/giây của thời lượng GC) của chu kỳ thu thập vừa kết thúc thấp hơn thông lượng trung bình của các hoạt động thu thập CC vùng nhớ khối xếp đầy. Khi điều này xảy ra, CC vùng nhớ khối xếp đầy sẽ được chọn cho GC đồng thời tiếp theo thay vì CC mới. Sau khi quá trình thu thập rác vùng nhớ khối xếp đầy hoàn tất, GC tiếp theo sẽ chuyển về CC mới. Một yếu tố chính giúp chiến lược này hoạt động là CC mới không điều chỉnh giới hạn kích thước vùng nhớ khối xếp sau khi hoàn tất. Điều này khiến CC non xảy ra thường xuyên hơn cho đến khi thông lượng thấp hơn CC đầy vùng nhớ khối xếp, cuối cùng làm tăng vùng nhớ khối xếp.

Sử dụng SIGQUIT để nhận thông tin hiệu suất GC

Để biết thời gian hiệu suất GC cho ứng dụng, hãy gửi SIGQUIT đến các ứng dụng đang chạy hoặc truyền -XX:DumpGCPerformanceOnShutdown vào dalvikvm khi khởi động một chương trình dòng lệnh. Khi nhận được tín hiệu yêu cầu ANR (SIGQUIT), ứng dụng sẽ kết xuất thông tin liên quan đến các khoá, ngăn xếp luồng và hiệu suất GC.

Để nhận tệp kết xuất thời gian GC, hãy sử dụng:

adb shell kill -s QUIT PID

Thao tác này sẽ tạo một tệp (có ngày và giờ trong tên, chẳng hạn như anr_2020-07-13-19-23-39-817) trong /data/anr/. Tệp này chứa một số tệp kết xuất ANR cũng như thời gian GC. Bạn có thể xác định thời gian GC bằng cách tìm kiếm Tải thời gian Gc tích luỹ. Các thời gian này cho thấy một số điều có thể bạn quan tâm, bao gồm cả thông tin biểu đồ cho các giai đoạn và thời điểm tạm dừng của từng loại GC. Bạn nên xem xét các điểm tạm dừng nhiều hơn. Ví dụ:

young concurrent copying paused:	Sum: 5.491ms 99% C.I. 1.464ms-2.133ms Avg: 1.830ms Max: 2.133ms

Điều này cho thấy thời gian tạm dừng trung bình là 1,83 ms, đủ thấp để không gây ra tình trạng bỏ lỡ khung hình trong hầu hết các ứng dụng và không đáng lo ngại.

Một khía cạnh khác cần quan tâm là thời gian tạm ngưng, đo lường thời gian một luồng cần để đạt đến điểm tạm ngưng sau khi GC yêu cầu tạm ngưng luồng đó. Thời gian này được đưa vào các lần tạm dừng GC, vì vậy, bạn có thể xác định xem các lần tạm dừng dài là do GC bị chậm hay luồng bị tạm ngưng chậm. Sau đây là ví dụ về thời gian thông thường để tạm ngưng trên Nexus 5:

suspend all histogram:	Sum: 1.513ms 99% C.I. 3us-546.560us Avg: 47.281us Max: 601us

Có những khía cạnh khác cũng đáng quan tâm, bao gồm tổng thời gian và lưu lượng GC. Ví dụ:

Total time spent in GC: 502.251ms
Mean GC size throughput: 92MB/s
Mean GC object throughput: 1.54702e+06 objects/s

Dưới đây là ví dụ về cách kết xuất thời gian GC của một ứng dụng đang chạy:

adb shell kill -s QUIT PID
adb pull /data/anr/anr_2020-07-13-19-23-39-817

Tại thời điểm này, thời gian GC nằm bên trong anr_2020-07-13-19-23-39-817. Dưới đây là kết quả mẫu từ Google Maps:

Start Dumping histograms for 2195 iterations for concurrent copying
MarkingPhase:   Sum: 258.127s 99% C.I. 58.854ms-352.575ms Avg: 117.651ms Max: 641.940ms
ScanCardsForSpace:      Sum: 85.966s 99% C.I. 15.121ms-112.080ms Avg: 39.164ms Max: 662.555ms
ScanImmuneSpaces:       Sum: 79.066s 99% C.I. 7.614ms-57.658ms Avg: 18.014ms Max: 546.276ms
ProcessMarkStack:       Sum: 49.308s 99% C.I. 6.439ms-81.640ms Avg: 22.464ms Max: 638.448ms
ClearFromSpace: Sum: 35.068s 99% C.I. 6.522ms-40.040ms Avg: 15.976ms Max: 633.665ms
SweepSystemWeaks:       Sum: 14.209s 99% C.I. 3.224ms-15.210ms Avg: 6.473ms Max: 201.738ms
CaptureThreadRootsForMarking:   Sum: 11.067s 99% C.I. 0.835ms-13.902ms Avg: 5.044ms Max: 25.565ms
VisitConcurrentRoots:   Sum: 8.588s 99% C.I. 1.260ms-8.547ms Avg: 1.956ms Max: 231.593ms
ProcessReferences:      Sum: 7.868s 99% C.I. 0.002ms-8.336ms Avg: 1.792ms Max: 17.376ms
EnqueueFinalizerReferences:     Sum: 3.976s 99% C.I. 0.691ms-8.005ms Avg: 1.811ms Max: 16.540ms
GrayAllDirtyImmuneObjects:      Sum: 3.721s 99% C.I. 0.622ms-6.702ms Avg: 1.695ms Max: 14.893ms
SweepLargeObjects:      Sum: 3.202s 99% C.I. 0.032ms-6.388ms Avg: 1.458ms Max: 549.851ms
FlipOtherThreads:       Sum: 2.265s 99% C.I. 0.487ms-3.702ms Avg: 1.031ms Max: 6.327ms
VisitNonThreadRoots:    Sum: 1.883s 99% C.I. 45us-3207.333us Avg: 429.210us Max: 27524us
InitializePhase:        Sum: 1.624s 99% C.I. 231.171us-2751.250us Avg: 740.220us Max: 6961us
ForwardSoftReferences:  Sum: 1.071s 99% C.I. 215.113us-2175.625us Avg: 488.362us Max: 7441us
ReclaimPhase:   Sum: 490.854ms 99% C.I. 32.029us-6373.807us Avg: 223.623us Max: 362851us
EmptyRBMarkBitStack:    Sum: 479.736ms 99% C.I. 11us-3202.500us Avg: 218.558us Max: 13652us
CopyingPhase:   Sum: 399.163ms 99% C.I. 24us-4602.500us Avg: 181.851us Max: 22865us
ThreadListFlip: Sum: 295.609ms 99% C.I. 15us-2134.999us Avg: 134.673us Max: 13578us
ResumeRunnableThreads:  Sum: 238.329ms 99% C.I. 5us-2351.250us Avg: 108.578us Max: 10539us
ResumeOtherThreads:     Sum: 207.915ms 99% C.I. 1.072us-3602.499us Avg: 94.722us Max: 14179us
RecordFree:     Sum: 188.009ms 99% C.I. 64us-312.812us Avg: 85.653us Max: 2709us
MarkZygoteLargeObjects: Sum: 133.301ms 99% C.I. 12us-734.999us Avg: 60.729us Max: 10169us
MarkStackAsLive:        Sum: 127.554ms 99% C.I. 13us-417.083us Avg: 58.111us Max: 1728us
FlipThreadRoots:        Sum: 126.119ms 99% C.I. 1.028us-3202.499us Avg: 57.457us Max: 11412us
SweepAllocSpace:        Sum: 117.761ms 99% C.I. 24us-400.624us Avg: 53.649us Max: 1541us
SwapBitmaps:    Sum: 56.301ms 99% C.I. 10us-125.312us Avg: 25.649us Max: 1475us
(Paused)GrayAllNewlyDirtyImmuneObjects: Sum: 33.047ms 99% C.I. 9us-49.931us Avg: 15.055us Max: 72us
(Paused)SetFromSpace:   Sum: 11.651ms 99% C.I. 2us-49.772us Avg: 5.307us Max: 71us
(Paused)FlipCallback:   Sum: 7.693ms 99% C.I. 2us-32us Avg: 3.504us Max: 32us
(Paused)ClearCards:     Sum: 6.371ms 99% C.I. 250ns-49753ns Avg: 207ns Max: 188000ns
Sweep:  Sum: 5.793ms 99% C.I. 1us-49.818us Avg: 2.639us Max: 93us
UnBindBitmaps:  Sum: 5.255ms 99% C.I. 1us-31us Avg: 2.394us Max: 31us
Done Dumping histograms
concurrent copying paused:      Sum: 315.249ms 99% C.I. 49us-1378.125us Avg: 143.621us Max: 7722us
concurrent copying freed-bytes: Avg: 34MB Max: 54MB Min: 2062KB
Freed-bytes histogram: 0:4,5120:5,10240:19,15360:69,20480:167,25600:364,30720:529,35840:405,40960:284,46080:311,51200:38
concurrent copying total time: 569.947s mean time: 259.657ms
concurrent copying freed: 1453160493 objects with total size 74GB
concurrent copying throughput: 2.54964e+06/s / 134MB/s  per cpu-time: 157655668/s / 150MB/s
Average major GC reclaim bytes ratio 0.486928 over 2195 GC cycles
Average major GC copied live bytes ratio 0.0894662 over 2199 major GCs
Cumulative bytes moved 6586367960
Cumulative objects moved 127490240
Peak regions allocated 376 (94MB) / 2048 (512MB)
Start Dumping histograms for 685 iterations for young concurrent copying
ScanCardsForSpace:      Sum: 26.288s 99% C.I. 8.617ms-77.759ms Avg: 38.377ms Max: 432.991ms
ProcessMarkStack:       Sum: 21.829s 99% C.I. 2.116ms-71.119ms Avg: 31.868ms Max: 98.679ms
ClearFromSpace: Sum: 19.420s 99% C.I. 5.480ms-50.293ms Avg: 28.351ms Max: 507.330ms
ScanImmuneSpaces:       Sum: 9.968s 99% C.I. 8.155ms-30.639ms Avg: 14.552ms Max: 46.676ms
SweepSystemWeaks:       Sum: 6.741s 99% C.I. 3.655ms-14.715ms Avg: 9.841ms Max: 22.142ms
GrayAllDirtyImmuneObjects:      Sum: 4.466s 99% C.I. 0.584ms-14.315ms Avg: 6.519ms Max: 24.355ms
FlipOtherThreads:       Sum: 3.672s 99% C.I. 0.631ms-16.630ms Avg: 5.361ms Max: 18.513ms
ProcessReferences:      Sum: 2.806s 99% C.I. 0.001ms-9.459ms Avg: 2.048ms Max: 11.951ms
EnqueueFinalizerReferences:     Sum: 1.857s 99% C.I. 0.424ms-8.609ms Avg: 2.711ms Max: 24.063ms
VisitConcurrentRoots:   Sum: 1.094s 99% C.I. 1.306ms-5.357ms Avg: 1.598ms Max: 6.831ms
SweepArray:     Sum: 711.032ms 99% C.I. 0.022ms-3.502ms Avg: 1.038ms Max: 7.307ms
InitializePhase:        Sum: 667.346ms 99% C.I. 303us-2643.749us Avg: 974.227us Max: 3199us
VisitNonThreadRoots:    Sum: 388.145ms 99% C.I. 103.911us-1385.833us Avg: 566.635us Max: 5374us
ThreadListFlip: Sum: 202.730ms 99% C.I. 18us-2414.999us Avg: 295.956us Max: 6780us
EmptyRBMarkBitStack:    Sum: 132.934ms 99% C.I. 8us-1757.499us Avg: 194.064us Max: 8495us
ResumeRunnableThreads:  Sum: 109.593ms 99% C.I. 6us-4719.999us Avg: 159.989us Max: 11106us
ResumeOtherThreads:     Sum: 86.733ms 99% C.I. 3us-4114.999us Avg: 126.617us Max: 19332us
ForwardSoftReferences:  Sum: 69.686ms 99% C.I. 14us-2014.999us Avg: 101.731us Max: 4723us
RecordFree:     Sum: 58.889ms 99% C.I. 0.500us-185.833us Avg: 42.984us Max: 769us
FlipThreadRoots:        Sum: 58.540ms 99% C.I. 1.034us-4314.999us Avg: 85.459us Max: 10224us
CopyingPhase:   Sum: 52.227ms 99% C.I. 26us-728.749us Avg: 76.243us Max: 2060us
ReclaimPhase:   Sum: 37.207ms 99% C.I. 7us-2322.499us Avg: 54.316us Max: 3826us
(Paused)GrayAllNewlyDirtyImmuneObjects: Sum: 23.859ms 99% C.I. 11us-98.917us Avg: 34.830us Max: 128us
FreeList:       Sum: 20.376ms 99% C.I. 2us-188.875us Avg: 29.573us Max: 998us
MarkZygoteLargeObjects: Sum: 18.970ms 99% C.I. 4us-115.749us Avg: 27.693us Max: 122us
(Paused)SetFromSpace:   Sum: 12.331ms 99% C.I. 3us-94.226us Avg: 18.001us Max: 109us
SwapBitmaps:    Sum: 11.761ms 99% C.I. 5us-49.968us Avg: 17.169us Max: 67us
ResetStack:     Sum: 4.317ms 99% C.I. 1us-64.374us Avg: 6.302us Max: 190us
UnBindBitmaps:  Sum: 3.803ms 99% C.I. 4us-49.822us Avg: 5.551us Max: 70us
(Paused)ClearCards:     Sum: 3.336ms 99% C.I. 250ns-7000ns Avg: 347ns Max: 7000ns
(Paused)FlipCallback:   Sum: 3.082ms 99% C.I. 1us-30us Avg: 4.499us Max: 30us
Done Dumping histograms
young concurrent copying paused:        Sum: 229.314ms 99% C.I. 37us-2287.499us Avg: 334.764us Max: 6850us
young concurrent copying freed-bytes: Avg: 44MB Max: 50MB Min: 9132KB
Freed-bytes histogram: 5120:1,15360:1,20480:6,25600:1,30720:1,35840:9,40960:235,46080:427,51200:4
young concurrent copying total time: 100.823s mean time: 147.187ms
young concurrent copying freed: 519927309 objects with total size 30GB
young concurrent copying throughput: 5.15683e+06/s / 304MB/s  per cpu-time: 333152554/s / 317MB/s
Average minor GC reclaim bytes ratio 0.52381 over 685 GC cycles
Average minor GC copied live bytes ratio 0.0512109 over 685 minor GCs
Cumulative bytes moved 1542000944
Cumulative objects moved 28393168
Peak regions allocated 376 (94MB) / 2048 (512MB)
Total time spent in GC: 670.771s
Mean GC size throughput: 159MB/s per cpu-time: 177MB/s
Mean GC object throughput: 2.94152e+06 objects/s
Total number of allocations 1974199562
Total bytes allocated 104GB
Total bytes freed 104GB
Free memory 10MB
Free memory until GC 10MB
Free memory until OOME 442MB
Total memory 80MB
Max memory 512MB
Zygote space size 2780KB
Total mutator paused time: 544.563ms
Total time waiting for GC to complete: 117.494ms
Total GC count: 2880
Total GC time: 670.771s
Total blocking GC count: 1
Total blocking GC time: 86.373ms
Histogram of GC count per 10000 ms: 0:259879,1:2828,2:24,3:1
Histogram of blocking GC count per 10000 ms: 0:262731,1:1
Native bytes total: 30599192 registered: 8947416
Total native bytes at last GC: 30344912

Các công cụ để phân tích vấn đề về độ chính xác của GC

Có nhiều yếu tố có thể gây ra sự cố bên trong ART. Các sự cố xảy ra khi đọc hoặc ghi vào các trường đối tượng có thể cho biết vùng nhớ khối xếp bị hỏng. Nếu GC gặp sự cố khi đang chạy, thì điều này cũng có thể chỉ ra sự cố hỏng vùng nhớ khối xếp. Nguyên nhân phổ biến nhất gây ra sự cố hỏng vùng nhớ khối xếp là do mã ứng dụng không chính xác. Rất may, có các công cụ để gỡ lỗi GC và các sự cố liên quan đến vùng nhớ khối xếp, bao gồm cả các tuỳ chọn xác minh vùng nhớ khối xếp được chỉ định ở trên và CheckJNI.

CheckJNI

CheckJNI là một chế độ thêm các bước kiểm tra JNI để xác minh hành vi của ứng dụng; các bước này không được bật theo mặc định vì lý do hiệu suất. Các bước kiểm tra này phát hiện một số lỗi có thể gây hỏng vùng nhớ khối xếp, chẳng hạn như sử dụng các tệp tham chiếu cục bộ và toàn cục không hợp lệ/lỗi thời. Cách bật CheckJNI:

adb shell setprop dalvik.vm.checkjni true

Chế độ buộc sao chép của CheckJNI rất hữu ích để phát hiện các hoạt động ghi vượt quá cuối vùng mảng. Khi được bật, forcecopy sẽ khiến các hàm JNI truy cập vào mảng trả về các bản sao có vùng màu đỏ. Vùng màu đỏ là một vùng ở cuối/đầu con trỏ được trả về có giá trị đặc biệt, được xác minh khi mảng được phát hành. Nếu các giá trị trong vùng màu đỏ không khớp với giá trị dự kiến, thì đã xảy ra tình trạng vượt quá bộ đệm hoặc thiếu bộ đệm. Điều này khiến CheckJNI huỷ. Cách bật chế độ forcecopy:

adb shell setprop dalvik.vm.jniopts forcecopy

Ví dụ về lỗi mà CheckJNI sẽ phát hiện là ghi vượt quá cuối mảng nhận được từ GetPrimitiveArrayCritical. Thao tác này có thể làm hỏng vùng nhớ khối xếp Java. Nếu hoạt động ghi nằm trong khu vực CheckJNI màu đỏ, thì CheckJNI sẽ phát hiện vấn đề khi ReleasePrimitiveArrayCritical tương ứng được gọi. Nếu không, thao tác ghi sẽ làm hỏng một số đối tượng ngẫu nhiên trong vùng nhớ khối xếp Java và có thể gây ra sự cố GC trong tương lai. Nếu bộ nhớ bị hỏng là một trường tham chiếu, thì GC có thể phát hiện lỗi và in lỗi Tried to mark <ptr> not contained by any spaces (Đã cố gắng đánh dấu <ptr> không chứa bất kỳ khoảng trắng nào).

Lỗi này xảy ra khi GC cố gắng đánh dấu một đối tượng mà không thể tìm thấy không gian cho đối tượng đó. Sau khi quy trình kiểm tra này không thành công, GC sẽ duyệt qua các thư mục gốc và cố gắng xem liệu đối tượng không hợp lệ có phải là thư mục gốc hay không. Từ đây, bạn có hai lựa chọn: đối tượng là đối tượng gốc hoặc đối tượng không phải gốc.

Ví dụ về thư mục gốc không hợp lệ

Trong trường hợp đối tượng là một thư mục gốc không hợp lệ, đối tượng đó sẽ in một số thông tin hữu ích: art E 5955 5955 art/runtime/gc/collector/mark_sweep.cc:383] Tried to mark 0x2 not contained by any spaces

art E  5955  5955 art/runtime/gc/collector/mark_sweep.cc:384] Attempting see if
it's a bad root
art E  5955  5955 art/runtime/gc/collector/mark_sweep.cc:485] Found invalid
root: 0x2
art E  5955  5955 art/runtime/gc/collector/mark_sweep.cc:486]
Type=RootJavaFrame thread_id=1 location=Visiting method 'java.lang.Object
com.google.gwt.collections.JavaReadableJsArray.get(int)' at dex PC 0x0002
(native PC 0xf19609d9) vreg=1

Trong trường hợp này, vreg=1 bên trong com.google.gwt.collections.JavaReadableJsArray.get phải chứa một tệp tham chiếu vùng nhớ khối xếp, nhưng lại chứa một con trỏ không hợp lệ của địa chỉ 0x2. Đây là thư mục gốc không hợp lệ. Để gỡ lỗi vấn đề này, hãy sử dụng oatdump trên tệp oat và xem phương thức có gốc không hợp lệ. Trong trường hợp này, lỗi này hóa ra là lỗi trình biên dịch trong phần phụ trợ x86. Dưới đây là danh sách thay đổi đã khắc phục vấn đề: https://android-review.googlesource.com/#/c/133932/

Ví dụ về đối tượng bị hỏng

Nếu đối tượng không phải là gốc, kết quả sẽ tương tự như các kết quả in sau:

01-15 12:38:00.196  1217  1238 E art     : Attempting see if it's a bad root
01-15 12:38:00.196  1217  1238 F art     :
art/runtime/gc/collector/mark_sweep.cc:381] Can't mark invalid object

Khi sự cố hỏng vùng nhớ khối xếp không phải là nguyên nhân gốc không hợp lệ, bạn sẽ khó gỡ lỗi. Thông báo lỗi này cho biết ít nhất có một đối tượng trong vùng nhớ khối xếp đang trỏ đến đối tượng không hợp lệ.