首页 > Java > JAVA 性能调优

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>

其中就是jps列出的进程号,使用这个命令就会导出一个heap.bin的文件,那么我们就可以分析这个文件,来找出那个类占用内存最多了,那么我们怎么打开这个文件呢?我们可以借用一个工具:MemoryAnalyzer,至于这个工具怎么用,大家可以自己去网上找一下资料看看

需要说明的是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,http://www.bridgeli.cn/
原文链接:https://www.bridgeli.cn/archives/156
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。
分类: Java 标签: , ,
  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.