java取得對象佔用的內存大小

本文環境

  1. win 10
  2. jdk 1.8.0_241
  3. IntelliJ 2019.1.3

一、通過Instrumentation獲取內存

  1. 在java工程中添加如下代理類:
package com.nineya.memorymeasurs;
import java.lang.instrument.Instrumentation;

public class MemoryMeasurs {

    static Instrumentation inst;

    // 由jvm注入
    public static void premain(String agentArgs, Instrumentation inst) {
        MemoryMeasurs.inst = inst;
    }

    // 取得對象大小
    public static long sizeOf(Object o) {
        if(inst == null) {
            throw new IllegalStateException("請在java的“-javaagent”命令行參數中運行。");
        }
        return inst.getObjectSize(o);
    }
}

上面的代理類能夠實現基礎的測量類對象內存佔用的功能,在網上找到的更完整的版本如下,能夠遞歸測量類對象引用的對象的佔用的內存。

package com.nineya.memorymeasurs;

import java.lang.instrument.Instrumentation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Stack;
/**
 * @author linsongwang
 * @date 2020/4/27
 */

public class MemoryMeasurs {
    static Instrumentation inst;

    // 由jvm注入
    public static void premain(String agentArgs, Instrumentation instP) {
        inst = instP;
    }

    // 計算對象內存佔用
    public static long sizeOf(Object o) {
        if(inst == null) {
            throw new IllegalStateException("請在java的“-javaagent”命令行參數中運行。");
        }
        return inst.getObjectSize(o);
    }
    /**
     * 遞歸計算當前對象佔用空間總大小,包括當前類和超類的實例字段大小以及實例字段引用對象大小
     */
    public static long fullSizeOf(Object obj) {//深入檢索對象,並計算大小
        Map<Object, Object> visited = new IdentityHashMap<Object, Object>();
        Stack<Object> stack = new Stack<Object>();
        long result = internalSizeOf(obj, stack, visited);
        while (!stack.isEmpty()) {//通過棧進行遍歷
            result += internalSizeOf(stack.pop(), stack, visited);
        }
        visited.clear();
        return result;
    }

    //判定哪些是需要跳過的
    private static boolean skipObject(Object obj, Map<Object, Object> visited) {
        if (obj instanceof String) {
            if (obj == ((String) obj).intern()) {
                return true;
            }
        }
        return (obj == null) || visited.containsKey(obj);
    }

    private static long internalSizeOf(Object obj, Stack<Object> stack, Map<Object, Object> visited) {
        if (skipObject(obj, visited)) {//跳過常量池對象、跳過已經訪問過的對象
            return 0;
        }
        visited.put(obj, null);//將當前對象放入棧中
        long result = 0;
        result += sizeOf(obj);
        Class <?>clazz = obj.getClass();
        if (clazz.isArray()) {//如果數組
            if(clazz.getName().length() != 2) {// skip primitive type array
                int length =  Array.getLength(obj);
                for (int i = 0; i < length; i++) {
                    stack.add(Array.get(obj, i));
                }
            }
            return result;
        }
        return getNodeSize(clazz , result , obj , stack);
    }
    //這個方法獲取非數組對象自身的大小,並且可以向父類進行向上搜索
    private static long getNodeSize(Class <?>clazz , long result , Object obj , Stack<Object> stack) {
        while (clazz != null) {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                if (!Modifier.isStatic(field.getModifiers())) {//這裏拋開靜態屬性
                    if (field.getType().isPrimitive()) {//這裏拋開基本關鍵字(因爲基本關鍵字在調用java默認提供的方法就已經計算過了)
                        continue;
                    }else {
                        field.setAccessible(true);
                        try {
                            Object objectToAdd = field.get(obj);
                            if (objectToAdd != null) {
                                stack.add(objectToAdd);//將對象放入棧中,一遍彈出後繼續檢索
                            }
                        } catch (IllegalAccessException ex) {
                            assert false;
                        }
                    }
                }
            }
            clazz = clazz.getSuperclass();//找父類class,直到沒有父類
        }
        return result;
    }
}
  1. 修改META-INF\MANIFEST.MF文件,添加如下行,引用上面的代理類
Premain-Class: com.nineya.memorymeasurs.MemoryMeasurs
  1. 在其他地方可以自由使用這個MemoryMeasurs代理類了。

  2. 編譯工程爲jar包,使用如下命令執行:

java -javaagent:XXX.jar main方法所在的類

