DEV Community

SameX
SameX

Posted on

Memory Management and GC Practices in Multi-threaded Applications on HarmonyOS Next

This article aims to deeply explore the technical details of the Huawei HarmonyOS Next system (up to API 12 as of now), and is summarized based on actual development practices.
It mainly serves as a vehicle for technical sharing and communication. Mistakes and omissions are inevitable. Colleagues are welcome to put forward valuable opinions and questions so that we can make progress together.
This article is original content, and any form of reprint must indicate the source and the original author.

When building high-performance applications, especially in scenarios that require handling a large number of concurrent tasks, a multi-threaded architecture is of particular importance. Huawei HarmonyOS Next provides powerful multi-threaded processing and memory management mechanisms. This article will, in combination with the ArkTS programming language and the GC (garbage collection) features of the HarmonyOS system, conduct an in-depth exploration of how to design and optimize memory management and garbage collection strategies in multi-threaded applications.

Project Background

We will build a scenario that simulates a large multi-threaded application, with tasks including real-time data collection and processing. This application needs to efficiently handle a large number of concurrent tasks and ensure that memory management and GC in a multi-threaded environment do not have an adverse impact on performance.

Architecture Design

1. Multi-threaded Architecture Design

In ArkTS, multi-threading design can be achieved through Worker and thread pools. The following is the basic design of the multi-threaded architecture in ArkTS:

  • Task Allocation: Assign data collection and processing tasks to multiple Worker threads, with each thread handling an independent task.
  • Inter-thread Communication: Achieve data synchronization and communication between threads through a message passing mechanism.
  • Concurrent Processing: Use Promise, async/await to ensure the orderly execution of concurrent tasks.

Code Example: Creating a Worker Thread and Distributing Tasks

@Entry
@Component
struct MainWorkerComponent {
    build() {
        // Create a worker instance
        let worker = new Worker('worker.js');

        // Listen for the message return of the worker
        worker.onMessage = (message) => {
            console.info("Main thread received:", message.data);
        };

        // Send task data to the worker
        worker.postMessage({ task: 'processData', data: someLargeData });
    }
}
Enter fullscreen mode Exit fullscreen mode

In worker.js, the logic for multi-threaded processing is defined:

// The worker receives the message from the main thread
onmessage = function(event) {
    let data = event.data;

    if (data.task === 'processData') {
        let result = processData(data.data);
        // Return the processing result
        postMessage({ result });
    }
};

function processData(data) {
    // Simulate the data processing logic
    return data.map(item => item * 2); 
}
Enter fullscreen mode Exit fullscreen mode

2. Thread Pool Management

To efficiently manage multi-threaded tasks, we can introduce a thread pool to control the number of threads and avoid resource waste caused by excessive creation and destruction of threads. In HarmonyOS, the size of the thread pool can be dynamically adjusted according to the complexity of the task and system resources.

Code Example: Using a Thread Pool to Execute Tasks

class ThreadPool {
    constructor(public maxThreads: number) {
        this.pool = [];
    }

    // Start a new thread
    runTask(task) {
        if (this.pool.length < this.maxThreads) {
            let worker = new Worker('worker.js');
            this.pool.push(worker);
            worker.onMessage = (message) => {
                console.info("Task completed:", message.data);
                this.releaseWorker(worker);
            };
            worker.postMessage({ task });
        } else {
            console.info("All threads are busy, retrying...");
            setTimeout(() => this.runTask(task), 1000);
        }
    }

    // Release the thread
    releaseWorker(worker) {
        this.pool = this.pool.filter(w => w!== worker);
        worker.terminate();
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Task Scheduling and Distribution

In multi-threaded applications, efficient task scheduling is crucial. By designing a task priority queue and task distribution strategy, resource conflicts can be avoided and high-priority tasks can be ensured to be processed in a timely manner.

Memory Management Strategies

1. Memory Allocation in the Young and Old Generations

In the GC mechanism of the HarmonyOS system, memory is divided into the young generation and the old generation. The young generation is used to store short-lived objects, while the old generation is used to store long-lived objects. We can optimize memory usage and recycling efficiency by reasonably allocating objects to different generations.

  • Young Generation (SemiSpace): Stores short-lived objects and uses the copying algorithm.
  • Old Generation (OldSpace): Stores long-lived objects and uses a hybrid algorithm.

Table: Memory Generations and Recycling Algorithms

Generation Type Object Type Algorithm Used Characteristics
Young Generation (SemiSpace) Short-lived Objects Copying High recycling frequency, mainly for newly allocated objects
Old Generation (OldSpace) Long-lived Objects Mark-Sweep-Compact High survival rate, lower GC frequency
Large Objects (HugeObject) Large Objects Special Handling Independent space, reduced movement overhead

2. GC Optimization Strategies

In a multi-threaded environment, frequent triggering of GC can affect the performance of the application. Therefore, we need to optimize GC. By adjusting parameters such as the number of GC threads (gcThreadNum) and the heap size (HeapSize), the impact of GC on performance can be effectively reduced.

{
  "gc-options": {
    "gcThreadNum": 8,         // Allocate more GC threads
    "heapSize": 1024          // Increase the heap size
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Using Smart GC

Smart GC is an optimization mechanism in the HarmonyOS system. It can delay garbage collection in performance-sensitive scenarios (such as UI operations, animations) to ensure smooth user operations. We can combine the capabilities of Smart GC in the application to avoid UI stuttering caused by frequent GC.

Code Example: Using Smart GC to Delay Recycling

ArkTools.hintGC(); // Manually prompt the system to perform GC, but only trigger it in appropriate scenarios
Enter fullscreen mode Exit fullscreen mode

Case Practice

1. Memory Monitoring and Debugging

Through memory snapshots and GC logs, we can monitor the memory usage and perform optimization. The HarmonyOS system provides detailed GC logs to help developers identify memory leaks and unnecessary memory occupation.

Example of GC Log:

[gc] [ HPP YoungGC ] 32.1176 (35) -> 12.1005 (10.5) MB, 160ms
[gc] [ CompressGC ] 48.2131 (50) -> 18.2004 (15.8) MB, 220ms
Enter fullscreen mode Exit fullscreen mode

2. Code Implementation of Garbage Collection

We can manually trigger garbage collection in ArkTS through ArkTools and combine logs to debug the memory occupation of the application.

@Entry
@Component
struct TriggerGCComponent {
    build() {
        Button("Trigger GC")
       .onClick(() => {
            ArkTools.hintGC();
            console.info("Manual GC triggered");
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Architecture Considerations

The Relationship between Memory Allocation Strategies and Application Architecture Design

When designing multi-threaded applications, memory allocation strategies are inseparable from architecture design. Short-lived objects should be allocated to the young generation as much as possible for quick recycling; while long-lived objects should be allocated to the old generation to reduce the recycling frequency. Through reasonable memory management, the overall performance of the system can be improved.

In a complex multi-threaded architecture, thread pool management, task scheduling, and memory optimization are all key links. Only by reasonably designing these modules can the stable operation and efficient memory management of the application in high-concurrency scenarios be ensured.

Top comments (0)