12/08/2018, 00:28

Android App Performance - Part2: Analyze and Optimizing Memory

Phần 1 Managing Memory đã giới thiệu khái niệm về cách thức HĐH Android quản lý bộ nhớ cũng như một số lưu ý khi xây dựng, triển khai ứng dụng tránh lãng phí tài nguyên, đảm bảo hiệu năng ứng dụng cũng như hiệu năng chung toàn hệ thống. Trong phần 2 này sẽ giới thiệu, tìm hiểu các đo đạc, phân ...

Phần 1 Managing Memory đã giới thiệu khái niệm về cách thức HĐH Android quản lý bộ nhớ cũng như một số lưu ý khi xây dựng, triển khai ứng dụng tránh lãng phí tài nguyên, đảm bảo hiệu năng ứng dụng cũng như hiệu năng chung toàn hệ thống.

Trong phần 2 này sẽ giới thiệu, tìm hiểu các đo đạc, phân tích bộ nhớ.

I. Phân tích log messages

Cách đơn giản nhất để theo dõi bộ nhớ đang được sử dụng của ứng dụng là thông qua run time log messages. Khi trình dọn rác GC làm việc một messages sẽ được in ra logcat, logcat có sẵn ngay trong Device Monitor Tool trong bộ Android SDK hoặc trong IDE như Eclipse hay Android Studio.

Dalvik Log Messages

Mỗi khi GC được gọi, một Dalvik log message được xuất ra, message có cấu trúc như sau:

D/dalvikvm: <GC_Reason> <Amount_freed>, <Heap_stats>, <External_memory_stats>, <Pause_time>

Ví dụ một message như sau:

D/dalvikvm(6969): GC_CONCURRENT freed 2069K, 69% free 3771K/9991K, external 4703K/5261K, paused 2ms+2ms

Trong đó

GC Reason: giá trị cho biết kiểu, trạng thái bộ nhớ hay ngắt mà khi đó GC được gọi.

  • GC_CONCURRENT: GC được gọi khi bộ nhớ heap vượt quá một giá trị nào đó.
  • GC_FOR_MALLOC: GC được gọi khi bộ nhớ không đủ để cấp phát cho một đối tượng mới.
  • GC_HPROF_DUMP_HEAP: GC được gọi khi có yêu cầu tạo ra một tập tin HPROF phân tích heap.
  • GC_EXPLICIT: Khi GC được gọi thông quá method *.gc() như Runtime.gc(), VMRuntime.gc(), SIGUSR1..
  • GC_EXTERNAL_ALLOC: Chỉ có trong các API nhỏ hơn 11 - GC được gọi cho bộ nhớ ngoài.

Amount freed: Giá trị bộ nhớ được giải phóng sau khi GC được gọi.

Heap stats: Tỉ lệ phân trăm bộ nhớ heap hiện chưa được sử dụng và giá trị (bộ nhớ cho các đối tượng đang sử dụng) / (tổng bộ nhớ heap).

External memory stats: giá trị bộ nhớ ngoài đang được sử dụng (giá trị bộ nhớ ngoài hiện tại đang sử dụng / ngưỡng giá trị mà tại đó GC được gọi). Chỉ có trong các API nhỏ hơn 11.

Pause time: thời gian tạm dừng khi GC làm việc, 1 giá trị tại khi bắt đầu quá trình GC và 1 giá trị khi GC gần kết thúc.

Trong ví dụ trên, nếu giá trị Heap stats tiếp tục tăng lên khi ứng dụng đang chạy chứng tỏ bộ nhớ heap đang được sử dụng cho việc cấp phát đối tượng mới hoặc ứng dụng đang xảy ra hiện tượng leak bộ nhớ.

ART Log Messages

ART log message chỉ được in ra khi quá trình GC tạm dừng quá 5ms hoặc tổng thời gian GC vượt quá 100ms. Khi đó quá trình GC được coi là chậm và message được in ra với cấu trúc như sau:

I/art: <GC_Reason> <GC_Name> <Objects_freed>(<Size_freed>) AllocSpace Objects, <Large_objects_freed>(<Large_object_size_freed>) <Heap_stats> LOS objects, <Pause_time(s)>

Ví dụ như:

