zoukankan      html  css  js  c++  java
  • 服务器的tomcat调优和jvm调化

    下面讲述的是tomcat的优化,及jvm的优化

    Tomcat 的缺省配置是不能稳定长期运行的,也就是不适合生产环境,它会死机,让你不断重新启动,甚至在午夜时分唤醒你。对于操作系统优化来说,是尽可能的增大可使用的内存容量、提高CPU 的频率,保证文件系统的读写速率等。经过压力测试验证,在并发连接很多的情况下,CPU 的处理能力越强,系统运行速度越快。

    Tomcat 7.png

    Tomcat 的优化不像其它软件那样,简简单单的修改几个参数就可以了,它的优化主要有三方面,分为系统优化,Tomcat 本身的优化,Java 虚拟机(JVM)调优。系统优化就不在介绍了,接下来就详细的介绍一下 Tomcat 本身与 JVM 优化,以 Tomcat 7 为例。

    一、Tomcat 本身优化

    Tomcat 的自身参数的优化,这块很像 ApacheHttp Server。修改一下 xml 配置文件中的参数,调整最大连接数,超时等。此外,我们安装 Tomcat 是,优化就已经开始了。

    1、工作方式选择

    为了提升性能,首先就要对代码进行动静分离,让 Tomcat 只负责 jsp 文件的解析工作。如采用 Apache 和 Tomcat 的整合方式,他们之间的连接方案有三种选择,JK、http_proxy 和 ajp_proxy。相对于 JK 的连接方式,后两种在配置上比较简单的,灵活性方面也一点都不逊色。但就稳定性而言不像JK 这样久经考验,所以建议采用 JK 的连接方式。 

    2、Connector 连接器的配置

    之前文件介绍过的 Tomcat 连接器的三种方式: bio、nio 和 apr,三种方式性能差别很大,apr 的性能最优, bio 的性能最差。而 Tomcat 7 使用的 Connector  默认就启用的 Apr 协议,但需要系统安装 Apr 库,否则就会使用 bio 方式。

    3、配置文件优化

    配置文件优化其实就是对 server.xml 优化,可以提大大提高 Tomcat 的处理请求的能力,下面我们来看 Tomcat 容器内的优化。

    默认配置下,Tomcat 会为每个连接器创建一个绑定的线程池(最大线程数 200),服务启动时,默认创建了 5 个空闲线程随时等待用户请求。

    首先,打开 ${TOMCAT_HOME}/conf/server.xml,搜索【<Executor name="tomcatThreadPool"】,开启并调整为

      <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
    
            maxThreads="500" minSpareThreads="20" maxSpareThreads="50" maxIdleTime="60000"/>

    注意, Tomcat 7 在开启线程池前,一定要安装好 Apr 库,并可以启用,否则会有错误报出,shutdown.sh 脚本无法关闭进程。

    然后,修改<Connector …>节点,增加 executor 属性,搜索【port="8080"】,调整为

     <Connector executor="tomcatThreadPool"
    
                   port="8080" protocol="HTTP/1.1"
    
                   URIEncoding="UTF-8"
    
                   connectionTimeout="30000"
    
                   enableLookups="false"
    
                   disableUploadTimeout="false"
    
                   connectionUploadTimeout="150000"
    
                   acceptCount="300"
    
                   keepAliveTimeout="120000"
    
                   maxKeepAliveRequests="1"
    
                   compression="on"
    
                   compressionMinSize="2048"
    
                   compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain,image/gif,image/jpg,image/png" 
    
                   redirectPort="8443" />

    maxThreads :Tomcat 使用线程来处理接收的每个请求,这个值表示 Tomcat 可创建的最大的线程数,默认值是 200

    minSpareThreads:最小空闲线程数,Tomcat 启动时的初始化的线程数,表示即使没有人使用也开这么多空线程等待,默认值是 10。

    maxSpareThreads:最大备用线程数,一旦创建的线程超过这个值,Tomcat 就会关闭不再需要的 socket 线程。

    上边配置的参数,最大线程 500(一般服务器足以),要根据自己的实际情况合理设置,设置越大会耗费内存和 CPU,因为 CPU 疲于线程上下文切换,没有精力提供请求服务了,最小空闲线程数 20,线程最大空闲时间 60 秒,当然允许的最大线程连接数还受制于操作系统的内核参数设置,设置多大要根据自己的需求与环境。当然线程可以配置在“tomcatThreadPool”中,也可以直接配置在“Connector”中,但不可以重复配置。

    URIEncoding:指定 Tomcat 容器的 URL 编码格式,语言编码格式这块倒不如其它 WEB 服务器软件配置方便,需要分别指定。

    connnectionTimeout: 网络连接超时,单位:毫秒,设置为 0 表示永不超时,这样设置有隐患的。通常可设置为 30000 毫秒,可根据检测实际情况,适当修改。

    enableLookups: 是否反查域名,以返回远程主机的主机名,取值为:true 或 false,如果设置为false,则直接返回IP地址,为了提高处理能力,应设置为 false。

    disableUploadTimeout:上传时是否使用超时机制。

    connectionUploadTimeout:上传超时时间,毕竟文件上传可能需要消耗更多的时间,这个根据你自己的业务需要自己调,以使Servlet有较长的时间来完成它的执行,需要与上一个参数一起配合使用才会生效。

    acceptCount:指定当所有可以使用的处理请求的线程数都被使用时,可传入连接请求的最大队列长度,超过这个数的请求将不予处理,默认为100个。

    keepAliveTimeout:长连接最大保持时间(毫秒),表示在下次请求过来之前,Tomcat 保持该连接多久,默认是使用 connectionTimeout 时间,-1 为不限制超时。

    maxKeepAliveRequests:表示在服务器关闭之前,该连接最大支持的请求数。超过该请求数的连接也将被关闭,1表示禁用,-1表示不限制个数,默认100个,一般设置在100~200之间。

    compression:是否对响应的数据进行 GZIP 压缩,off:表示禁止压缩;on:表示允许压缩(文本将被压缩)、force:表示所有情况下都进行压缩,默认值为off,压缩数据后可以有效的减少页面的大小,一般可以减小1/3左右,节省带宽。

    compressionMinSize:表示压缩响应的最小值,只有当响应报文大小大于这个值的时候才会对报文进行压缩,如果开启了压缩功能,默认值就是2048。

    compressableMimeType:压缩类型,指定对哪些类型的文件进行数据压缩。

    noCompressionUserAgents="gozilla, traviata": 对于以下的浏览器,不启用压缩。

    如果已经对代码进行了动静分离,静态页面和图片等数据就不需要 Tomcat 处理了,那么也就不需要配置在 Tomcat 中配置压缩了。

    以上是一些常用的配置参数属性,当然还有好多其它的参数设置,还可以继续深入的优化,HTTP Connector 与 AJP Connector 的参数属性值,可以参考官方文档的详细说明:

    https://tomcat.apache.org/tomcat-7.0-doc/config/http.html

    https://tomcat.apache.org/tomcat-7.0-doc/config/ajp.html

    二、JVM 优化

     Tomcat 启动命令行中的优化参数,就是 JVM 的优化 。Tomcat 首先跑在 JVM 之上的,因为它的启动其实也只是一个 java 命令行,首先我们需要对这个 JAVA 的启动命令行进行调优。不管是 YGC 还是 Full GC,GC 过程中都会对导致程序运行中中断,正确的选择不同的 GC 策略,调整 JVM、GC 的参数,可以极大的减少由于 GC 工作,而导致的程序运行中断方面的问题,进而适当的提高 Java 程序的工作效率。但是调整 GC 是以个极为复杂的过程,由于各个程序具备不同的特点,如:web 和 GUI 程序就有很大区别(Web可以适当的停顿,但GUI停顿是客户无法接受的),而且由于跑在各个机器上的配置不同(主要 cup 个数,内存不同),所以使用的 GC 种类也会不同。

    1、JVM 参数配置方法

    Tomcat 的启动参数位于安装目录 ${JAVA_HOME}/bin目录下,Linux 操作系统就是 catalina.sh 文件。JAVA_OPTS,就是用来设置 JVM 相关运行参数的变量,还可以在 CATALINA_OPTS 变量中设置。关于这 2 个变量,还是多少有些区别的:

    JAVA_OPTS:用于当 Java 运行时选项“start”、“stop”或“run”命令执行。

    CATALINA_OPTS:用于当 Java 运行时选项“start”或“run”命令执行。

    为什么有两个不同的变量?它们之间都有什么区别呢?

    首先,在启动 Tomcat 时,任何指定变量的传递方式都是相同的,可以传递到执行“start”或“run”命令中,但只有设定在 JAVA_OPTS 变量里的参数被传递到“stop”命令中。对于 Tomcat 运行过程,可能没什么区别,影响的是结束程序,而不是启动程序。

    第二个区别是更微妙,其他应用程序也可以使用 JAVA_OPTS 变量,但只有在 Tomcat 中使用 CATALINA_OPTS 变量。如果你设置环境变量为只使用 Tomcat,最好你会建议使用 CATALINA_OPTS 变量,而如果你设置环境变量使用其它的 Java 应用程序,例如 JBoss,你应该把你的设置放在JAVA_OPTS 变量中。

    2、JVM 参数属性

    32 位系统下 JVM 对内存的限制:不能突破 2GB ,那么这时你的 Tomcat 要优化,就要讲究点技巧了,而在 64 位操作系统上无论是系统内存还是 JVM 都没有受到 2GB 这样的限制。

    针对于 JMX 远程监控也是在这里设置,以下为 64 位系统环境下的配置,内存加入的参数如下:

    CATALINA_OPTS="
    
    -server 
    
    -Xms6000M 
    
    -Xmx6000M 
    
    -Xss512k 
    
    -XX:NewSize=2250M 
    
    -XX:MaxNewSize=2250M 
    
    -XX:PermSize=128M
    
    -XX:MaxPermSize=256M  
    
    -XX:+AggressiveOpts 
    
    -XX:+UseBiasedLocking 
    
    -XX:+DisableExplicitGC 
    
    -XX:+UseParNewGC 
    
    -XX:+UseConcMarkSweepGC 
    
    -XX:MaxTenuringThreshold=31 
    
    -XX:+CMSParallelRemarkEnabled 
    
    -XX:+UseCMSCompactAtFullCollection 
    
    -XX:LargePageSizeInBytes=128m 
    
    -XX:+UseFastAccessorMethods 
    
    -XX:+UseCMSInitiatingOccupancyOnly
    
    -Duser.timezone=Asia/Shanghai 
    
    -Djava.awt.headless=true"

    为了看着方便,将每个参数单独写一行。上面参数好多啊,可能有人写到现在都没见过一个在 Tomcat 的启动命令里加了这么多参数,当然,这些参数只是我机器上的,不一定适合你,尤其是参数后的 value(值)是需要根据你自己的实际情况来设置的。

    上述这样的配置,基本上可以达到:

    系统响应时间增快;

    JVM回收速度增快同时又不影响系统的响应率;

    JVM内存最大化利用;

    线程阻塞情况最小化。

    JVM 常用参数详解:

    -server:一定要作为第一个参数,在多个 CPU 时性能佳,还有一种叫 -client 的模式,特点是启动速度比较快,但运行时性能和内存管理效率不高,通常用于客户端应用程序或开发调试,在 32 位环境下直接运行 Java 程序默认启用该模式。Server 模式的特点是启动速度比较慢,但运行时性能和内存管理效率很高,适用于生产环境,在具有 64 位能力的 JDK 环境下默认启用该模式,可以不配置该参数。

    -Xms:表示 Java 初始化堆的大小,-Xms 与-Xmx 设成一样的值,避免 JVM 反复重新申请内存,导致性能大起大落,默认值为物理内存的 1/64,默认(MinHeapFreeRatio参数可以调整)空余堆内存小于 40% 时,JVM 就会增大堆直到 -Xmx 的最大限制。

    -Xmx:表示最大 Java 堆大小,当应用程序需要的内存超出堆的最大值时虚拟机就会提示内存溢出,并且导致应用服务崩溃,因此一般建议堆的最大值设置为可用内存的最大值的80%。如何知道我的 JVM 能够使用最大值,使用 java -Xmx512M -version 命令来进行测试,然后逐渐的增大 512 的值,如果执行正常就表示指定的内存大小可用,否则会打印错误信息,默认值为物理内存的 1/4,默认(MinHeapFreeRatio参数可以调整)空余堆内存大于 70% 时,JVM 会减少堆直到-Xms 的最小限制。

    -Xss:表示每个 Java 线程堆栈大小,JDK 5.0 以后每个线程堆栈大小为 1M,以前每个线程堆栈大小为 256K。根据应用的线程所需内存大小进行调整,在相同物理内存下,减小这个值能生成更多的线程,但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在 3000~5000 左右。一般小的应用, 如果栈不是很深, 应该是128k 够用的,大的应用建议使用 256k 或 512K,一般不易设置超过 1M,要不然容易出现out ofmemory。这个选项对性能影响比较大,需要严格的测试。

    -XX:NewSize:设置新生代内存大小。

    -XX:MaxNewSize:设置最大新生代新生代内存大小

    -XX:PermSize:设置持久代内存大小

    -XX:MaxPermSize:设置最大值持久代内存大小,永久代不属于堆内存,堆内存只包含新生代和老年代。

    -XX:+AggressiveOpts:作用如其名(aggressive),启用这个参数,则每当 JDK 版本升级时,你的 JVM 都会使用最新加入的优化技术(如果有的话)。

    -XX:+UseBiasedLocking:启用一个优化了的线程锁,我们知道在我们的appserver,每个http请求就是一个线程,有的请求短有的请求长,就会有请求排队的现象,甚至还会出现线程阻塞,这个优化了的线程锁使得你的appserver内对线程处理自动进行最优调配。

    -XX:+DisableExplicitGC:在 程序代码中不允许有显示的调用“System.gc()”。每次在到操作结束时手动调用 System.gc() 一下,付出的代价就是系统响应时间严重降低,就和关于 Xms,Xmx 里的解释的原理一样,这样去调用 GC 导致系统的 JVM 大起大落。

    -XX:+UseConcMarkSweepGC:设置年老代为并发收集,即 CMS gc,这一特性只有 jdk1.5
    后续版本才具有的功能,它使用的是 gc 估算触发和 heap 占用触发。我们知道频频繁的 GC 会造面 JVM
    的大起大落从而影响到系统的效率,因此使用了 CMS GC 后可以在 GC 次数增多的情况下,每次 GC 的响应时间却很短,比如说使用了 CMS
    GC 后经过 jprofiler 的观察,GC 被触发次数非常多,而每次 GC 耗时仅为几毫秒。

    -XX:+UseParNewGC:对新生代采用多线程并行回收,这样收得快,注意最新的 JVM 版本,当使用 -XX:+UseConcMarkSweepGC 时,-XX:UseParNewGC 会自动开启。因此,如果年轻代的并行 GC 不想开启,可以通过设置 -XX:-UseParNewGC 来关掉。

    -XX:MaxTenuringThreshold:设置垃圾最大年龄。如果设置为0的话,则新生代对象不经过 Survivor 区,直接进入老年代。对于老年代比较多的应用(需要大量常驻内存的应用),可以提高效率。如果将此值设置为一 个较大值,则新生代对象会在 Survivor 区进行多次复制,这样可以增加对象在新生代的存活时间,增加在新生代即被回收的概率,减少Full GC的频率,这样做可以在某种程度上提高服务稳定性。该参数只有在串行 GC 时才有效,这个值的设置是根据本地的 jprofiler 监控后得到的一个理想的值,不能一概而论原搬照抄。

    -XX:+CMSParallelRemarkEnabled:在使用 UseParNewGC 的情况下,尽量减少 mark 的时间。

    -XX:+UseCMSCompactAtFullCollection:在使用 concurrent gc 的情况下,防止 memoryfragmention,对 live object 进行整理,使 memory 碎片减少。

    -XX:LargePageSizeInBytes:指定 Java heap 的分页页面大小,内存页的大小不可设置过大, 会影响 Perm 的大小。

    -XX:+UseFastAccessorMethods:使用 get,set 方法转成本地代码,原始类型的快速优化。

    -XX:+UseCMSInitiatingOccupancyOnly:只有在 oldgeneration 在使用了初始化的比例后 concurrent collector 启动收集。

    -Duser.timezone=Asia/Shanghai:设置用户所在时区。

    -Djava.awt.headless=true:这个参数一般我们都是放在最后使用的,这全参数的作用是这样的,有时我们会在我们的 J2EE 工程中使用一些图表工具如:jfreechart,用于在 web 网页输出 GIF/JPG 等流,在 winodws 环境下,一般我们的 app server 在输出图形时不会碰到什么问题,但是在linux/unix 环境下经常会碰到一个 exception 导致你在 winodws 开发环境下图片显示的好好可是在 linux/unix 下却显示不出来,因此加上这个参数以免避这样的情况出现。

    -Xmn:新生代的内存空间大小,注意:此处的大小是(eden+ 2 survivor space)。与 jmap -heap 中显示的 New gen 是不同的。整个堆大小 = 新生代大小 + 老生代大小 + 永久代大小。在保证堆大小不变的情况下,增大新生代后,将会减小老生代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的 3/8。

    -XX:CMSInitiatingOccupancyFraction:当堆满之后,并行收集器便开始进行垃圾收集,例如,当没有足够的空间来容纳新分配或提升的对象。对于 CMS 收集器,长时间等待是不可取的,因为在并发垃圾收集期间应用持续在运行(并且分配对象)。因此,为了在应用程序使用完内存之前完成垃圾收集周期,CMS 收集器要比并行收集器更先启动。因为不同的应用会有不同对象分配模式,JVM 会收集实际的对象分配(和释放)的运行时数据,并且分析这些数据,来决定什么时候启动一次 CMS 垃圾收集周期。这个参数设置有很大技巧,基本上满足(Xmx-Xmn)*(100-CMSInitiatingOccupancyFraction)/100 >= Xmn 就不会出现 promotion failed。例如在应用中 Xmx 是6000,Xmn 是 512,那么 Xmx-Xmn 是 5488M,也就是老年代有 5488M,CMSInitiatingOccupancyFraction=90 说明老年代到 90% 满的时候开始执行对老年代的并发垃圾回收(CMS),这时还 剩 10% 的空间是 5488*10% = 548M,所以即使 Xmn(也就是新生代共512M)里所有对象都搬到老年代里,548M 的空间也足够了,所以只要满足上面的公式,就不会出现垃圾回收时的 promotion failed,因此这个参数的设置必须与 Xmn 关联在一起。

    -XX:+CMSIncrementalMode:该标志将开启 CMS 收集器的增量模式。增量模式经常暂停 CMS 过程,以便对应用程序线程作出完全的让步。因此,收集器将花更长的时间完成整个收集周期。因此,只有通过测试后发现正常 CMS 周期对应用程序线程干扰太大时,才应该使用增量模式。由于现代服务器有足够的处理器来适应并发的垃圾收集,所以这种情况发生得很少,用于但 CPU情况。

    -XX:NewRatio:年轻代(包括 Eden 和两个 Survivor 区)与年老代的比值(除去持久代),-XX:NewRatio=4 表示年轻代与年老代所占比值为 1:4,年轻代占整个堆栈的 1/5,Xms=Xmx 并且设置了 Xmn 的情况下,该参数不需要进行设置。

    -XX:SurvivorRatio:Eden 区与 Survivor 区的大小比值,设置为 8,表示 2 个 Survivor 区(JVM 堆内存年轻代中默认有 2 个大小相等的 Survivor 区)与 1 个 Eden 区的比值为 2:8,即 1 个 Survivor 区占整个年轻代大小的 1/10。

    -XX:+UseSerialGC:设置串行收集器。

    -XX:+UseParallelGC:设置为并行收集器。此配置仅对年轻代有效。即年轻代使用并行收集,而年老代仍使用串行收集。

    -XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集,JDK6.0 开始支持对年老代并行收集。

    -XX:ConcGCThreads:早期 JVM 版本也叫-XX:ParallelCMSThreads,定义并发 CMS 过程运行时的线程数。比如 value=4 意味着 CMS 周期的所有阶段都以 4 个线程来执行。尽管更多的线程会加快并发 CMS 过程,但其也会带来额外的同步开销。因此,对于特定的应用程序,应该通过测试来判断增加 CMS 线程数是否真的能够带来性能的提升。如果还标志未设置,JVM 会根据并行收集器中的 -XX:ParallelGCThreads 参数的值来计算出默认的并行 CMS 线程数。

    -XX:ParallelGCThreads:配置并行收集器的线程数,即:同时有多少个线程一起进行垃圾回收,此值建议配置与 CPU 数目相等。

    -XX:OldSize:设置 JVM 启动分配的老年代内存大小,类似于新生代内存的初始大小 -XX:NewSize。

    以上就是一些常用的配置参数,有些参数是可以被替代的,配置思路需要考虑的是 Java 提供的垃圾回收机制。虚拟机的堆大小决定了虚拟机花费在收集垃圾上的时间和频度。收集垃圾能够接受的速度和应用有关,应该通过分析实际的垃圾收集的时间和频率来调整。假如堆的大小很大,那么完全垃圾收集就会很慢,但是频度会降低。假如您把堆的大小和内存的需要一致,完全收集就很快,但是会更加频繁。调整堆大小的的目的是最小化垃圾收集的时间,以在特定的时间内最大化处理客户的请求。在基准测试的时候,为确保最好的性能,要把堆的大小设大,确保垃圾收集不在整个基准测试的过程中出现。

    假如系统花费很多的时间收集垃圾,请减小堆大小。一次完全的垃圾收集应该不超过 3-5 秒。假如垃圾收集成为瓶颈,那么需要指定代的大小,检查垃圾收集的周详输出,研究垃圾收集参数对性能的影响。当增加处理器时,记得增加内存,因为分配能够并行进行,而垃圾收集不是并行的。

    3、设置系统属性

    之前说过,Tomcat 的语言编码,配置起来很慢,要经过多次设置才可以了,否则中文很有可能出现乱码情况。譬如汉字“中”,以 UTF-8 编码后得到的是 3 字节的值 %E4%B8%AD,然后通过 GET 或者 POST 方式把这 3 个字节提交到 Tomcat 容器,如果你不告诉 Tomcat 我的参数是用 UTF-8编码的,那么 Tomcat 就认为你是用 ISO-8859-1 来编码的,而 ISO8859-1(兼容 URI 中的标准字符集 US-ASCII)是兼容 ASCII 的单字节编码并且使用了单字节内的所有空间,因此 Tomcat 就以为你传递的用 ISO-8859-1 字符集编码过的 3 个字符,然后它就用 ISO-8859-1 来解码。

    设置起来不难使用“ -D<名称>=<值> ”来设置系统属性:

    -Djavax.servlet.request.encoding=UTF-8

    -Djavax.servlet.response.encoding=UTF-8 

    -Dfile.encoding=UTF-8 

    -Duser.country=CN 

    -Duser.language=zh

    4、常见的 Java 内存溢出有以下三种

    (1) java.lang.OutOfMemoryError: Java heap space —-JVM Heap(堆)溢出

    JVM 在启动的时候会自动设置 JVM Heap 的值,其初始空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)不可超过物理内存。可以利用 JVM提供的 -Xmn -Xms -Xmx 等选项可进行设置。Heap 的大小是 Young Generation 和 Tenured Generaion 之和。在 JVM 中如果 98% 的时间是用于 GC,且可用的 Heap size 不足 2% 的时候将抛出此异常信息。

    解决方法:手动设置 JVM Heap(堆)的大小。  
    (2) java.lang.OutOfMemoryError: PermGen space  —- PermGen space溢出。

    PermGen space 的全称是 Permanent Generation space,是指内存的永久保存区域。为什么会内存溢出,这是由于这块内存主要是被 JVM 存放Class 和 Meta 信息的,Class 在被 Load 的时候被放入 PermGen space 区域,它和存放 Instance 的 Heap 区域不同,sun 的 GC 不会在主程序运行期对 PermGen space 进行清理,所以如果你的 APP 会载入很多 CLASS 的话,就很可能出现 PermGen space 溢出。

    解决方法: 手动设置 MaxPermSize 大小

    (3) java.lang.StackOverflowError   —- 栈溢出

    栈溢出了,JVM 依然是采用栈式的虚拟机,这个和 C 与 Pascal 都是一样的。函数的调用过程都体现在堆栈和退栈上了。调用构造函数的 “层”太多了,以致于把栈区溢出了。通常来讲,一般栈区远远小于堆区的,因为函数调用过程往往不会多于上千层,而即便每个函数调用需要 1K 的空间(这个大约相当于在一个 C 函数内声明了 256 个 int 类型的变量),那么栈区也不过是需要 1MB 的空间。通常栈的大小是 1-2MB 的。
    通常递归也不要递归的层次过多,很容易溢出。

    解决方法:修改程序。

    更多信息,请参考以下文章:

    JVM 垃圾回收调优总结

    http://developer.51cto.com/art/201201/312639.htm

    JVM调优总结:典型配置举例

    http://developer.51cto.com/art/201201/311739.htm

    JVM基础:JVM参数设置、分析 

    http://developer.51cto.com/art/201201/312018.htm

    JVM 堆内存相关的启动参数:年轻代、老年代和永久代的内存分配

    http://www.2cto.com/kf/201409/334840.html

    Java 虚拟机–新生代与老年代GC

    http://my.oschina.net/sunnywu/blog/332870

    JVM(Java虚拟机)优化大全和案例实战

    http://blog.csdn.net/kthq/article/details/8618052

    JVM内存区域划分Eden Space、Survivor Space、Tenured Gen,Perm Gen解释 

    http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=29632145&id=4616836

    原文:http://blog.chopmoon.com/favorites/231.html

    jvm调优参数理解

    在JVM启动参数中,可以设置跟内存、垃圾回收相关的一些参数设置,默认情况不做任何设置JVM会工作的很好,但对一些配置很好的Server和具体的应用必须仔细调优才能获得最佳性能。通过设置我们希望达到一些目标:

    • GC的时间足够的小
    • GC的次数足够的少
    • 发生Full GC的周期足够的长

      前两个目前是相悖的,要想GC时间小必须要一个更小的堆,要保证GC次数足够少,必须保证一个更大的堆,我们只能取其平衡。

       (1)针对JVM堆的设置,一般可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,我们通常把最大、最小设置为相同的值
       (2)年轻代和年老代将根据默认的比例(1:2)分配堆内存,可以通过调整二者之间的比率NewRadio来调整二者之间的大小,也可以针对回收代,比如年轻代,通过 -XX:newSize -XX:MaxNewSize来设置其绝对大小。同样,为了防止年轻代的堆收缩,我们通常会把-XX:newSize -XX:MaxNewSize设置为同样大小

       (3)年轻代和年老代设置多大才算合理?这个我问题毫无疑问是没有答案的,否则也就不会有调优。我们观察一下二者大小变化有哪些影响

    • 更大的年轻代必然导致更小的年老代,大的年轻代会延长普通GC的周期,但会增加每次GC的时间;小的年老代会导致更频繁的Full GC
    • 更小的年轻代必然导致更大年老代,小的年轻代会导致普通GC很频繁,但每次的GC时间会更短;大的年老代会减少Full GC的频率
    • 如何选择应该依赖应用程序对象生命周期的分布情况:如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该适当增大。但很多应用都没有这样明显的特性,在抉择时应该根据以下两点:(A)本着Full GC尽量少的原则,让年老代尽量缓存常用对象,JVM的默认比例1:2也是这个道理 (B)通过观察应用一段时间,看其他在峰值时年老代会占多少内存,在不影响Full GC的前提下,根据实际情况加大年轻代,比如可以把比例控制在1:1。但应该给年老代至少预留1/3的增长空间

      (4)在配置较好的机器上(比如多核、大内存),可以为年老代选择并行收集算法: -XX:+UseParallelOldGC ,默认为Serial收集

      (5)线程堆栈的设置:每个线程默认会开启1M的堆栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太了,一般256K就足用。理论上,在内存不变的情况下,减少每个线程的堆栈,可以产生更多的线程,但这实际上还受限于操作系统。

      (4)可以通过下面的参数打Heap Dump信息

    • -XX:HeapDumpPath
    • -XX:+PrintGCDetails
    • -XX:+PrintGCTimeStamps
    • -Xloggc:/usr/aaa/dump/heap_trace.txt

        通过下面参数可以控制OutOfMemoryError时打印堆的信息

    • -XX:+HeapDumpOnOutOfMemoryError

     请看一下一个时间的Java参数配置:(服务器:Linux 64Bit,8Core×16G)

     JAVA_OPTS="$JAVA_OPTS -server -Xms3G -Xmx3G -Xss256k -XX:PermSize=128m -XX:MaxPermSize=128m -XX:+UseParallelOldGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/aaa/dump -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/usr/aaa/dump/heap_trace.txt -XX:NewSize=1G -XX:MaxNewSize=1G"

    经过观察该配置非常稳定,每次普通GC的时间在10ms左右,Full GC基本不发生,或隔很长很长的时间才发生一次

    (1)参数

    -Xms:初始堆大小

    -Xmx :最大堆大小 此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存

    -Xmn :年轻代大小 整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。

    -XX:NewSize:设置年轻代大小

    -XX:MaxNewSize:年轻代最大值

    -XX:NewRatio 年老代与年轻代的比值

    -XX:SurvivorRatio:设置年轻代中Eden区与Survivor区的大小比值

    -XX:MaxTenuringThreshold:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加在年轻代即被回收的概论。

    -XX:PermSize:设置持久带

    -XX:MaxPermSize:设置持久代最大值

    (2)调优

    JVM调优主要是针对内存管理方面的调优,包括控制各个代的大小,GC策略。由于GC开始垃圾回收时会挂起应用线程,严重影响了性能,调优的目是为了尽量降低GC所导致的应用线程暂停时间、 减少Full GC次数。

    关键参数:-Xms -Xmx 、-Xmn 、-XX:SurvivorRatio、-XX:MaxTenuringThreshold、-XX:PermSize、-XX:MaxPermSize

    (1)-Xms、 -Xmx 通常设置为相同的值,避免运行时要不断扩展JVM内存,这个值决定了JVM heap所能使用的最大内存。

    (2)-Xmn 决定了新生代空间的大小,新生代Eden、S0、S1三个区域的比率可以通过-XX:SurvivorRatio来控制(假如值为 4  表示:Eden:S0:S1 = 4:3:3 )

    (3)-XX:MaxTenuringThreshold 控制对象在经过多少次minor GC之后进入老年代,此参数只有在Serial 串行GC时有效。

    (4)-XX:PermSize、-XX:MaxPermSize 用来控制方法区的大小,通常设置为相同的值。

    (1)代调优

       合理设置新生代大小

        1)避免新生代大小设置过小

    当新生代设置过小时,会产生两种比较明显的现象,一是minor GC次数频繁,二是可能导致 minor GC对象直接进入老年代。当老年代内存不足时,会触发Full GC。

        2)避免新生代设置过大

    新生代设置过大,会带来两个问题:一是老年大变小,可能导致Full  GC频繁执行;二是 minor GC 执行回收的时间大幅度增加。

    年轻代大小选择

    响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。

    吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。

    年老代大小选择

    响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:

    并发垃圾收集信息

    持久代并发收集次数

    传统GC信息

    花在年轻代和年老代回收上的时间比例

    减少年轻代和年老代花费的时间,一般会提高应用的效率

    吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。

        合理设置Survivor区

      -XX:SurvivorRatio参数的值越大,就意味着Eden区域变大,minor GC次数会降低,但两块Survivor区域变小,如果超过Survivor区域内存大小的对象在minor GC后仍没被回收,则会直接进入老年代,

    -XX:SurvivorRatio参数值设置过小,就意味着Eden区域变小,minor GC触发次数会增加,Survivor区域变大,意味着可以存储更多在minor GC后任存活的对象,避免其进入老年代。

      合理设置对象在新生代的存活时间

    新生代存活周期的值决定了新生代对象在经过多少次Minor GC后进入老年代。因此这个值要根据自己的应用来调优,Jvm参数上这个值对应的为-XX:MaxTenuringThreshold,默认值为15次。

     初始堆大小和最大堆大小相同

    -XX:PermSize、-XX:MaxPermSize 用来控制方法区的大小,通常设置为相同的值。避免运行时要不断扩展JVM内存,这个值决定了JVM heap所能使用的最大内存。

    较小堆引起的碎片问题

    因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:

    -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。

    -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩

    (2)GC策略调优

    (1)合理选择垃圾收集器的搭配使用

    (2)使用可视化工具JConsole查看JVM参数

    JConsole工具在JDK/bin目录下,启动JConsole后,将自动搜索本机运行的jvm进程,不需要jps命令来查询指定。双击其中一个jvm进程即可开始监控,也可使用“远程进程”来连接远程服务器

    JVM常用调试参数:
    –verbose:gc在虚拟机发生内存回收时在输出设备显示信息

    -Xloggc:filename把GC相关日志信息记录到文件以便分析

    -XX:-HeapDumpOnOutOfMemoryError当首次遭遇OOM时导出此时堆中相关信息

    -XX:OnError="<cmdargs>;<cmd args>" 出现致命ERROR之后运行自定义命令

    -XX:-PrintClassHistogram遇到Ctrl-Break后打印类实例的柱状信息,与jmap -histo功能相同

    -XX:-PrintConcurrentLocks遇到Ctrl-Break后打印并发锁的相关信息,与jstack -l功能相同

    -XX:-PrintGC每次GC时打印相关信息

    -XX:-PrintGCDetails每次GC时打印详细信息

    -XX:-PrintGCTimeStamps打印每次GC的时间戳

    -XX:+PrintGCApplicationStoppedTime打印垃圾回收期间程序暂停的时间

    -XX:+PrintHeapAtGC打印GC前后的详细堆栈信息

    -XX:+PrintTenuringDistribution查看每次minor GC后新的存活周期的阈值,即在年轻代survivor中的复制次数.

    -XX:-TraceClassLoading跟踪类的加载信息

    -XX:-TraceClassUnloading跟踪类的卸载信息

    -XX:-TraceLoaderConstraints跟踪类加载器约束的相关信息

    -XX:ErrorFile=/opt/tomcat/bin/hs_error_%p.log  Crash日志

    [GC [DefNew:209792K->4417K(235968K), 0.0201630 secs] 246722K->41347K(498112K),0.0204050 secs]

    YoungGen内存区回收前209792K,回收后4417K, YoungGen大小是235968K, HEAP 内存区回收前246722K, 回收后41347K, heap的大小是498112K,GC消耗的时间是0.0204050 secs

     [GC [1 CMS-initial-mark: 655374K(1310720K)] 662197K(1546688K), 0.0303050 secs] [Times: user=0.02 sys=0.02, real=0.03 secs]

     [weak refs processing, 0.0417710 secs] [1 CMS-remark: 655734K(1310720K)] 766736K(1546688K), 0.0932010 secs] [Times: user=0.17 sys=0.00, real=0.09 secs]

    CMS-initial-mark阶段暂停了0.0303050秒,而CMS-remark阶段暂停了0.0932010秒,因此两次暂停的总共时间是0.123506秒.

    两种fail引起full gc:Prommotionfailed和Concurrent mode failed:

    [ParNew (promotion failed): 320138K->320138K(353920K), 0.2365970 secs]

     [CMS: 1458785K->1120688K(2520704K), 9.4584090 secs]

    Prommotion failed由于救助空间不够,从而向年老代转移对象,年老代没有足够的空间来容纳这些对象; 或者老生代空闲空间存在碎片,导致没有足够大的连续空间开存放新生代对,导致一次fullgc的产生。解决这个问题的办法有两种完全相反的倾向:增大救助空间、增大年老代。调整-XX:SurvivorRatio参数

    Concurrent mode failed的产生是由于CMS回收年老代的速度太慢,导致年老代在CMS完成前就被沾满,引起full gc,避免这个现象的产生就是调小-XX:CMSInitiatingOccupancyFraction参数的值.

    java.lang.OutOfMemoryError:GC overhead limit exceeded:

    JDK6新添的错误类型。是发生在GC占用大量时间为释放很小空间的时候发生的,是一种保护机制。解决方案是,关闭该功能,使用-XX:-UseGCOverheadLimit. 需要查看是否有使用大内存的代码或死循环。

    JVM常用调试工具:
    jconsole – jconsole是基于JavaManagementExtensions (JMX)的实时图形化监测工具,这个工具利用了内建到JVM里面的JMX指令来提供实时的性能和资源的监控,包括了Java程序的内存使用,Heap size, 线程的状态,类的分配状态和空间使用等等。Linux下设置环境变量如下:export DISPLAY=:0.0

    jstatd–启动jvm监控服务。它是一个基于rmi的应用,向远程机器提供本机jvm应用程序的信息。默认端口1099。

    -nr 当一个存在的RMI Registry没有找到时,不尝试创建一个内部的RMI Registry

    -p port 端口号,默认为1099

    -nrminame 默认为JStatRemoteHost;如果多个jstatd服务开始在同一台主机上,rminame唯一确定一个jstatd服务

    -J jvm选项

    $JAVA_HOME/jre/lib/security/java.policy文件中添加下面的代码:

    grantcodebase "file:${java.home}/../lib/tools.jar" {

      permission java.security.AllPermission;

    };

    默认端口为1099:   jstatd -J-Djava.security.policy=jstatd.all.policy

    指定hostname 指定端口: jstatd -J-Djava.rmi.server.hostname=192.168.8.7-J-Djava.security.policy=test/jstatd.all.policy -p 6001  

     启动JMX: jstatd -J-Djava.rmi.server.hostname=192.168.8.7-J-Djava.security.policy=test/jstatd.all.policy  -J-Dcom.sun.management.jmxremote.port=6001 -J-Dcom.sun.management.jmxremote.ssl=false-J-Dcom.sun.management.jmxremote.authenticate=false -J -Djava.awt.headless=true

    (java.net.MalformedURLException:Local host name unknown: java.net.UnknownHostException: a: a

    原因是/etc/hosts文件里没有主机名为:a的,解决方法就是在hosts文件中加入a:

    127.0.0.1   a   localhost)

    jps –jps是用来查看JVM里面所有进程的具体状态, 包括进程ID,进程启动的路径等等。

    -m 输出传递给main方法的参数,如果是内嵌的JVM则输出为null。

    -l 输出应用程序主类的完整包名,或者是应用程序JAR文件的完整路径。

    -v 输出传给JVM的参数。

    jstack -- jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息,如果是在64位机器上,需要指定选项"-J-d64",如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和nativestack的信息。Windows的jstack只支持-l.

    -F当’jstack [-l] pid’没有相应的时候强制打印栈信息

    -l长列表. 打印关于锁的附加信息,如属于java.util.concurrent的ownable synchronizers列表.

    -m打印java和native c/c++框架的所有栈信息.

    Jstackpid  显示jvm中当前所有线程的运行情况和线程当前状态

    jinfo –可以输出并修改运行时的java 进程的opts。用处比较简单,用于输出JAVA系统参数及命令行参数。查看和修改运行中的java程序的运行环境参数。

    jinfo-flag MaxPermSize  4084

    jmap –jmap 可以从core文件或进程中获得内存的具体匹配情况,包括Heap size, Perm size等等,打印出某个java进程(使用pid)内存内的,所有‘对象’的情况。

    -heap :打印jvm heap的情况
    -histo: 打印jvm heap的直方图。其输出信息包括类名,对象数量,对象占用大小。
    -histo:live : 同上,但是只输出存活对象的情况,会触发FULL GC

    -permstat: 打印permanentgeneration heap情况

    jmap-dump:format=b,file=outfile 3024可以将3024进程的内存heap输出出来到outfile文件里,再配合JHAT进行分析.

    Jhat用于对JAVA heap进行离线分析的工具,他可以对不同虚拟机中导出的heap信息文件进行分析,如LINUX上导出的文件可以拿到WINDOWS上进行分析

    jhat -J-Xmx512m <heap dump file>

    jstat – jstat利用了JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控, 可以观察到classloader,compiler,gc相关信息,包括了对Heap size和垃圾回收状况的监控等等。

    -class:统计class loader行为信息
    -compile:统计编译行为信息
    -gc:统计jdk gc时heap信息
    -gccapacity:统计不同的generations(不知道怎么翻译好,包括新生区,老年区,permanent区)相应的heap容量情况
    -gccause:统计gc的情况,(同-gcutil)和引起gc的事件
    -gcnew:统计gc时,新生代的情况
    -gcnewcapacity:统计gc时,新生代heap容量
    -gcold:统计gc时,老年区的情况
    -gcoldcapacity:统计gc时,老年区heap容量
    -gcpermcapacity:统计gc时,permanent区heap容量
    -gcutil:统计gc时,heap情况

    -compiler:显示VM实时编译的数量等信息

    -snap: 查看Java进程的jvmstat的各个monitor的值

    jstat-gc PID@172.30.0.160

    java-verbose:class PID输出虚拟机装入的类的信息, 当虚拟机报告类找不到或类冲突时可用此参数来诊断来查看虚拟机从装入类的情况。

    java –verbose:jni输出native方法调用的相关情况,一般用于诊断jni调用错误信息

    常用JVM分析信息获取:

    1、确认服务器上是否存在SUN JDK6,如果没有建议安装一个,我们需要使用里面的工具(jps、jmap、jstat、jconsole、jstack)

    2、 获取MKEY的JAVA进程ID(后面简称PID)

    a)   操作方法:进入SUN JDK的bin目录在命令行中输入jps,查看MKEY的进程ID

    3、提取GC信息()

    a)   操作方法:示例:jstat –gcPID 600000 43200 >> gc_20110909.log

    4、 提取Heap区信息(间隔10分钟提取一次)

    a)   操作方法:jmap -heap PID >>heap_20110909.log

    5、提取对象信息(间隔10分钟提取一次)

    a)      操作方法:jmap-histo PID > histo_*.log(文件较大,*按序号生成输入)

    6、线程转储日志

    a)      jstack PID

    b)      宕机日志(在domain目录,如果没有宕机,条件允许的情况下可使用 kill -3 PID(此操作会杀掉进程))

    精简版:

    服务器挂起后,在重启之前执行以下命令:

    jps-vl 查询到相应的JAVA进程,简称PID

    jstat –gcutilPID 5000 >> /opt/tomcat/bin/gcutil.log

    vmstat3

    jstackPID > /opt/tomcat/bin/jstack.log

    jmap-histo PID > /opt/tomcat/bin/histo.log

    网络方面:

    whiletrue; do A=$(netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a,S[a]}'); echo $A ; sleep 3; done

    netstat -an | grep1521

    再重启服务.提取相关日志发出来

  • 相关阅读:
    HTTP协议
    浏览器兼容性问题
    面试笔试重点总结:操作系统、计算机网络、设计模式
    笔试、面试重点总结:算法基础、数据结构
    笔试、面试重点总结:WIN32、MFC与Linux
    《Java程序员面试笔试宝典》终于在万众期待中出版啦~
    《Java程序员面试笔试宝典》之Static关键字有哪些作用
    《Java程序员面试笔试宝典》之字符串创建与存储的机制是什么
    《Java程序员面试笔试宝典》之为什么需要public static void main(String[] args)这个方法
    《Java程序员面试笔试宝典》之Java变量命名有哪些规则
  • 原文地址:https://www.cnblogs.com/h-c-g/p/10851223.html
Copyright © 2011-2022 走看看