Android Runtime直接内存管理原理深度剖析(73)

Android Runtime直接内存管理原理深度剖析

一、直接内存基础架构概述

1.1 直接内存核心概念

Android Runtime(ART)的直接内存(Direct Memory)是指通过Java NIO包中的ByteBuffer.allocateDirect()方法分配的堆外内存。与Java堆内存不同,直接内存不受JVM垃圾回收机制的直接管理,而是由操作系统负责回收。其核心优势在于:

  • 减少Java堆内存压力,避免频繁GC
  • 提升I/O操作效率,避免数据在Java堆与本地内存之间的复制
  • 支持创建超出JVM堆大小限制的内存缓冲区

1.2 直接内存架构分层

直接内存管理涉及三个主要层次:

  1. Java层 :通过java.nio.DirectByteBuffer类提供直接内存操作接口
  2. JNI层:负责Java对象与本地内存的映射和交互
  3. Runtime层:管理直接内存的分配、回收和监控

1.3 核心数据结构

直接内存管理的关键数据结构定义于art/runtime/direct_memory.h与相关文件中:

cpp 复制代码
// 表示直接内存区域的内部结构
class DirectMemoryRegion {
public:
    // 内存起始地址
    void* address_; 
    // 内存大小(字节)
    size_t size_; 
    // 分配时间戳
    time_t allocation_time_; 
    // 指向对应的Java DirectByteBuffer对象
    mirror::Object* byte_buffer_; 
    // 下一个直接内存区域(用于链表管理)
    DirectMemoryRegion* next_; 
};

// 直接内存管理器
class DirectMemoryManager {
private:
    // 总分配内存大小
    size_t total_allocated_; 
    // 最大允许分配内存大小
    size_t max_capacity_; 
    // 直接内存区域链表头节点
    DirectMemoryRegion* regions_; 
    // 保护内存操作的互斥锁
    Mutex lock_; 
public:
    // 分配直接内存
    DirectMemoryRegion* Allocate(size_t size, mirror::Object* byte_buffer); 
    // 释放直接内存
    void Free(DirectMemoryRegion* region); 
    // 获取当前分配状态
    size_t GetTotalAllocated() const; 
    // 检查是否超过内存限制
    bool IsOverLimit() const; 
};

二、直接内存分配流程

2.1 Java层分配接口

Java代码通过ByteBuffer.allocateDirect()方法触发直接内存分配:

java 复制代码
// java.nio.ByteBuffer类
public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}

// java.nio.DirectByteBuffer构造函数
DirectByteBuffer(int cap) {
    super(-1, 0, cap, cap);
    boolean pa = VM.isDirectMemoryPageAligned();
    int ps = Bits.pageSize();
    long size = Math.max(1L, (long)cap + (pa ? ps : 0));
    // 检查直接内存是否足够
    Bits.reserveMemory(size, cap);
    // 通过JNI调用本地分配函数
    long base = unsafe.allocateMemory(size);
    unsafe.setMemory(base, size, (byte) 0);
    if (pa && (base % ps != 0)) {
        // 内存页对齐处理
        address = base + ps - (base & (ps - 1));
    } else {
        address = base;
    }
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    att = null;
}

2.2 本地内存分配实现

JNI层通过art/runtime/native/java_nio_DirectByteBuffer.cc实现内存分配:

cpp 复制代码
// 本地内存分配函数
static jlong DirectByteBuffer_allocateMemory(JNIEnv* env, jclass, jlong size) {
    // 调用操作系统内存分配函数
    void* address = malloc(size);
    if (address == nullptr) {
        // 内存分配失败,抛出异常
        ThrowOutOfMemoryError(env, "Failed to allocate %" PRId64 " bytes", size);
        return 0;
    }
    // 记录内存分配信息
    DirectMemoryManager::Current()->RecordAllocation(address, size);
    return reinterpret_cast<jlong>(address);
}

2.3 内存页对齐处理