// 示例
java -javaagent:ObjectSize.jar  ObjectSizeTest

二、通過jol包

1.maven導包

        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>

2.使用api

import org.openjdk.jol.info.ClassLayout;

// 取得map佔用的空間
Map<String, String> map = new HashMap<>();
ClassLayout.parseInstance(map).instanceSize();

3.jol只是獲取對象佔用的內存空間,不會遞歸獲取對象引用的其他對象佔用的內存空間,稍微修改上面的MemoryMeasurs類進行封裝。

package com.cl.graph.util;

import org.openjdk.jol.info.ClassLayout;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Stack;

/**
 * @author linsongwang
 * @date 2020/4/27
 */

public class ObjectShallowSize {

    // 計算對象內存佔用
    public static long sizeOf(Object o) {
        return ClassLayout.parseInstance(o).instanceSize();
    }

    /**
     * 深入檢索對象,遞歸計算當前對象佔用空間總大小,包括當前類和父類類的實例字段大小以及實例字段引用對象大小
     */
    public static long fullSizeOf(Object obj) {//深入檢索對象,並計算大小
        Map<Object, Object> visited = new IdentityHashMap<Object, Object>();
        Stack<Object> stack = new Stack<Object>();
        long result = internalSizeOf(obj, stack, visited);
        while (!stack.isEmpty()) {//通過棧進行遍歷
            result += internalSizeOf(stack.pop(), stack, visited);
        }
        visited.clear();
        return result;
    }

    //判定哪些是需要跳過的
    private static boolean skipObject(Object obj, Map<Object, Object> visited) {
        if (obj instanceof String) {
            if (obj == ((String) obj).intern()) {
                return true;
            }
        }
        return (obj == null) || visited.containsKey(obj);
    }

    private static long internalSizeOf(Object obj, Stack<Object> stack, Map<Object, Object> visited) {
        if (skipObject(obj, visited)) {//跳過常量池對象、跳過已經訪問過的對象
            return 0;
        }
        visited.put(obj, null);//將當前對象放入棧中
        long result = 0;
        result += sizeOf(obj);
        Class<?> clazz = obj.getClass();
        if (clazz.isArray()) {//如果數組
            if (clazz.getName().length() != 2) {// skip primitive type array
                int length = Array.getLength(obj);
                for (int i = 0; i < length; i++) {
                    stack.add(Array.get(obj, i));
                }
            }
            return result;
        }
        return getNodeSize(clazz, result, obj, stack);
    }

    //這個方法獲取非數組對象自身的大小,並且可以向父類進行向上搜索
    private static long getNodeSize(Class<?> clazz, long result, Object obj, Stack<Object> stack) {
        while (clazz != null) {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                if (!Modifier.isStatic(field.getModifiers())) {//這裏拋開靜態屬性
                    if (field.getType().isPrimitive()) {//這裏拋開基本關鍵字(因爲基本關鍵字在調用java默認提供的方法就已經計算過了)
                        continue;
                    } else {
                        field.setAccessible(true);
                        try {
                            Object objectToAdd = field.get(obj);
                            if (objectToAdd != null) {
                                stack.add(objectToAdd);//將對象放入棧中,一遍彈出後繼續檢索
                            }
                        } catch (IllegalAccessException ex) {
                            assert false;
                        }
                    }
                }
            }
            clazz = clazz.getSuperclass();//找父類class,直到沒有父類
        }
        return result;
    }

    public static long getGraphMemory(String graphName){
        Graph graph = GraphManage.getGraph(graphName);
        if (graph==null){
            System.out.println("圖 "+graphName+" 不存在!");
            return 0;
        }
        System.out.println("遞歸獲取內存中,該過程將非常長!");
        return fullSizeOf(graph);
    }
}

4.使用ObjectShallowSize獲取對象內存佔用

Map<String, String> map = new HashMap<>();
// 取得map佔用的空間
ObjectShallowSize.sizeOf(map);
// 遞歸取得map佔用的空間
ObjectShallowSize.fullSizeOf(map);

三、通過計算jvm內存空間取得

// gc操作
System.gc();
// startMemory=totalMemory(總內存)-freeMemory(剩餘內存)
long startMemory = Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();

// 在這裏new需要測試內存佔用的對象
...

// gc操作
System.gc();
// endMemory=totalMemory(總內存)-freeMemory(剩餘內存)
long endMemory = Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();

Log.info("內存佔用:"+ (endMemory-startMemory));
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章