1.把堆内存dump下载
jps //查看pid
jmap -dump:format=b,file=heap.1 pid //不进行垃圾回收直接dump
jmap -dump:live,format=b,file=heap.2 pid //进行fgc垃圾回收在进行dump
2.使用java VisualVM(lib目录下jdk8自带)导入dump文件
导入后的截图
2.分析下面1.5g数组在哪里使用
3.双击byte[]看到的页面如下
数组不是1.5g吗上面只是定位了1g的使用位置那么还有500m在哪里使用了呢我们继续定位
注意!
如果发现某个对象在线程中显示按钮是灰色的表示该对象的线程或者方法已经执行完毕
是无非定位到该对象,解决办法在方法中进行睡眠然后dump文件
此时dump的文件对象是可以获取使用位置具体代码如下
1.解决方案
jvm中发现一个Goods对象有10万个对象怎么定位,可以在构造方法判断这个对象创建==10万个
,此时是dump文件的最佳时间
private static int createNum;
public Customer() {
private static int createNum;
@Transient
Logger logger = LoggerFactory.getLogger(Customer.class);
public Customer() {
synchronized (Customer.class){
createNum++;
}
StackTraceUtil.updateStackTrace();
if(createNum==100000){
try {
String path = AutoDumpUtil.doDump(this);
logger.warn("发现有"+createNum+"个"+Customer.class+"对象,已自动dump文件,请检查是否异常,path="+path);
logger.warn(StackTraceUtil.getStackTrace());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
工具类1
public class AutoDumpUtil {
private static Logger logger = LoggerFactory.getLogger(AutoDumpUtil.class);
/**
*
* @param o 什么都不做为了确保能获取o对象的GCRoot必须传入
* @throws Exception
*/
public static String doDump(Object o) throws Exception {
RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
String name = runtimeMXBean.getName();
int index = name.indexOf("@");
String path="";
if (index != -1) {
int pid = Integer.parseInt(name.substring(0, index));
Runtime runtime = Runtime.getRuntime();
long start = System.currentTimeMillis();
char separatorChar = File.separatorChar;
path="f:"+separatorChar+"dev"+separatorChar+"autoDump."+pid+" ";
String cmd="jmap -dump:format=b,file="+path+pid;
Process exec = runtime.exec(cmd);
//等待命令执行完
exec.waitFor();
long end = System.currentTimeMillis();
logger.warn("autoDump."+pid+"文件成功,耗时:"+(end-start)+"ms");
}
return path;
}
}
工具类2
public class StackTraceUtil {
private static Map<String,Integer> map = new ConcurrentHashMap<>();
public static void updateStackTrace(){
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
String stack="class=["+stackTrace[3].getClassName()+"] methodName=["+stackTrace[3].getMethodName()+"] line=["+stackTrace[3].getLineNumber()+"]";
if(map.containsKey(stack)){
Integer count = map.get(stack);
map.put(stack,++count);
}else{
map.put(stack,1);
}
}
public static String getStackTrace(){
final String[] result = {""};
map.forEach((k,v)->{
result[0] +=k+",调用次数="+v+"\r\n";
});
return result[0];
}
}
2.看项目的调用结构,找出Customer占用内存比较大的位置,这里可以使用StackTraceUtil 定位,也可以自己打开dump文件分析定位
3.打印的日志,根据日志的提示找到大对象的位置比如下面有三个地方创建了Customer大量的对象,我们就以创建最多的7万次分析
class=[com.yujie.service.impl.OrderCustomerImpl] methodName=[save] line=[20],意思是OrderCustomerImpl类save方法第20行创建了7万次对象
2022-06-05 10:47:30.826 [Thread-13] WARN com.yujie.utils.AutoDumpUtil - autoDump.10872文件成功,耗时:2583ms
2022-06-05 10:47:30.826 [Thread-13] WARN com.yujie.model.Customer - 发现有100000个class com.yujie.model.Customer对象,已自动dump文件,请检查是否异常,path=f:\dev\autoDump.10872
2022-06-05 10:47:30.827 [Thread-13] WARN com.yujie.model.Customer - class=[com.yujie.service.impl.OrderCustomerImpl] methodName=[save] line=[20],调用次数=70000
class=[sun.reflect.NativeConstructorAccessorImpl] methodName=[newInstance0] line=[-2],调用次数=1
class=[com.yujie.service.impl.C] methodName=[save] line=[19],调用次数=19999
class=[com.yujie.service.impl.B] methodName=[save] line=[19],调用次数=10000
到此完毕谢谢大家支持