JDK提供给我们很多小工具,可供我们使用去分析当前内存的一些数据。
jps(JVM Process Status)
可以列出正在运行的虚拟机进程,以及主类名称和唯一ID。
public class TestOom {
public static void main(final String[] args) throws Exception {
while (true){
}
}
}
基于上述简单代码,使用命令可以看到:
虽然简单,但是它是我们使用工具时最开始的一步获取PID。一些参数如下:
jstat(JVM Statistics Monitoring Tool)
用来监视虚拟机各种运行状态信息的命令行工具,应该说没有GUI界面的情况下,它是首选工具。
jstat [ option vmid [ interval [ s | ms ] [ count ] ] ]
对于本机而言,vmid和lvmid是一致的,参数inteval和count代表查询间隔和次数,省略代表查询一次,假设每250毫秒查询一次进程2764的垃圾收集状况,一共查询20此:
jstat -gc 2764 250 20
主要参数列表:
接着上次的例子:
E(Eden区)使用了28.21%。
S0和S1(Survivor区),互为备份的。
O(Old区,老年代),没有使用。
P(永久代),使用了32.02%。
YGC(Minor GC)发生0次。
YGCT(Minor GC总耗时)0s。
FGC(Full GC)发生0次。
FGCT(Full GC总耗时)0s。
GCT(所有GC总耗时)0s。
jinfo(Configuration Info for Java)
Java配置信息工具,实时查看和调整虚拟机各项参数。感觉这个用的肯定是非常少的,不去看了,知道下。
jmap(Memory Map for Java)
用来生成堆转储快照(heapdump或者dump文件)。
1.-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/dump/
配置上述两个参数可以在出现OOM时生成dump文件和保存路径。
2.Kill -3命令发送退出信号让虚拟机生成dump文件。
windows平台功能受限制,演示不了。不去关注。
jstack(Stack Trace for Java)
看名字其实就能理解了,生成的是虚拟机当前时刻的线程快照(threaddump或者javacore文件)。线程快照是当前虚拟机内每一条线程正在执行的方法堆栈的集合。
目的:定位线程出现长时间停顿的原因,如死锁,死循环,请求外部资源导致的长时间等待。
使用如下(截取关键部分):
因为程序是个无线循环,所以一直运行到这边。
拿一个喜欢乐见的例子来分析一下看看,死锁。
public class TestOom {
public static Object lock1 = new Object();
public static Object lock2 = new Object();
public static void main(final String[] args) throws Exception {
new Thread(new Runnable() {
public void run() {
synchronized (lock1){
try {
Thread.sleep(100);
}catch (Exception e){
}
synchronized (lock2){
System.out.println("yoh,lock2");
}
}
}
}).start();
new Thread(new Runnable() {
public void run() {
synchronized (lock2){
try {
}catch (Exception e){
}
synchronized (lock1){
System.out.println("yoh,lock1");
}
}
}
}).start();
}
}
结果截图,部分:
很清楚,死锁位置,出错原因都有,神器有木有。
jhat(JVM Heap AnalysisTool)
用来分析生成的dump文件的。但是作者不推荐使用它去分析:
1. 一般不会在服务器内去直接分析dump文件,可以复制到其他环境中去,使用更方便的工具。
2. 比较简陋。
试验一下吧,文件就拿前一篇里生成的dump文件吧。
1. jhat 文件
2. 访问 localhost: 7000
的确不好用,很卡,这个文件才27M,一般的dump文件不得上天啊。但是简单的来看还是能看见,因为我们的例子简单。
Visual VM
网上推荐下面一款Visual VM。在jdk1.7.0_17\bin\jvisualvm.exe。应该是这个吧。
打开我们上面的那个dump文件去看看。
非常清晰:jstack是命令行神器,这个工具应该就是GUI下的神器了吧。
摘要里面也清晰的列出了内存溢出的具体代码位置:
另外参考网上一篇博文的分析,很全面:
常见例子上手:
代码问题很容易导致内存溢出,如果你没有遇见过,只是你项目的并发太少,经得起访问。
我们项目内有这样一段代码,其实平时没多大问题,但是在我最近开始关注JVM内存溢出和做了一些测试之后,发现这段代码还是存在不少问题的。
一些前提:
1. 项目的架构中针对dao层的方法有一些固定方法,比如selectByPrimaryId,或者deleteByPrimaryId。这都是常见的,你新建的时候只要继承就能直接使用。
但是针对非主键的查询没有固定,这是由于不确定性造成的,也没法统一,因此这种查询还是需要你自己去新建,比如selectByid(Integer id),这个id可能是一个序号,类似于外键。
但是项目里有这个方法List<Model> selectByQueryObject(Model object)。也就是你可以通过构造一个查询model,往里面塞值,比如说塞id,就能查询出一个list。这样你自己就可以偷懒不需要重新写一个查询方法了。
带来的问题:
这就带来了一个问题,涉及到比如说日志表的查询的时候,你本来只想获取某个用户Id的最新的一条数据的,但是你还是通过一个list查询,然后代码里获取get(0)了。
1. 并发少的时候吃得消,毕竟新建的对象又不大。
2.日志表数据量小的时候也无所谓,但是数据量一旦大起来,查询本身就很费力。
并发大+数据量大的时候这种代码立马就会增加开销。基于两个场景问题,我进行了测验。
场景设置:
1. 数据库:
数据库准备了110万数据,查询字段针对的是age字段,类比外键字段。模仿的场景就是age代表一个用户id,而所代表的数据就是它的日志。
2. 服务端:
@Controller
@RequestMapping("/test")
public class TestController {
@Resource(name = "userServiceImpl")
private UserService userService;
@RequestMapping("/testq")
public void showStudent(Integer id) throws Exception {
List<User> a = userService.selectList(id);
if(!a.isEmpty()){
System.out.println("this name is: "+a.get(0).getName());
}
}
}
mapper文件,模仿的就是一个list查询,只不过此处省略了对象的构造。
<select id="selectList" resultType="com.system.po.User">
select * from user where age = #{id} order by create_time desc
</select>
3. 客户端:
public class TestJava {
public static int [] arr = {114,11,23,12,42,56,13,14,15,16,17,110,100,1,2,3,4,5,6,7,8,9,10,18,19,20,21,22,24,25};
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final CountDownLatch cdOrder = new CountDownLatch(1);
final CountDownLatch cdAnswer = new CountDownLatch(120);
for(int i=0;i<120;i++){
Runnable runnable = new Runnable(){
public void run(){
try {
cdOrder.await();
try {
String s = "http://localhost:8080/test/testq.htm?id=";
int index=(int)(Math.random()*arr.length);
s+=arr[index];
URL url = new URL(s);
URLConnection URLconnection = url.openConnection();
HttpURLConnection httpConnection = (HttpURLConnection) URLconnection;
int responseCode = httpConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
System.err.println("成功");
} else {
System.err.println("失败");
}
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
cdAnswer.countDown();
}
}
};
service.execute(runnable);
}
try {
Thread.sleep((long)(Math.random()*10000));
cdOrder.countDown();
cdAnswer.await();
} catch (Exception e) {
e.printStackTrace();
} finally {
}
service.shutdown();
}
客户端通过CountDownLatch和线程池来模拟瞬时高并发。
3. 结果:
启动时我设置了服务端内存为512M。
3.1 刚启动并且项目稳定运行时的截图:常见的一些实例和大小,没有特别异常的地方。
3.2 执行瞬时并发时的堆情况:很明显的可以看见实体类和Integer太多了,这还只是查询,没有涉及到具体的业务代码。
4. 优化:
优化可以从两个方面来:4.1 代码层面:把这种list查询换成精确查询,因为只是获取最新的一个,所以只拿出一个就行,别偷懒用这种list查询。
4.2 数据库层面:加索引,减少查询时间。