JAVA 性能调优
学习Java性能调优之前,我们必须得先了解Java中的内存分配:堆、栈、非堆
为了更好的说明这个问题,我们先看一个程序:
package cn.bridgeli.demo; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; public class Test { public void test() { List<Email> emails = new ArrayList<Email>(); Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { Class.forName(""); String url = ""; conn = DriverManager.getConnection(url, "", ""); pstmt = conn.prepareStatement(""); rs = pstmt.executeQuery(); Email email = null; while (rs.next()) { email = new Email(); email.setSubject(""); emails.add(email); } } catch (Exception e) { e.printStackTrace(); } finally { // close conn } } }
在这段代码中,那些哪些数据放在堆上,哪些数据放在栈上,又有哪些数据放在方法去呢?一言以蔽之:在方法中,“=”左边的值,全部放在栈上,占4个字节,而“=”右边的肯定就是放在堆上了,所以该段代码中像:emails、conn的变量都是放在栈上的,而我们 new 出来的全部放在了堆上,而方法区是没有放东西的,既然new 出来的Email是放在堆上的,那么Email中的这些变量又是放在哪呢?
package cn.bridgeli.demo; public class Email { private String subject; public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } }
是不是和方法中的变量一样呢?当然不是,Email中subject是放在堆上的,如果Email中在有一个对象User,表示Email属于哪个User,那么同样该User也是放在堆上的,那么方法区到底会放些什么呢?其实很简单,static的变量和Class的结构信息、字段描述、方法描述。好了,这样我们就知道了我们项目中的所有变量放在了内存中的那个地方。下面我们先看Java中的栈内存:
1. 栈
开启一个线程时,同时会开启一个线程栈(或者叫方法栈),至于这些变量怎么放的怎么弹出的,相信不用我多说了,大家都明白的,在JVM中,栈默认的大小是 1 M,为了说明这个问题,我们可以看一个例子:
package cn.bridgeli.demo; public class StackOOM { public void stackLeak() { int i = 0; int j = 0; stackLeak(); } public static void main(String[] args) { StackOOM stackOOM = new StackOOM(); stackOOM.stackLeak(); } }
然后我们把它跑起来,就可以看到这段代码会报:
Exception in thread "main" java.lang.StackOverflowError at cn.bridgeli.demo.StackOOM.stackLeak(StackOOM.java:7) at cn.bridgeli.demo.StackOOM.stackLeak(StackOOM.java:7) at cn.bridgeli.demo.StackOOM.stackLeak(StackOOM.java:7) at cn.bridgeli.demo.StackOOM.stackLeak(StackOOM.java:7) at cn.bridgeli.demo.StackOOM.stackLeak(StackOOM.java:7) at cn.bridgeli.demo.StackOOM.stackLeak(StackOOM.java:7) at cn.bridgeli.demo.StackOOM.stackLeak(StackOOM.java:7) at cn.bridgeli.demo.StackOOM.stackLeak(StackOOM.java:7) at cn.bridgeli.demo.StackOOM.stackLeak(StackOOM.java:7) at cn.bridgeli.demo.StackOOM.stackLeak(StackOOM.java:7) at cn.bridgeli.demo.StackOOM.stackLeak(StackOOM.java:7) at cn.bridgeli.demo.StackOOM.stackLeak(StackOOM.java:7) at cn.bridgeli.demo.StackOOM.stackLeak(StackOOM.java:7) ...
太多了,就用…代替了,但是我们可以明显的看到是:StackOverflowError,就是栈溢出,同时为了更快的展示效果我们可以在jvm启动的时候设置-Xss 128k,即把默认的 1 M 改成 128k,这段代码之所以出问题了,就是我们的递归除了问题,弄了无数的i和j放在了栈上,如果你的代码没有明显的报这个错误而是OutOfMemoryError,你也可以在启动的是加入:-XX:UseGCOverheadLimit,下面我们我们接着看jvm堆大小的设置:
2. 堆
同样我们看一段代码:
package cn.bridgeli.demo; import java.util.ArrayList; import java.util.List; public class HeapOOM { public static void main(String[] args) { List<Email> emails = new ArrayList<Email>(); Email email = null; while (true) { email = new Email(); email.setSubject("Hello World"); emails.add(email); } } }
在这段代码中,我们不停的new Email对象也就是在堆上放了很多对象,以至于最后报出了:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:2760) at java.util.Arrays.copyOf(Arrays.java:2734) at java.util.ArrayList.ensureCapacity(ArrayList.java:167) at java.util.ArrayList.add(ArrayList.java:351) at cn.bridgeli.demo.StackOOM.main(StackOOM.java:14)
同样为了更快的看到效果,我们可以设置堆的大小:-Xms20m -Xmx20m -XX:UseGCOverheadLimit
3. 方法区
至于方法区的验证,我们就不做了,因为代码中静态的变量一般不多,所以一般也不会报这个异常,大家可以用String类的intern()方法验证,设置持久带的大小可以用-XX:PermSize=5M -xxMaxPermSize=5M -XX:UseGCOverheadLimit
注:这个方法,大家测试一下就好了,在项目中千万不要用,如果感兴趣的可以自己Google一下怎么玩。
看了栈、堆、方法区的分配之后,我们可以看看常用的调试的命令:
1. jps:列出机器上的所有的jvm,包括进程名和进程号
2. jconsole:连接jvm进程
一般我们看到jvm的最大内存使用情况几乎是平的,那么就说明内存是没有问题的,但是如果是越来越高,那么你就要查一下原因了。那么gc出现之后,我们怎么看哪一个类占用内存最多呢?接下来就要用到我们的下一个命令了
3. jmap:导出内存分析包
使用方法:
jmap -dump:live,format=b,file=heap.bin <pid>
其中
需要说明的是full gc是不能完全回收内存中的持久带的,所以使用Spring框架的项目,隔一段间虽好重启一下服务器
最后再说一下在tomcat中,怎么设置JVM内存:
我们可以再jvm启动的添加一下代码:
#catalina.bat #set JAVA_OPTS='-Xms512m -Xmx1024m -XX:MaxPermSize=512m' JAVA_OPTS="$JAVA_OPTS -server -Xms2048m -Xmx2048m -XX:PermSize=128M -Xss512K -XX:MaxNewSize=256m -XX:MaxPermSize=256m -Djava.awt.headless=true" JAVA_OPTS="$JAVA_OPTS -verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -XX:DisableExplicitGC -Xloggc:/usr/local/logs/gc/gc_'date''+%Y-%m-%d''.log"
即添加到
elif ["s1" = "start"]; then
下面;而tomcat中线程的配置我们可以打开:
<Executor name="tomcatThreadPool" namePrefix="catalina-exce-" maxThreads="300" minSpareThreads="50"/> <Connector executor="tomcatThreadPool" port="8080" URLEncoding="UTF-8" connectionTimeout="20000" caseSensitive="false" redirectPort="9443" enableLookups="false" acceptCount="200" protocol="prg.apache.coyote.http11.Http11NioProtocol"/>
作 者: BridgeLi,https://www.bridgeli.cn
原文链接:http://www.bridgeli.cn/archives/156
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。
近期评论