编写代码,向List集合中添加100万个字符串,每个字符串由1000个UUID组成。如果程序能够正常执行,最后打印ok。
public class TestJvmOutOfMemory {
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
for (int i = 0; i < 10000000; i++) {
String str = "";
for (int j = 0; j < 1000; j++) {
str += UUID.randomUUID().toString();
}
list.add(str);
}
System.out.println("ok");
}
}
设置参数:
#参数如下:
-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
#指定路径
-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=F:\t\dump.hprof
#手动dump内存
#用法:
jmap -dump:format=b,file=dumpFileName
#示例
jmap -dump:format=b,file=/tmp/dump.dat 6219
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid14768.hprof …
Heap dump file created [8127578 bytes in 0.026 secs]
Exception in thread “main” java.lang.OutOfMemoryError: Java heap space
可以看到,当发生内存溢出时,会dump内存到java_pid14768.hprof文件中,该文件在项目的根目录下。
MAT(Memory Analyzer Tool),一个基于Eclipse的内存分析工具,是一个快速、功能丰富的JAVA heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。
官网地址:https://www.eclipse.org/mat/
下载地址:https://www.eclipse.org/mat/downloads.php
查看对象以及它的依赖:
查看可能存在内存泄露的分析:
可以看到,有91.02%的内存由Object[]数组占有,所以比较可疑。
查看详情:
可以看到集合中存储了大量的uuid字符串。
有些时候我们需要查看下jvm中的线程执行情况,比如,发现服务器的CPU的负载突然增高了、出现了死锁、死循环等,我们该如何分析呢?
由于程序是正常运行的,没有任何的输出,从日志方面也看不出什么问题,所以就需要看下jvm的内部线程的执行情况,然后再进行分析查找出原因。
这个时候,就需要借助于jstack命令了,jstack的作用是将正在运行的jvm的线程 情况进行快照,并且打印出来:
#用法:jstack
编写代码,启动2个线程,Thread1拿到了obj1锁,准备去拿obj2锁时,obj2已经被Thread2锁定,所以发生了死锁。
public class TestDeadLock {
private static Object obj1 = new Object();
private static Object obj2 = new Object();
public static void main(String[] args) {
new Thread(new Thread1()).start();
new Thread(new Thread2()).start();
}
private static class Thread1 implements Runnable{
@Override
public void run() {
synchronized (obj1){
System.out.println("Thread1 拿到了 obj1 的锁!");
try {
// 停顿2秒的意义在于,让Thread2线程拿到obj2的锁
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2){
System.out.println("Thread1 拿到了 obj2 的锁!");
}
}
}
}
private static class Thread2 implements Runnable{
@Override
public void run() {
synchronized (obj2){
System.out.println("Thread2 拿到了 obj2 的锁!");
try {
// 停顿2秒的意义在于,让Thread1线程拿到obj1的锁
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1){
System.out.println("Thread2 拿到了 obj1 的锁!");
}
}
}
}
}
补充知识点:产生死锁的条件
可以看到,程序已经卡在这里了,不在继续往下执行。
查看进程ID,jps -l
jstack 12652 #查看进程中的线程状态
在输出的信息中,已经看到,发现了1个死锁,关键看这个:
Found one Java-level deadlock:
=============================
“Thread-1”:
waiting to lock monitor 0x00000000176636f8 (object 0x00000000d749d958, a java.lang.Object),
which is held by “Thread-0”
“Thread-0”:
waiting to lock monitor 0x0000000017662258 (object 0x00000000d749d968, a java.lang.Object),
which is held by “Thread-1”
Java stack information for the threads listed above:
===================================================
“Thread-1”:
at cn.itcast.jvm.TestDeadLock T h r e a d 2. r u n ( T e s t D e a d L o c k . j a v a : 49 ) − w a i t i n g t o l o c k < 0 x 00000000 d 749 d 958 > ( a j a v a . l a n g . O b j e c t ) − l o c k e d < 0 x 00000000 d 749 d 968 > ( a j a v a . l a n g . O b j e c t ) a t j a v a . l a n g . T h r e a d . r u n ( T h r e a d . j a v a : 748 ) " T h r e a d − 0 " : a t c n . i t c a s t . j v m . T e s t D e a d L o c k Thread2.run(TestDeadLock.java:49) - waiting to lock <0x00000000d749d958> (a java.lang.Object) - locked <0x00000000d749d968> (a java.lang.Object) at java.lang.Thread.run(Thread.java:748) "Thread-0": at cn.itcast.jvm.TestDeadLock Thread2.run(TestDeadLock.java:49)−waitingtolock<0x00000000d749d958>(ajava.lang.Object)−locked<0x00000000d749d968>(ajava.lang.Object)atjava.lang.Thread.run(Thread.java:748)"Thread−0":atcn.itcast.jvm.TestDeadLockThread1.run(TestDeadLock.java:29)
- waiting to lock <0x00000000d749d968> (a java.lang.Object)
- locked <0x00000000d749d958> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
可以清晰的看到:
在jdk的安装目录的bin目录下,找到jvisualvm.exe,双击打开即可。
VisualJVM不仅是可以监控本地jvm进程,还可以监控远程的jvm进程,需要借助于JMX技术实现。
JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。
想要监控远程的tomcat,就需要在远程的tomcat进行对JMX配置,方法如下:
#在tomcat的bin目录下,修改catalina.sh,添加如下的参数
JAVA_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999
-Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false""
#这几个参数的意思是:
#-Dcom.sun.management.jmxremote :允许使用JMX远程管理
#-Dcom.sun.management.jmxremote.port=9999 :JMX远程连接端口
#-Dcom.sun.management.jmxremote.authenticate=false :不进行身份认证,任何用户都可以连接
#-Dcom.sun.management.jmxremote.ssl=false :不使用ssl
检测堆内存的具体使用情况,需要安装插件Visual GC进行检测:
public static void main(String[] args) {
List<User> userList = new ArrayList<>();
while (true){
User user = new User();
user.setId(1L);
user.setUsername("user");
user.setPassword("pass");
if(System.currentTimeMillis() % 2 ==0 ){
userList.add(user);
System.out.println("add to list, size = " + userList.size());
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行参数:-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
可以看到,年轻代、老年代中的内存使用情况。