为提升内存访问效率,直接内存通常需要页对齐:

cpp 复制代码
// 内存页对齐分配函数
void* AllocateAlignedMemory(size_t size, size_t alignment) {
    void* ptr;
    // 使用posix_memalign进行页对齐分配
    int result = posix_memalign(&ptr, alignment, size);
    if (result != 0) {
        return nullptr;
    }
    return ptr;
}

页对齐的内存分配可能会导致额外的内存开销,但能显著提升内存访问性能。

三、直接内存回收机制

3.1 Cleaner机制

Java通过sun.misc.Cleaner类实现直接内存的回收:

java 复制代码
// Cleaner类关键方法
public void clean() {
    if (remove(this)) {
        try {
            // 调用清理器的run方法
            if (thunk != null) {
                thunk.run();
            }
        } catch (final Throwable x) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    if (System.err != null)
                        new Error("Cleaner terminated abnormally", x)
                            .printStackTrace();
                    System.exit(1);
                    return null;
                }
            });
        }
    }
}

// DirectByteBuffer的Deallocator内部类
private static class Deallocator implements Runnable {
    private long address;
    private long size;
    private int capacity;
    
    public void run() {
        if (address == 0) {
            return;
        }
        // 通过JNI调用本地释放函数
        unsafe.freeMemory(address);
        address = 0;
        // 更新直接内存统计信息
        Bits.unreserveMemory(size, capacity);
    }
}

3.2 本地内存释放实现

JNI层通过art/runtime/native/java_nio_DirectByteBuffer.cc实现内存释放:

cpp 复制代码
// 本地内存释放函数
static void DirectByteBuffer_freeMemory(JNIEnv* env, jclass, jlong address) {
    if (address == 0) {
        return;
    }
    void* ptr = reinterpret_cast<void*>(address);
    // 更新直接内存统计信息
    DirectMemoryManager::Current()->RecordFree(ptr);
    // 调用操作系统内存释放函数
    free(ptr);
}

3.3 内存回收触发时机

直接内存的回收主要通过以下方式触发:

  1. GC触发:当DirectByteBuffer对象被垃圾回收时,其关联的Cleaner会被调用
  2. 主动调用 :开发者可通过调用Cleaner.clean()方法主动触发内存回收
  3. 内存压力:当直接内存使用接近上限时,系统会尝试触发回收

四、直接内存限制与监控

4.1 内存限制配置

直接内存的最大容量由JVM参数控制,默认值为64MB:

cpp 复制代码
// 初始化直接内存管理器
void DirectMemoryManager::Init() {
    // 从系统属性获取最大直接内存大小
    std::string max_direct_size_str = GetSystemProperty("sun.nio.MaxDirectMemorySize");
    if (!max_direct_size_str.empty()) {
        max_capacity_ = ParseSize(max_direct_size_str);
    } else {
        // 默认最大直接内存大小为64MB
        max_capacity_ = 64 * 1024 * 1024; 
    }
    total_allocated_ = 0;
    regions_ = nullptr;
}

4.2 内存使用监控

ART提供直接内存使用情况的监控机制:

cpp 复制代码
// 获取当前直接内存使用量
size_t DirectMemoryManager::GetTotalAllocated() const {
    MutexLock lock(Thread::Current(), lock_);
    return total_allocated_;
}

// 获取剩余可用直接内存
size_t DirectMemoryManager::GetRemainingCapacity() const {
    MutexLock lock(Thread::Current(), lock_);
    return max_capacity_ - total_allocated_;
}

// 记录内存分配
void DirectMemoryManager::RecordAllocation(void* address, size_t size) {
    MutexLock lock(Thread::Current(), lock_);
    // 检查是否超过限制
    if (total_allocated_ + size > max_capacity_) {
        // 尝试触发GC回收内存
        TriggerGC(); 
        // 再次检查
        if (total_allocated_ + size > max_capacity_) {
            ThrowOutOfMemoryError("Direct buffer memory");
            return;
        }
    }
    // 创建新的内存区域记录
    DirectMemoryRegion* region = new DirectMemoryRegion();
    region->address_ = address;
    region->size_ = size;
    region->allocation_time_ = time(nullptr);
    // 添加到链表
    region->next_ = regions_;
    regions_ = region;
    total_allocated_ += size;
}

