如何优化Tomcat的JVM及参数详解和常见错误?(JVM优化及参数详解,Java垃圾回收机制纯文字介绍,慎入)

前言

  • JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
  • 引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行

一:JVM优化

1.1:JVM概述及参数

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

1.2:JVM参数概述

  • Tomcat 的启动参数位于安装目录 ${JAVA_HOME}/bin 目录下,Linux 操作系统就是catalina.sh 文件。Java_OPTS 就是用来设置 JVM 相关运行参数的变量

    mark

1.3:参数详解

  • Ø -server:一定要作为第一个参数,只要 Tomcat 是运行在生产环境中,这个参数必须给加上,不然后面的参数不会生效 。

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

  • Ø -Xmx:表示最大 Java 堆大小,当应用程序需要的内存超出堆的最大值时虚拟机就会提示内存溢出,并且导致应用服务崩溃,因此一般建议堆的最大值设置为物理内存的最大 值的 50%。

  • Ø -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:+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 等流,在 Windows 环境下,一般我们的 app server 在输出图形时不会碰到什么问题,但是在 Linux/Unix 环境下经常会碰到一个 exception 导致你在 Windows 开发环境下图片正常显示,可是在 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%的空间是 548810% = 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 提供的垃圾回收机制。虚拟机的堆大小决定了虚拟机花费在收集垃圾上的时间和频度。收集垃圾能够接受的速度和应用有关,应该通过分析实际的垃圾收集的时间和频率来调整。假如堆的大小很大,那么完全垃圾收集就会很慢,但是频度会降低。假如您把堆的大小和内存的需要一致,完全收集就很快,但是会更加频繁。调整堆大小的目的是最小化垃圾收集的时间,以在特定的时间内最大化处理客户的请求。在基准测试的时候,为确保最好的性能, 要把堆的大小设大,确保垃圾收集不在整个基准测试的过程中出现。

1.4:Java垃圾回收机制

mark

二:JVM常见错误

2.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.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 大小。

2.3: java.lang.StackOverflowError——栈溢出

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

  • 通常递归也不要递归的层次过多,很容易溢出。
    1K 的空间(这个大约相当于在一个 C 函数内声明了 256 个 int 类型的变量),那么栈区也不过是需要 1MB 的空间。通常栈的大小是 1-2MB 的。

  • 通常递归也不要递归的层次过多,很容易溢出。

  • 解决方法:修改程序,减少函数的“层”数

发布了94 篇原创文章 · 获赞 60 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/CN_TangZheng/article/details/103772707