I/art : Explicit concurrent mark sweep GC freed 104710(7MB) AllocSpace objects, 21(416KB) LOS objects, 33% free, 25MB/38MB, paused 1.230ms total 67.216ms

Trong đó: GC_Reason:

  • Concurrent: Quá trình GC chạy trong một tiến trình nền song song, GC không ngăn cản hay tạm dừng việc cấp phát bộ nhớ của ứng dụng.

  • Alloc: GC hoạt động bởi vì ứng dụng đang cố gắng cấp phát bộ nhớ khi heap đầy. Trong trường hợp này, GC chạy trong tiến trình ứng dụng (tiến trình cấp phát bộ nhớ).

  • Explicit: GC được gọi bằng một method trong ứng dụng, ví dụ System.gc () hoặc Runtime.gc (). Cũng giống như với máy ảo Dalvik, nên tránh việc gọi trực tiếp GC ART trong tiến trình ứng dụng.

  • NativeAlloc: GC được gọi khi bộ nhớ không đủ để cấp phát cho các tài nguyên như file ảnh hoặc đối tượng RenderScript.

  • CollectorTransition: GC được gọi trong một quá trình chuyển đổi heap. Quá trình chuyển đổi chỉ xảy ra khi tiến trình ứng dụng chuyển đổi trạng thái từ tạm dừng sang hoạt động trên các thiết bị có bộ nhớ RAM thấp.

  • HomogeneousSpaceCompact: GC được gọi khi HĐH nén dữ liệu trên bộ nhớ của các tiến trình đang trong trạng thái dừng hoạt động. Điều này làm giảm bộ nhớ RAM được sử dụng và chống phân mảnh heap.

  • HeapTrim: GC đang trong trạng thái block tới khi kết thúc.

GC Name: tên của các trình dọn rác GC trong ART.

  • Concurrent mark sweep (CMS): GC chạy song song quét toàn bộ bộ nhớ heap ngoại trừ vùng bộ nhớ cấp phát cho hình ảnh.

  • Concurrent partial mark sweep: GC chạy song song quét toàn bộ bộ nhớ heap ngoại trừ vùng bộ nhớ cấp phát cho hình ảnh và Zygote.

  • Concurrent sticky mark sweep : GC chạy song song thu thập các đối tượng được cấp phát từ các GC trước đó. GC này thường xuyên được chạy trên toàn bộ vùng nhớ vì thời gian tạm dừng thấp hơn.

  • Marksweep + semispace: A non concurrent, copying GC used for heap transitions as well as homogeneous space compaction (to defragement the heap). GC chạy trong tiến trình ứng dụng, đồng nhất và nén dữ liệu, chống phân mảnh heap.

Objects freed: Số lượng các đối tượng đã được giải phóng, bỏ qua các đối tượng kích thước lớn.

Size freed: Giá trị bộ nhớ đã được giải phóng, bỏ qua các đối tượng kích thước lớn.

Large objects freed: Số lượng các đối tượng kích thước lớn được giải phóng.

Large object size freed: Giá trị bộ nhớ đã được giải phóng của các đối tượng kích thước lớn.

Heap stats: Tỷ lệ phần trăm bộ nhớ heap hiện chưa được sử dụng và giá trị (bộ nhớ cho các đối tượng đang sử dụng) / (tổng bộ nhớ heap).

Pause times: thời gian tạm dừng khi GC làm việc, thời gian tạm dừng tỷ lệ thuận với số lượng tham chiếu đối tượng được thay đổi trong khi GC chạy.

Nếu ứng dụng đang chạy và một lượng lớn thông tin của GC được in ra trong logcat, giá trị tỉ lệ và tổng bộ nhớ trong heap stats tiếp tục tăng lên mà không dừng lại, khi đó ứng dụng đang leak bộ nhớ. Ngoài ra, nếu GC_Reason với giá trị Allocliên tục được in ra khi giá trị heap sắp đạt ngưỡng, khi đó một OutOfMemory Exception sẽ được tạo ra.

II. Theo dõi trạng thái heap

Android cung cấp công cụ Device Monitor trong thư mục tools giúp theo dõi trạng thái bộ nhớ heap và GC:

monitor-vmheap

Công cụ Device Monitor, Cập nhật Heap và GC. Tab Heap trên bên phải cho thấy thông tin về trạng thái heap.