4.3 内存溢出处理

当直接内存使用超过限制时,系统会抛出OutOfMemoryError

cpp 复制代码
// 检查内存是否超过限制
bool DirectMemoryManager::IsOverLimit() const {
    return total_allocated_ > max_capacity_;
}

// 抛出内存溢出异常
void DirectMemoryManager::ThrowOutOfMemoryError(const char* message) {
    Thread* self = Thread::Current();
    self->ThrowNewException("Ljava/lang/OutOfMemoryError;", message);
}

五、直接内存与垃圾回收的协作

5.1 Cleaner对象的生命周期

Cleaner对象作为PhantomReference的子类,其生命周期与垃圾回收密切相关:

cpp 复制代码
// Cleaner类继承关系
class Cleaner extends PhantomReference<Object> {
    // 清理器链表头节点
    private static Cleaner first = null;
    // 下一个清理器
    private Cleaner next = null;
    private Cleaner prev = null;
    // 清理操作
    private final Runnable thunk;
    
    // 注册新的清理器
    private static synchronized Cleaner add(Cleaner cl) {
        if (first != null) {
            cl.next = first;
            first.prev = cl;
        }
        first = cl;
        return cl;
    }
    
    // 从链表移除清理器
    private static synchronized boolean remove(Cleaner cl) {
        if (cl.next == cl) {
            return false;
        }
        if (first == cl) {
            if (cl.next != null) {
                first = cl.next;
            } else {
                first = cl.prev;
            }
        }
        if (cl.next != null) {
            cl.next.prev = cl.prev;
        }
        if (cl.prev != null) {
            cl.prev.next = cl.next;
        }
        cl.next = cl;
        cl.prev = cl;
        return true;
    }
}

5.2 垃圾回收时的内存释放

当DirectByteBuffer对象被标记为垃圾时,Cleaner会被触发:

cpp 复制代码
// GC过程中处理PhantomReference的函数
void GcHeap::ProcessPhantomReferences() {
    // 获取所有PhantomReference对象
    ObjectQueue<mirror::Object>* phantom_ref_queue = GetPhantomReferenceQueue();
    // 遍历队列
    while (!phantom_ref_queue->IsEmpty()) {
        mirror::Object* ref = phantom_ref_queue->Dequeue();
        if (IsCleaner(ref)) {
            // 对于Cleaner对象,调用clean方法
            InvokeCleanerMethod(ref);
        }
        // 处理其他PhantomReference
        //...
    }
}

// 调用Cleaner的clean方法
void InvokeCleanerMethod(mirror::Object* cleaner) {
    // 获取clean方法
    ArtMethod* clean_method = FindMethod(cleaner->GetClass(), "clean", "()V");
    if (clean_method != nullptr) {
        // 调用clean方法
        InvokeVirtualMethod(cleaner, clean_method);
    }
}

5.3 内存泄漏风险与防范

若DirectByteBuffer对象长时间存活,可能导致直接内存泄漏。防范措施包括:

  1. 及时释放不再使用的DirectByteBuffer对象引用
  2. 主动调用Cleaner.clean()方法
  3. 监控直接内存使用情况,避免过度分配

六、直接内存与JNI交互

6.1 JNI访问直接内存

JNI代码可直接访问Java层分配的直接内存:

