本文环境

  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));