III. Theo dõi việc cấp phát bộ nhớ

Allocation tracker cho phép theo dõi việc cấp phát bộ nhớ, từ đó xác định các vấn đề về bộ nhớ với ứng dụng, giúp tối ưu, làm mịn tổng thể ứng dụng:

monitor-tracker

Mặc dù việc quan sát này có thể không cần thiết (và cũng không thể) để xác định tất cả các hoạt động cấp phát bộ nhớ cho các đối tượng trong code nhưng theo dõi hoạt động cấp phát có thể xác định các vấn đề quan trong trong code, tránh lãng phí cũng như leak memory.

Ví dụ, một số ứng dụng có thể tạo ra một đối tượng lớn trong mỗi method. Chuyển đối tượng thành một biến global hay static là một thay đổi đơn giản giúp cải thiện hiệu suất.

IV. Theo tổng thể bộ nhớ

Có thể theo dõi, quan sát bộ nhớ ứng dụng được phân bổ trên RAM bằng cách sử dụng lệnh adb trong <sdk>/platform-tools/

adb shell dumpsys meminfo <package_name | pid> [-d]

Đối số -d để đưa thêm thông tin về Dalvik và ART.

Các giá trị số được tính bằng kilobyte.Trong đó có hai thông số quan trọng cần lưu ý:

  • Private (Clean and Dirty) RAM: Đây là bộ nhớ đang được sử dụng bởi chỉ có tiến trình ứng dụng, đồng thời cũng là lượng RAM mà hệ thống có thể giải phóng khi tiến trình ứng dụng kết thúc. Phần quan trọng nhất là "private dirty" RAM, đó là phần bộ nhớ quan trọng nhất vì nó được sử dụng bởi tiến trình và context của nó chỉ tồn tại trong bộ nhớ RAM vì vậy không thể đánh số trang để lưu trữ (Android không sử dụng swap). Dalvik code và navite heap sẽ được cấp phát trong dirty RAM, Dalvik code và native resource chia sẻ chung bộ nhớ với Zygote trong share dirty RAM.

  • Proportional Set Size (PSS): Proportional Set Size: xác định số page mà tiến trình đang chiếm giữ (cả private page và share page). Thông số này giúp tính toán xem giá trị thực sự của bộ nhớ mà tiến trình chiếm giữ.

Ví dụ:

adb shell dumpsys meminfo com.google.android.apps.maps -d

Google Maps dump

Có rất nhiều thông tin ở đây, những thông tin chính được liệt kê dưới đây.

  • Dalvik Heap: RAM được sử dụng bởi Dalvik trong ứng dụng.

  • .so mmap và .dex mmap Bộ nhớ RAM được sử dụng bởi mmapped: .so (native) và .dex (Dalvik hoặc ART) code.

  • .oat mmap Giá trị RAM được cấp phát cho code image - những mã nguồn sử dụng chung giữa nhiều ứng dụng.

  • .art mmap Giá trị RAM được cấp phát cho heap image - phần heap sử dụng chung giữa nhiều ứng dụng.

  • .Heap (với tham số -d) Giá trị heap cho ứng dụng, không bao gồm các đối tượng kích thước lớn và image, nhưng bao gồm bộ nhớ cho Zygote và static.

  • .LOS (với tham số -d) Giá trị bộ nhớ cấp phát cho các đối tượng kích thước lớn (>12KB) của ART.

  • .GC (với tham số -d) Giá trị bộ nhớ được GC sử dụng.

  • .Zygote (với tham số -d) Lượng bộ nhớ được sử dụng bởi Zygote. Vùng bộ nhớ Zygote được tạo ra trong quá trình khởi động thiết bị và không được cấp phát mới..

  • .NonMoving (với tham số -d) Giá trị bộ nhớ được sử dụng bởi các giá trị static trong ART.

  • Unknown Các trang RAM mà hệ thống không thể phân loại vào một trong các mục cụ thể.

  • TOTAL Tổng các giá trị bộ nhớ, có thể được so sánh trực tiếp với các tiến trình, ứng dụng khác.

  • ViewRootImpl Số lượng viewroot đang active trong ứng dụng. Mỗi viewroot liên kết với một window như activity hay dialog, vì vậy điều này giúp xác định việc leak bộ nhớ liên quan tới UI.

  • AppContexts và Activities Số lượng Context và Activity đang active trong ứng dụng. Điều này giúp xác định nhanh việc leak bộ nhớ khi các Activity chuyển đổi trạng thái. Một đối tượng View hay Drawable giữ một tham chiếu đến các Activity mà nó được tạo ra, vì vậy giữ một đối tượng View hay Drawable cũng có thể dẫn đến ứng dụng leak bộ nhớ.