cpp 复制代码
// JNI函数示例:读取DirectByteBuffer内容
JNIEXPORT void JNICALL Java_com_example_MyNativeClass_processDirectBuffer(
    JNIEnv* env, jclass clazz, jobject buffer) {
    // 检查是否为DirectByteBuffer
    if (!env->IsInstanceOf(buffer, env->FindClass("java/nio/DirectByteBuffer"))) {
        return;
    }
    // 获取内存地址
    void* address = env->GetDirectBufferAddress(buffer);
    if (address == nullptr) {
        return;
    }
    // 获取缓冲区大小
    jlong capacity = env->GetDirectBufferCapacity(buffer);
    // 处理内存数据
    ProcessMemory(address, capacity);
}

6.2 直接内存与本地内存的映射

在JNI层,可将直接内存与本地内存结构映射:

cpp 复制代码
// 将DirectByteBuffer映射为本地结构体
struct MyDataStructure {
    int id;
    char name[100];
    float value;
};

void MapDirectBufferToStruct(JNIEnv* env, jobject buffer) {
    void* address = env->GetDirectBufferAddress(buffer);
    if (address == nullptr) {
        return;
    }
    // 将内存地址转换为结构体指针
    MyDataStructure* data = static_cast<MyDataStructure*>(address);
    // 访问结构体成员
    LOG_INFO("ID: %d, Name: %s, Value: %f", data->id, data->name, data->value);
}

6.3 直接内存与文件I/O

直接内存常用于高效的文件I/O操作:

cpp 复制代码
// 使用直接内存进行文件写入
void WriteFileWithDirectBuffer(const char* filename, jobject buffer) {
    JNIEnv* env = GetCurrentJNIEnv();
    void* address = env->GetDirectBufferAddress(buffer);
    jlong size = env->GetDirectBufferCapacity(buffer);
    
    FILE* file = fopen(filename, "wb");
    if (file != nullptr) {
        // 直接将内存内容写入文件
        fwrite(address, 1, size, file);
        fclose(file);
    }
}

七、直接内存性能优化

6.1 内存分配优化

为减少内存碎片,ART采用多种分配策略:

cpp 复制代码
// 内存池分配器
class MemoryPoolAllocator {
private:
    // 内存池列表
    std::vector<void*> pools_; 
    // 当前可用内存块
    std::list<void*> free_blocks_; 
    // 块大小
    size_t block_size_; 
public:
    // 从内存池分配内存
    void* Allocate(size_t size) {
        if (size > block_size_) {
            // 大块内存直接分配
            return malloc(size);
        }
        if (free_blocks_.empty()) {
            // 创建新的内存池
            void* pool = malloc(block_size_ * kPoolSize);
            pools_.push_back(pool);
            // 分割内存池为多个块
            for (int i = 0; i < kPoolSize; ++i) {
                free_blocks_.push_back((char*)pool + i * block_size_);
            }
        }
        // 从空闲块列表获取内存
        void* block = free_blocks_.front();
        free_blocks_.pop_front();
        return block;
    }
    
    // 释放内存回内存池
    void Free(void* ptr) {
        if (IsInPool(ptr)) {
            free_blocks_.push_back(ptr);
        } else {
            free(ptr);
        }
    }
};

6.2 I/O操作优化

直接内存减少了数据在Java堆与本地内存之间的复制,提升I/O性能:

cpp 复制代码
// 使用直接内存的高效I/O操作
void EfficientIOOperation() {
    // 创建直接内存缓冲区
    jobject direct_buffer = CreateDirectByteBuffer(1024 * 1024); // 1MB
    JNIEnv* env = GetCurrentJNIEnv();
    void* address = env->GetDirectBufferAddress(direct_buffer);
    
    // 直接与底层I/O交互
    int fd = open("/dev/some_device", O_RDWR);
    if (fd >= 0) {
        // 零拷贝I/O操作
        write(fd, address, 1024 * 1024); 
        close(fd);
    }
}

6.3 内存访问优化

通过对齐和缓存友好的内存布局提升访问速度:

cpp 复制代码
// 内存对齐分配
void* AllocateAlignedMemory(size_t size, size_t alignment) {
    void* ptr;
    // 使用posix_memalign确保内存对齐
    int result = posix_memalign(&ptr, alignment, size);
    if (result != 0) {
        return nullptr;
    }
    // 初始化内存
    memset(ptr, 0, size);
    return ptr;
}

