一、引入
进入tomcat下的webapps文件夹中,将test1.war上传至该目录下,重启tomcat:
sh startup.sh && tail -f ../logs/catalina.out
这时候tomcat会自动解压war包,文件夹文件如下:
在浏览器中访问该项目jsp文件的地址:
使用JMeter对该地址进行压测,录制http请求:
配置压测并发及时间:
查看聚合报告:
页面报堆内存溢出错误:
查看日志:
常见的内存溢出:
java.lang.OutOfMemoryError: PermGen space → 持久代内存溢出
java.lang.OutOfMemoryError: Java heap space → 堆内存溢出
java.lang.StackOverflowError → 栈内存溢出
通过:jmap -heap pid可以查看内存状况:
如上图表明内存溢出
二、JVM说明
1、JVM内存管理
1)堆与栈:
栈是运行时的单位,而堆是存储的单位。
栈解决程序的运行问题,即程序如何执行,或者说如何处理数据
堆解决的是数据存储的问题,即数据怎么放、放在哪儿
栈的优势是存取速度比堆要快,仅次于寄存器,栈数据可以共享
在Java中一个线程就会相应有一个线程栈与之对应,不同的线程执行逻辑有所不同,因此需要一个独立的线程栈
堆是所有线程共享的。
栈是运行的单位,存储的信息跟当前线程(或程序)相关的信息,包括局部变量、程序运行状态、方法返回值等等,优势是栈的优势是存取速度比堆要快,仅次于寄存器,栈数据可以共享。缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
堆只负责存储对象信息,堆的优势是可以动态地分配内存大小,但缺点是,由于要在运行时动态分配内存,存取速度较慢。
2)JVM内存图解
①new出来的对象,最开始优先分配进入伊甸园区,伊甸园区满了之后,jvm会触发YGC进行垃圾回收:
A、寻根判断,哪些对象有引用,哪些对象需要进行GC标识
B、将伊甸园区的存活对象移动到s0存活区,将伊甸园区的需要GC的对象GC掉
②在执行垃圾回收的过程中,整个Java应用程序的线程是暂停没有工作的,直至垃圾回收接收之后,YGC较为频繁。由此可知jvm在进行YGC的时候,会影响应用系统性能。
③当伊甸园区再次满了的时候:
A、jvm会再次对伊甸园区的对象进行寻根判断标识,然后将存活的对象移动到s1存活区,将伊甸园区的不需要的对象GC掉;
B、对s0区中的对象进行寻根判断,存活的移动到s1存活区,将s0中不需要的对象GC掉。
上述A、B步骤就叫做两个存活区大小相等,位置互换。
④当伊甸园区第3次满了的时候:
A、jvm会再次对伊甸园区的对象进行寻根判断标识,然后将存活的对象移动到s0存活区,将伊甸园去的不需要的对象GC掉;
B、对s1区中的对象进行寻根判断,存活的移动到s0存活区,将s1中不需要的对象GC掉。大小相等,位置互换。
⑤jvm在进行YGC的时候重复上述①到④步骤。
⑥大对象直接进入老年代(大对象 → JDK默认设置大对象的大小)
⑦长期存活的对象进入老年代(长期存活的对象,默认age=15即进行了15次GC之后,有对象一直存活,那么在下次GC的时候,该对象会进入老年代)
⑧对象动态分配原则:比方说,在存活区里边存在着各年龄的对象,jvm会根据一定的方法判断,当某个年龄的对象之和超过存活区的一半大小,那么jvm会将大于等于该age的对象全部移动到老年代
⑨随着时间的推移,老年代内存空间满了之后会jvm会触发FullGC,FullGC的时候会回收整个堆内存和非堆内存(回收的过程都会进程寻根判断,GC掉没用的对象,意味着各区GC完之后还会有些存活的对象);
随着Java程序的继续运行还会触发FullGC,如果对象回收不彻底,老年代被占用的内存比例会越来越高,当再次要往老年代中放置对象的时候,不能将对象再放入老年代了,这时候jvm就会抛出内存溢出的异常。
老年代占整个堆内存的3/8(官方建议)
在进行jvm性能优化的核心要素:尽量延缓FullGC的间隔时间
FullGC的触发条件:
①老年代满了的时候,会触发FullGC
②permgen(持久代)满了的时候,也会触发FullGC
③代码中显示调用System.gc();Runtime.getRuntime().gc();也会触发FullGC
④一些悲观策略RMI(基于tcp/ip通信)框架中,会定时的显示调用System.gc();,会触发FullGC
⑤Jmap-dump时,也会触发FullGC
2、JVM内存监控
1)jconsole.exe
jconsole.exe连接服务器tomcat
A、运行本地JDK,/bin目录下的jconsole.exe
B、配置linux服务端的catalina.sh文件,在文件的前边加上配置:
JAVA_OPTS="-Xms256m -Xmx256m -Xss1024K -XX:PermSize=128m -XX:MaxPermSize=128m -Djava.rmi.server.hostname=192.168.20.129 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=1090 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"
重启tomcat,在关闭tomcat服务时,报错:
Using CATALINA_BASE: /usr/local/MyFiles/apache-tomcat-8.5.15
Using CATALINA_HOME: /usr/local/MyFiles/apache-tomcat-8.5.15
Using CATALINA_TMPDIR: /usr/local/MyFiles/apache-tomcat-8.5.15/temp
Using JRE_HOME: /usr/local/MyFiles/jdk1.8.0_131
Using CLASSPATH: /usr/local/MyFiles/apache-tomcat-8.5.15/bin/bootstrap.jar:/usr/local/MyFiles/apache-tomcat-8.5.15/bin/tomcat-juli.jar
Java HotSpot(TM) Client VM warning: ignoring option PermSize=128m; support was removed in 8.0
Java HotSpot(TM) Client VM warning: ignoring option MaxPermSize=128m; support was removed in 8.0
错误: 代理抛出异常错误: java.net.MalformedURLException: Local host name unknown: java.net.UnknownHostException: Linz: Linz: 未知的名称或服务
tomcat无法关闭
解决办法:
vi /etc/hosts ,在文件127.0.0.1的末尾加上主机名
重启tomcat
C、使用windows端JDK,bin目录中的jconsole.exe连接服务端:
点击连接
至此使用window端的jconsole.exe连接服务器的tomcat成功
2)jmap
A、jmap -heap pid → 发现是否内存泄漏
B、jmap -histo pid > test.txt → 打印堆内存的快照信息,找内存溢出的原因
可以确定,上图中的cn.test.TestBean是自己写的代码
进入项目目录cd ../webapps/test1,查看代码:
vi init1.jsp
泄漏是过程,溢出是结果,由此可知TestMain.list.add(b);是造成内存溢出的原因
C、jmap -dump:live,format=b,file=lhy0523.bin pid
这时候在/usr/local/MyFiles/apache-tomcat-8.5.15/webapps/test1文件夹中生成了一个lhy0523.bin文件
注意:在用jmap命令dump的时候,需要关闭浏览器访问的http://192.168.20.129:8080/test1/init1.jsp网页,要不dump不下来
D、使用jhat -J-mx512m lhy0523.bin命令对lhy0523.bin文件进行解析:
上图报错,说明内存不过,需要将命令的内存改大些:jhat -J-mx900m lhy0523.bin,如下图:
这时候,就可以在客户端浏览器中,用IP加端口号7000,访问:
在此处由于虚拟机配置的原因,花费较长时间,需要说明的是,在对test1进行压测的时候,要时刻跟踪日志,看到日志报内存溢出,要及时停止压测,以避免C步骤中通过jmap -dump:live,format=b,file=filename.bin pid 命令生成的bin文件过大,而导致在该步骤D中使用jhat -J-mx900m filename.bin这个命令由于内存不够,而导致内存溢出。
3)jstack
作用:将dump线程栈信息(方法调用)
用法:jstack 1773 > test.txt
jstack → 分析线程栈(cpu高、IO高、负载高)执行调用方法、获取资源
线程栈的状态分析:
①查看线程的状态,看有没有block状态,at后边是在执行什么调用的是什么方法
②应用程序代码响应时间比较长,cpu、负载不高→分析线程栈中的waiting状态,查看线程在等待什么,等待的资源,就是造成系统性能瓶颈的原因
③cpu高→running,查看在获取什么资源信息
定位uscpu高的原因:查看消耗查看cpu高的进程,再看该进程下的线程,再看占用cpu高的线程执行的方法,这个执行的方法就是造成cpu高的原因
A、top -H -p pid → 查看进程下的线程,分析哪个线程占用cpu高(消耗cpu高的线程,看TIME+,谁占用时间高,就是消耗cpu最高的线程)
B、使用jstack命令,查看线程栈,并且重定向到一个文件中,将A中,线程的id,转化为16进制的,在重定向中的文件查找,从而找到方法
另外一种查看进程下,线程的方法:ps -mp pid -o THREAD,tid,time → 替换掉pid
linux下,将十进制转化为十六进制:printf "%x " tid → tid为线程id
4)jstat
作用:监听垃圾回收,内存使用的情况
jstat -gcutil pid 3000 20 → 监控垃圾回收情况(YGC的时间、次数、FullGC的频率从而对代码、jvm配置进行优化)
命令监视对应用程序造成的性能影响最小
任何监控工具都会耗费系统性能,所以在进行监控的时候,最好使用命令行工具,对性能干扰最小
------------------------------------------------------------
想为你去蹦极,诉说心中对你的眷念... ...