本文環境
- 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));