Cơ bản về quản lý bộ nhớ trong Java

Memory management là quá trình cấp phát cho các đối tượng mới và loại bỏ những đối tượng không còn sử dụng để tạo không gian cấp phát cho các đối tượng mới. Trong bài viết này, sẽ trình bày những khái niệm và giải thích một cách cơ bản nhất về việc cấp phát và thu gom rác bên trong Oracle JRockit JVM.

The Heap and the Nursery

Các đối tượng java nằm bên trong một vùng nhớ gọi là heap. Heap được tạo ra khi JVM start up và có thể tăng hoặc giảm kích thước khi các ứng dụng chạy. Khi heap trở nên đầy, quá trình thu gom rác (GC) sẽ chạy, lúc này các đối tượng không còn được sử dụng sẽ bị xóa đi, tạo ra khoảng trống cho các đối tượng mới trên heap.

Chú ý rằng, JVM sử dụng các bộ nhớ khác, như là các methods, thread stack, và các xử lý native được phân bố trong các vùng nhớ tách biệt với heap, cũng như các cấu trúc dữ liệu bên trong JVM. Ex:

  • code section: chứa bytecode
  • Stack section: chứa methods, local variablesreference variables
  • Heap section: chứa objects (có thể chứa cả reference variables)
  • Static section: chứa static data/methods

Heap section đôi khi được chia làm 2 vùng (hay thế hệ) gọi là Nursey (hay young space) và vùng old space.

Nursery là một phần của heap để dành cho việc cấp phát cho các đối tượng mới. Khi nursery bắt đầu đầy, rác sẽ được thu gom bằng một process thu gom rác đặc biệt gọi là young collection, nơi mà các đối tượng sống đủ lâu trong nursery được promoted và di chuyển lên vùng old space, do đó giải phóng nursery để cấp phát các đối tượng khác.

Khi old space trở nên đầy, rác sẽ được thu gom bởi process khác được gọi là old collection. Lý do đằng sau 1 inursery là hầu hết các đối tượng đều là tạm thời và ngắn hạn. Một process young collection được thiết kế để nhanh chóng tìm các đối tượng mới được cấp phát mà vẫn tồn tại (alive) và di chuyển chúng khỏi nursery. Thông thường, young collection process sẽ giải phóng một số lượng bộ nhớ nhất định nhanh hơn old collection process hay garbage collection process của một heap mà không có vùng nursery (còn gọi là single-generational heap)

Từ các version release mới của JVM, có một phần của nursery được giữ lại gọi là keep area. Vùng này sẽ chứa các đối tượng được cấp phát gần đây nhất và chưa được thu gom cho đến khi young collection process tiếp theo được chạy. Điều này sẽ ngăn cản việc đối tượng được di chuyển lên old space vì các đối tượng chỉ vừa được cấp phát ngay trước khi young collection chạy.

Object allocation

Trong quá trình cấp phát đối tượng, JVM sẽ phân biệt giữa các đối tượng nhỏ (small object) và lớn (large object). Giới hạn khi một đối tượng được xem là lớn phụ thuộc vào các yếu tố như JVM version, độ lớn heap size, chiến lược GCplatform được sử dụng, nhưng thường sự khác nhau giữa 2 loại đối tượng là khoảng 128kB.

Các đối tượng nhỏ sẽ được cấp phát trong vùng gọi là thread local areas (TLAs), TLAs là các khối rỗng (free chunks) từ heap và được giao cho một Java thread sử dụng độc quyền. Thread này có thể cấp phát các đối tượng trong TLA của nó mà không cần phải đồng bộ với các thread khác. Khi TLA bắt đầu đầy, thread chỉ đơn giản yêu cầu một TLA mới.

Các đối tượng lớn sẽ không vừa bên trong một TLA được cấp phát trực tiếp trên heap. Khi một Nursery được dùng, các đối tượng lớn sẽ được phân bố trực tiếp trên old space. Sự phân bố các đối tượng lớn yêu cầu sự đồng bộ giữa các Java thread, mặc dù JVM dùng một hệ thống cache của các khối rỗng khác kích thước để giảm nhu cầu đồng bộ và tăng tốc độ cấp phát.

Garbage collection

Là quá trình giải phóng không gian của heap hoặc nursery để cấp phát cho các đối tượng mới.

Mark and Sweep model

