本文环境
- win 10
- jdk 1.8.0_241
- IntelliJ 2019.1.3
一、通过Instrumentation获取内存
- 在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;
}
}
- 修改
META-INF\MANIFEST.MF
文件,添加如下行,引用上面的代理类
Premain-Class: com.nineya.memorymeasurs.MemoryMeasurs
-
在其他地方可以自由使用这个MemoryMeasurs代理类了。
-
编译工程为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));