MemoryOptimizer.java

/*
 * SPDX-FileCopyrightText: 2025 Lucimber UG
 * SPDX-License-Identifier: Apache-2.0
 */
package com.lucimber.dbus.util;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Utility class for memory optimization and monitoring in D-Bus Client Java.
 *
 * <p>This class provides tools for:
 *
 * <ul>
 *   <li>Monitoring memory usage and detecting memory pressure
 *   <li>Providing guidance on when to trigger cleanup operations
 *   <li>Collecting statistics about object allocation patterns
 *   <li>Suggesting optimal collection sizes
 * </ul>
 *
 * <p>This is primarily intended for internal use by the library to make intelligent decisions about
 * memory management, such as when to clean up cached objects or resize collections.
 */
public final class MemoryOptimizer {

    private static final MemoryMXBean MEMORY_MX_BEAN = ManagementFactory.getMemoryMXBean();

    // Thresholds for memory pressure detection
    private static final double HIGH_MEMORY_USAGE_THRESHOLD = 0.85; // 85%
    private static final double CRITICAL_MEMORY_USAGE_THRESHOLD = 0.95; // 95%

    // Statistics tracking
    private static final AtomicLong totalAllocations = new AtomicLong(0);
    private static final AtomicLong totalBytesAllocated = new AtomicLong(0);
    private static final AtomicLong cacheHits = new AtomicLong(0);
    private static final AtomicLong cacheMisses = new AtomicLong(0);

    private MemoryOptimizer() {
        // Utility class - no instantiation
    }

    /**
     * Checks if the system is currently under memory pressure. This can be used to trigger cleanup
     * operations or reduce caching.
     *
     * @return true if memory usage is above the high threshold
     */
    public static boolean isUnderMemoryPressure() {
        return getHeapUsageRatio() > HIGH_MEMORY_USAGE_THRESHOLD;
    }

    /**
     * Checks if the system is in critical memory state. This should trigger immediate cleanup and
     * reduce allocations.
     *
     * @return true if memory usage is above the critical threshold
     */
    public static boolean isInCriticalMemoryState() {
        return getHeapUsageRatio() > CRITICAL_MEMORY_USAGE_THRESHOLD;
    }

    /**
     * Gets the current heap memory usage as a ratio (0.0 to 1.0).
     *
     * @return current heap usage ratio
     */
    public static double getHeapUsageRatio() {
        MemoryUsage heapUsage = MEMORY_MX_BEAN.getHeapMemoryUsage();
        long used = heapUsage.getUsed();
        long max = heapUsage.getMax();

        // If max is undefined (-1), use committed memory
        if (max < 0) {
            max = heapUsage.getCommitted();
        }

        return max > 0 ? (double) used / max : 0.0;
    }

    /**
     * Suggests an optimal initial capacity for collections based on memory pressure and expected
     * size.
     *
     * @param expectedSize expected number of elements
     * @param elementSize approximate size per element in bytes
     * @return suggested initial capacity
     */
    public static int suggestCollectionCapacity(int expectedSize, int elementSize) {
        if (expectedSize <= 0) {
            return 0;
        }

        // Under memory pressure, be more conservative
        if (isUnderMemoryPressure()) {
            // Start smaller and let it grow
            return Math.min(expectedSize, 8);
        }

        // Normal memory conditions - size appropriately with some buffer
        long totalBytes = (long) expectedSize * elementSize;

        // For small collections, use exact size
        if (totalBytes < 1024) { // Less than 1KB
            return expectedSize;
        }

        // For larger collections, add 25% buffer to reduce resizing
        return Math.max(expectedSize, (int) (expectedSize * 1.25));
    }

    /**
     * Records an allocation for statistics tracking.
     *
     * @param bytes number of bytes allocated
     */
    public static void recordAllocation(long bytes) {
        totalAllocations.incrementAndGet();
        totalBytesAllocated.addAndGet(bytes);
    }

    /** Records a cache hit for statistics tracking. */
    public static void recordCacheHit() {
        cacheHits.incrementAndGet();
    }

    /** Records a cache miss for statistics tracking. */
    public static void recordCacheMiss() {
        cacheMisses.incrementAndGet();
    }

    /**
     * Gets the current cache hit ratio.
     *
     * @return cache hit ratio (0.0 to 1.0)
     */
    public static double getCacheHitRatio() {
        long hits = cacheHits.get();
        long misses = cacheMisses.get();
        long total = hits + misses;

        return total > 0 ? (double) hits / total : 0.0;
    }

    /**
     * Returns current memory usage statistics as a formatted string. Useful for logging and
     * debugging.
     *
     * @return memory statistics string
     */
    public static String getMemoryStatistics() {
        MemoryUsage heapUsage = MEMORY_MX_BEAN.getHeapMemoryUsage();
        MemoryUsage nonHeapUsage = MEMORY_MX_BEAN.getNonHeapMemoryUsage();

        return String.format(
                "Memory Statistics:\n"
                        + "  Heap: %.1f%% used (%d MB / %d MB)\n"
                        + "  Non-Heap: %d MB used\n"
                        + "  Total Allocations: %d (%d MB)\n"
                        + "  Cache Hit Ratio: %.2f%%\n"
                        + "  Memory Pressure: %s",
                getHeapUsageRatio() * 100,
                heapUsage.getUsed() / (1024 * 1024),
                heapUsage.getMax() / (1024 * 1024),
                nonHeapUsage.getUsed() / (1024 * 1024),
                totalAllocations.get(),
                totalBytesAllocated.get() / (1024 * 1024),
                getCacheHitRatio() * 100,
                isUnderMemoryPressure() ? "HIGH" : "NORMAL");
    }

    /**
     * Suggests whether a cleanup operation should be performed based on current memory conditions
     * and operation frequency.
     *
     * @param operationCount number of operations since last cleanup
     * @param cleanupThreshold normal threshold for cleanup
     * @return true if cleanup is recommended
     */
    public static boolean shouldPerformCleanup(long operationCount, long cleanupThreshold) {
        // Always cleanup if in critical memory state
        if (isInCriticalMemoryState()) {
            return operationCount > 0;
        }

        // Under memory pressure, cleanup more frequently
        if (isUnderMemoryPressure()) {
            return operationCount > (cleanupThreshold / 2);
        }

        // Normal memory conditions
        return operationCount >= cleanupThreshold;
    }

    /** Resets all internal statistics counters. Useful for testing or periodic stats reporting. */
    public static void resetStatistics() {
        totalAllocations.set(0);
        totalBytesAllocated.set(0);
        cacheHits.set(0);
        cacheMisses.set(0);
    }
}