// 缓存友好的数据处理
void ProcessDataCacheFriendly(void* data, size_t size) {
    // 按缓存行大小处理数据
    const size_t cache_line_size = 64;
    for (size_t i = 0; i < size; i += cache_line_size) {
        // 处理一个缓存行的数据
        ProcessCacheLine((char*)data + i, cache_line_size);
    }
}

八、直接内存的安全性与错误处理

8.1 内存访问越界检查

为防止直接内存的越界访问,ART在JNI层进行边界检查:

cpp 复制代码
// 安全访问直接内存
bool SafeAccessDirectBuffer(JNIEnv* env, jobject buffer, size_t offset, size_t length) {
    void* address = env->GetDirectBufferAddress(buffer);
    jlong capacity = env->GetDirectBufferCapacity(buffer);
    
    // 边界检查
    if (address == nullptr || offset < 0 || length < 0 || offset + length > capacity) {
        // 越界访问,抛出异常
        ThrowIllegalArgumentException(env, "Buffer access out of bounds");
        return false;
    }
    
    // 安全访问内存
    ProcessMemory((char*)address + offset, length);
    return true;
}

8.2 内存损坏检测

ART通过内存保护机制检测和处理内存损坏:

cpp 复制代码
// 内存完整性检查
bool CheckMemoryIntegrity(void* address, size_t size) {
    // 计算内存区域的校验和
    uint32_t checksum = CalculateChecksum(address, size);
    // 比较当前校验和与存储的校验和
    return checksum == GetStoredChecksum(address);
}

// 内存损坏处理
void HandleMemoryCorruption(void* address) {
    LOG_ERROR("Memory corruption detected at %p", address);
    // 尝试恢复或清理损坏的内存
    RepairMemory(address);
    // 通知应用层
    NotifyMemoryError();
}

8.3 错误处理与恢复

当直接内存操作失败时,ART提供错误处理机制:

cpp 复制代码
// 直接内存操作错误处理
void HandleDirectMemoryError(JNIEnv* env, const char* message) {
    // 记录错误日志
    LOG_ERROR("Direct memory error: %s", message);
    // 尝试释放部分内存
    FreeUnusedMemory();
    // 抛出异常
    ThrowOutOfMemoryError(env, message);
}

// 内存压力缓解
void RelieveMemoryPressure() {
    // 触发GC
    TriggerGC();
    // 释放缓存
    ReleaseCaches();
    // 减少直接内存使用
    ShrinkDirectMemory();
}

九、直接内存与系统资源管理

9.1 与虚拟内存系统的交互

直接内存分配受操作系统虚拟内存系统的限制:

cpp 复制代码
// 获取系统可用内存
size_t GetSystemAvailableMemory() {
    struct sysinfo info;
    if (sysinfo(&info) != 0) {
        return 0;
    }
    // 计算可用内存
    return info.freeram * info.mem_unit;
}

// 智能内存分配
void* SmartAllocate(size_t size) {
    // 检查系统可用内存
    size_t available = GetSystemAvailableMemory();
    if (available < size * 1.5) {
        // 系统内存不足,尝试释放资源
        RelieveMemoryPressure();
        // 再次检查
        available = GetSystemAvailableMemory();
        if (available < size) {
            return nullptr;
        }
    }
    // 分配内存
    return AllocateDirectMemory(size);
}

9.2 内存映射文件与直接内存

直接内存常用于内存映射文件操作:

cpp 复制代码
// 内存映射文件
void* MapFileToMemory(const char* filename, size_t size) {
    int fd = open(filename, O_RDWR);
    if (fd < 0) {
        return nullptr;
    }
    // 使用mmap将文件映射到内存
    void* address = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);
    if (address == MAP_FAILED) {
        return nullptr;
    }
    // 注册内存映射区域
    DirectMemoryManager::Current()->RegisterMappedMemory(address, size);
    return address;
}