V. Dump Heap

Heap dump là một bản chụp tất cả các đối tượng trong heap của ứng dụng, lưu trữ trong một file nhị phân được gọi là HPROF.

Heap dump ứng dụng cung cấp thông tin về trạng thái tổng thể của heap trong ứng dụng, do đó có thể theo dõi những vấn đề đã được xác định trong khi theo dõi heap.

heap dump

Nếu cần chính xác hơn về thời điểm tạo ra heap dump tại các điểm quan trọng trong code ứng dụng bằng cách gọi dumpHprofData().

Điểm khác biệt giữa heap dump Android và Java dù cùng một định dạng là trong heap dump Android chứa thêm một lượng thông tin về Zygote, nhưng do bộ nhớ Zygote được chia sẻ cho tất cả các tiến trình ứng dụng nên đó không phải là vấn đề lớn khi phân tích bộ nhớ.

Để phân tích heap dump có thể dùng các công cụ tiêu chuẩn như jhat hoặc Eclipse Memory Analyzer Tool (MAT). Tuy nhiên trước hết cần chuyển đổi các tập tin từ định dạng HPROF Android sang J2SE HPROF bằng cách sử dụng công cụ hprof-conv được cung cấp sẵn trong thư mục <sdk>/platform-tools/. Đơn giản chỉ cần chạy lệnh hprof-conv với hai đối số: file HPROF gốc và vị trí để ghi các file HPROF chuyển đổi. Ví dụ như:

hprof-conv heap-original.hprof heap-converted.hprof

Lưu ý: Nếu đang sử dụng các phiên bản của DDMS đó là tích hợp vào Eclipse hay Android Studio thì không cần phải thực hiện việc chuyển đổi trên HPROF.

  • Các tham chiếu long-lived tới Activity, Context, View, Drawable, và các đối tượng khác giữ tham chiếu tới container Activity hoặc Context.
  • Các lớp inner non-static như là Runnable, có thể đang giữ một Activity instance.
  • Caches lưu giữ các đối tượng lâu hơn cần thiết.

VI. Tạo Memory Leaks

Trong khi sử dụng các công cụ giới thiệu ở trên, nên cố gắng kiểm tra và tối ưu mã nguồn bằng cách cố gắng buộc ứng dụng gây ra memory leak. Một cách để tạo memory leak trong ứng dụng của là để ứng dụng chạy trong một thời gian trước khi kiểm tra heap.

Bạn cũng có thể cố gắng tạo ra memory leak bằng một trong các cách sau đây:

  • Xoay điện thoại từ dọc sang ngang và ngược lại nhiều lần trong khi ở trạng thái hoạt động khác nhau. Xoay thiết bị thường có thể khiến ứng dụng tạo ra memory leak trong các Activity, Context, hoặc các View bởi vì hệ thống tái tạo lại các Activity và nếu ứng dụng giữ một tham chiếu đến một trong những đối tượng ở một nơi khác, GC sẽ không loại bỏ các đối tượng này khỏi bộ nhớ.

  • Chuyển đổi giữa các ứng dụng khác nhau khi đang ở trạng thái hoạt động khác nhau (nhấn Home, sau đó quay trở lại ứng dụng ...).

Một cách đơn giản hơn là sử dụng công cụ monkeyrunner có sẵn trong <skd>/tools hoặc "monkey" test framework để tạo ra các thao tác khác nhau trong quá trình sử dụng ứng dụng.

VII. Refrences

  1. Investigating Your RAM Usage - Android Developer
  2. Improving Layout Performance - Android Developer
  3. The ART of Garbage Collection - The CommonsBlog A Closer Look at Android RunTime (ART) in Android L
  4. Garbage Collection: Theory and Practice
  5. Android Runtime Performance Analysis Using New Relic: ART vs. Dalvik
0