JRockit JVM sử dụng MarkSweep garbage collection model để thực hiện việc thu gôm rác cho toàn bộ heap. Có 2 phase chính:

  • Mark phase: tất cả các đối tượng có thể truy cập từ các Java thread, các xử lý native và các nguồn khác, cũng như các đối tượng có thể truy cập từ những đối tượng này sẽ được đánh dấu (marked) là alive. Quá trình này xác định và đánh dấu tất cả các đối tượng còn sử dụng, phần còn lại có thể coi là rác.

  • Sweep phase: Heap sẽ được quét qua để tìm những khoảng trống giữa các đối tượng còn sống. Những khoảng này sẽ được ghi lại trong một danh sách rỗng và có thể sẵn sàng để cấp phát cho các đối tượng mới.

JVM sử dụng 2 version cải tiến : Mostly concurrentparallel.

Mostly concurrent mark and sweep

Cho phép Java thread tiếp tục chạy trong phần lớn của quá trình GC. Các thread có thể phải tạm dừng ở một số thời điểm cho việc đồng bộ.

Mostly concurrent mark phase chia làm 4 phần:

  1. Initial marking: Nơi mà tập gốc các đối tượng còn tồn tại được xác định. Lúc này các Java thread sẽ bị tạm dừng.
  2. Concurrent marking: Nơi các tham khảo (references) đến tập đối tượng gốc theo sau để tìm và đánh dấu phần còn lại của các live object trong heap. Lúc này các Java thread vẫn chạy.
  3. Precleaning: Nơi các thay đổi bên trong heap suốt giai đoạn Concurrent marking được xác định và bất kì live object bổ sung được tìm thấy và đánh dấu. Lúc này các Java thread vẫn chạy.
  4. Final marking: Nơi các thay đổi trong suốt giai đoạn Precleaning được xác định và bất kì live objects bổ sung được tìm thấy và đánh dấu. Lúc này các Java thread sẽ bị tạm dừng.

Mostly concurrent sweep phase cũng chia làm 4 phần:

  1. Sweep một nửa heap. Quá trình này được thực hiện trong khi các java thread vẫn được chạy và được cho phép cấp phát các đối tượng trong phần heap chưa được quét.
  2. Khoảng tạm dừng ngắn để chuyển sang nửa còn lại của heap.
  3. Sweep nửa còn lại. Quá trình này được thực hiện trong khi các java thread vẫn được chạy và được cho phép cấp phát các đối tượng trong phần heap đã được quét.
  4. Khoảng tạm dừng ngắn để đồng bộ và ghi lại thống kê.

Parallel mark and sweep

Chiến lược này còn được gọi là parallel garbage collector dùng toàn bộ CPUs sẵn có trong hệ thống để thực hiện việc thu gom rác càng nhanh càng tốt. Tất cả các java thread bị tàm dừng trong quá trình này.

Generational Garbage collection

Như trình bày ở trên, Nursery nếu tồn tại, rác được thu gom bởi process đặc biệt là young collection. Chiến lược GC sử dụng Nursery gọi là generational garbage collection strategy

Young collector dùng trong JRockit JVM xác định và di chuyển tất cả các đối tượng sống trong Nursery mà nằm ngoài keep area lên khu vực old space. Công việc này được thực hiện song song bằng tất cả CPUs hiện có. Java thread sẽ tạm dừng khi quá trình này thực hiện.

Compaction

Các đối tượng được cấp phát nằm cạnh nhau sẽ không nhất thiết trở nên không thể truy cập cùng lúc. Điều này có nghĩa là heap có thể bị phân mảnh sau quá trình thu gom rác, do đó những không gian trống trong heap rất nhiều nhưng nhỏ, khiến cho việc cấp phát cho các đối tượng lớn khó khăn, thậm chí không thể. Các không gian trống nhỏ hơn kích thước tối thiểu của TLA không thể sử dụng, garbage collector sẽ loại bỏ chúng dưới dạng dark matter đến khi các vùng nhớ xung quanh nó được giải phóng và không gian đủ lớn để tạo một TLA.

Để giảm việc phân mảnh, JRockit JVM sẽ nén một phần của heap mỗi khi quá trình thu gom rác (old collection). Compaction process sẽ di chuyển các đối tượng lại gần nhau hơn và tiếp tục đẩy xuống dưới heap, tạo ra các vùng trống gần đỉnh heap. Kích thước và vị trí của vùng compaction được lựa chọn bằng thuộc toán heuristics, tùy thuộc vào gabage collection mode được dùng.

Compaction được thực hiện lúc bắt đầu hoặc trong suốt giai đoạn sweep phase và trong lúc này tất cả các Java thread sẽ tạm dừng.

References: https://www.guru99.com/java-stack-heap.html https://docs.oracle.com/cd/E13150_01/jrockit_jvm/jrockit/geninfo/diagnos/garbage_collect.html