// 解除内存映射
void UnmapMemory(void* address, size_t size) {
    // 取消注册
    DirectMemoryManager::Current()->UnregisterMappedMemory(address);
    // 使用munmap解除映射
    munmap(address, size);
}

9.3 内存使用统计与报告

ART提供直接内存使用情况的统计与报告功能:

cpp 复制代码
// 生成内存使用报告
void GenerateMemoryReport() {
    LOG_INFO("Direct Memory Report:");
    LOG_INFO("  Total Allocated: %zu bytes", DirectMemoryManager::Current()->GetTotalAllocated());
    LOG_INFO("  Max Capacity: %zu bytes", DirectMemoryManager::Current()->GetMaxCapacity());
    LOG_INFO("  Usage Percentage: %.2f%%", 
             (float)DirectMemoryManager::Current()->GetTotalAllocated() / 
             DirectMemoryManager::Current()->GetMaxCapacity() * 100);
    
    // 报告最大的几个内存分配
    std::vector<DirectMemoryRegion*> large_regions = 
        DirectMemoryManager::Current()->GetLargestRegions(10);
    LOG_INFO("Top 10 largest direct memory regions:");
    for (const auto& region : large_regions) {
        LOG_INFO("  %p - %zu bytes", region->address_, region->size_);
    }
}

十、直接内存的应用场景与最佳实践

10.1 高性能I/O操作

直接内存适用于需要频繁进行I/O操作的场景:

  • 网络编程:减少数据在用户空间与内核空间之间的复制
  • 文件处理:支持零拷贝文件读写
  • 多媒体处理:高效处理图像、音频和视频数据

10.2 与本地库交互

当Java应用需要调用本地库时,直接内存可简化数据传递:

  • JNI调用:避免Java对象与本地数据结构之间的转换
  • 高性能计算:将数据直接传递给本地计算库
  • 硬件交互:与底层硬件设备进行内存映射通信

10.3 最佳实践指南

  1. 合理控制内存使用:避免过度分配直接内存,监控内存使用情况
  2. 及时释放资源:不再使用的DirectByteBuffer应及时释放,或主动调用clean方法
  3. 避免内存碎片:采用内存池技术管理直接内存分配
  4. 性能测试:在关键路径上使用直接内存前,进行充分的性能测试与对比
  5. 异常处理:在使用直接内存的代码中添加完善的异常处理逻辑

10.4 常见问题与解决方案

问题 原因分析 解决方案
OutOfMemoryError 直接内存使用超过限制 减少分配、增加最大直接内存限制
内存泄漏 DirectByteBuffer未及时释放 主动调用clean方法、使用弱引用
性能下降 频繁分配/释放直接内存 使用内存池技术
数据损坏 内存越界访问 加强边界检查、使用安全访问接口
相关推荐
资深前端之路3 小时前
react 面试题 react 有什么特点?
前端·react.js·面试·前端框架
拉不动的猪3 小时前
回顾vue中的Props与Attrs
前端·javascript·面试
一笑的小酒馆3 小时前
Android性能优化之截屏时黑屏卡顿问题
android
boonya5 小时前
Redis核心原理与面试问题解析
数据库·redis·面试
懒人村杂货铺6 小时前
Android BLE 扫描完整实战
android
在未来等你6 小时前
Kafka面试精讲 Day 8:日志清理与数据保留策略
大数据·分布式·面试·kafka·消息队列
沐怡旸6 小时前
【算法--链表】114.二叉树展开为链表--通俗讲解
算法·面试
白水清风7 小时前
关于Js和Ts中类(class)的知识
前端·javascript·面试
TeleostNaCl8 小时前
如何安装 Google 通用的驱动以便使用 ADB 和 Fastboot 调试(Bootloader)设备
android·经验分享·adb·android studio·android-studio·android runtime