JVM组成、GC回收机制、算法、JVM常见启动参数、JAVA出现OOM,如何解决、tomcat优化方法

性能测试之JVM组成以及GC回收

我们会围绕以下几点来展开关于性能测试方面的话题

  1. JVM组成
  2. GC垃圾回收
  3. JVM常见启动参数
  4. JAVA程序出现OOM,如何解决
  5. tomcat的优化方法

JVM的组成

JVM组成部分:

  • 类加载子系统: 使用Java语言编写.java Source Code文件,通过javac编译成.class Byte Code文件。class loader类加载器将所需所有类加载到内存,必要时将类实例化成实例

  • 运行时数据区: 最消耗内存的空间,需要优化

  • 执行引擎: 包括JIT (JustInTimeCompiler)即时编译器, GC垃圾回收器

  • 本地方法接口: 将本地方法栈通过JNI(Java Native Interface)调用Native Method Libraries, 比

    如:C,C++库等,扩展Java功能,融合不同的编程语言为Java所用

JVM运行时数据区域由下面部分构成:

  • Method Area (线程共享):方法区是所有线程共享的内存空间,存放已加载的类信息(构造方法,接口定义),常量(final),静态变量(static), 运行时常量池等。但实例变量存放在堆内存中. 从JDK8开始此空间由永久代改名为元空间
  • heap (****线程共享):堆在虚拟机启动时创建,存放创建的所有对象信息。如果对象无法申请到可用内存将抛出OOM异常.堆是靠GC垃圾回收器管理的,通过-Xmx -Xms 指定最大堆和最小堆空间大小**
  • Java stack (线程私有):Java栈是每个线程会分配一个栈,存放java中8大基本数据类型,对象引用,实例的本地变量,方法参数和返回值等,基于FILO()(First In Last Out),每个方法为一个栈帧**
  • Program Counter Register (线程私有):PC寄存器就是一个指针,指向方法区中的方法字节码,每一个线程用于记录当前线程正在执行的字节码指令地址。由执行引擎读取下一条指令.因为线程需要切换,当一个线程被切换回来需要执行的时候,知道执行到哪里了**
  • Native Method stack (线程私有):本地方法栈为本地方法执行构建的内存空间,存放本地方法执行时的局部变量、操作数等。所谓本地方法,使用native 关健字修饰的方法,比如:Thread.sleep方法. 简单的说是非Java实现的方法,例如操作系统的C编写的库提供的本地方法,Java调用这些本地方法接口执行。但是要注意,本地方法应该避免直接编程使用,因为Java可能跨平台使用,如果用了Windows API,换到了Linux平台部署就有了问题

GC (Garbage Collection) 垃圾收集器

在堆内存中如果创建的对象不再使用,仍占用着内存,此时即为垃圾.需要即使进行垃圾回收,从而释放内存空间给其它对象使用

其实不同的开发语言都有垃圾回收问题,C,C++需要程序员人为回收,造成开发难度大,容易出错等问题,但执行效率高,而JAVA和Python中不需要程序员进行人为的回收垃圾,而由JVM或相关程序自动回收垃圾,减轻程序员的开发难度,但可能会造成执行效率低下

堆内存里面经常创建、销毁对象,内存也是被使用、被释放。如果不妥善处理,一个使用频繁的进程,可能会出现虽然有足够的内存容量,但是无法分配出可用内存空间,因为没有连续成片的内存了,内存全是碎片化的空间。

所以需要有合适的垃圾回收机制,确保正常释放不再使用的内存空间,还需要保证内存空间尽可能的保持一定的连续

对于垃圾回收,需要解决三个问题

  • 哪些是垃圾要回收

  • 怎么回收垃圾

  • 什么时候回收垃圾

Garbage 垃圾确定方法

  1. 引用计数: 每一个堆内对象上都与一个私有引用计数器,记录着被引用的次数,引用计数清零,该对象所占用堆内存就可以被回收。循环引用的对象都无法将引用计数归零,就无法清除。Python中即使用此种方式
  2. 根搜索(可达)算法 Root Searching

垃圾回收基本算法

  1. 标记-清除 Mark-Sweep

    分垃圾标记阶段和内存释放阶段。标记阶段,找到所有可访问对象打个标记。清理阶段,遍历整个堆,

    对未标记对象(即不再使用的对象)逐一进行清理。

标记-清除最大的问题:会造成内存碎片,但是不浪费空间,效率较高高(如果对象较多,逐一删除效率也会影响)

  1. 标记-压缩(压实)Mark-Compact

    分垃圾标记阶段和内存整理阶段。标记阶段,找到所有可访问对象打个标记。内存清理阶段时,整理时将对象向内存一端移动,整理后存活对象连续的集中在内存一端。

​ 标记-压缩算法好处是整理后内存空间连续分配,有大段的连续内存可分配,没有内存碎片。缺点是内存整理过程有消耗,效率相对低下

  1. 复制 Copying

    先将可用内存分为大小相同两块区域A和B,每次只用其中一块,比如A。当A用完后,则将A中存活的对象复制到B。复制到B的时候连续的使用内存,最后将A一次性清除干净。

    缺点是比较浪费内存,只能使用原来一半内存,因为内存对半划分了,复制过程毕竟也是有代价。

    好处是没有碎片,复制过程中保证对象使用连续空间,且一次性清除所有垃圾,所以效率很高

  2. 多种算法总结

    没有最好的算法,在不同场景选择最合适的算法

    • 效率: 复制算法>标记清除算法> 标记压缩算法

    • 内存整齐度: 复制算法=标记压缩算法> 标记清除算法

    • 内存利用率: 标记压缩算法=标记清除算法>复制算法

  3. STW

对于大多数垃圾回收算法而言,GC线程工作时,停止所有工作的线程,称为Stop The World。GC 完成时,恢复其他工作线程运行。这也是JVM运行中最头疼的问题。

​ 6. 堆内存分代

将heap内存空间分为三个不同类别:年轻代、老年代、持久代


Heap堆内存分为

年轻代YoungYoung Generation

  • 伊甸园区****eden: 只有一个,刚刚创建的对象

  • 幸存****(存活)区Servivor Space:有2个幸存区,一个是from区,一个是to区。大小相等、地位相同、可互换。

    • from 指的是本次复制数据的源区
    • to 指的是本次复制数据的目标区

老年代Tenured:Old Generation, 长时间存活的对象

默认空间大小比例

永久代:JDK1.7之前使用, 即Method Area方法区,保存JVM自身的类和方法,存储JAVA运行时的环境信息, JDK1.8后 改名为 MetaSpace,此空间不存在垃圾回收,关闭JVM会释放此区域内存,此空间物理上不属于heap内存,但逻辑上存在于heap内存

  • 永久代必须指定大小限制,字符串常量JDK1.7存放在永久代,1.8后存放在heap中

  • MetaSpace 可以设置,也可不设置,无上限

规律 一般情况99%的对象都是临时对象

范例: 在tomcat 状态页可以看到以下的内存分代

范例查看JVM内存分配情况

[root@centos8 ~]#cat Heap.java
public class Heap {
   public static void main(String[] args){
       //返回JVM试图使用的最大内存,字节单位
       long max = Runtime.getRuntime().maxMemory();
       //返回JVM初始化总内存
       long total = Runtime.getRuntime().totalMemory();
       System.out.println("max="+max+"字节\t"+(max/(double)1024/1024)+"MB");
       System.out.println("total="+total+"字节\t"+
(total/(double)1024/1024)+"MB");
   }
}
[root@centos8 ~]#javac Heap.java
[root@centos8 ~]#java -classpath . Heap
max=243269632字节 232.0MB
total=16252928字节 15.5MB
[root@centos8 ~]#java -XX:+PrintGCDetails Heap
max=243269632字节 232.0MB
total=16252928字节 15.5MB
Heap
 def new generation   total 4928K, used 530K [0x00000000f1000000, 
0x00000000f1550000, 0x00000000f6000000)
 eden space 4416K,  12% used [0x00000000f1000000, 0x00000000f1084a60, 
0x00000000f1450000)
 from space 512K,   0% used [0x00000000f1450000, 0x00000000f1450000, 
0x00000000f14d0000)
 to   space 512K,   0% used [0x00000000f14d0000, 0x00000000f14d0000, 
0x00000000f1550000)
 tenured generation   total 10944K, used 0K [0x00000000f6000000, 
0x00000000f6ab0000, 0x0000000100000000)
   the space 10944K,   0% used [0x00000000f6000000, 0x00000000f6000000, 
0x00000000f6000200, 0x00000000f6ab0000)
 Metaspace       used 2525K, capacity 4486K, committed 4864K, reserved 1056768K
 class space   used 269K, capacity 386K, committed 512K, reserved 1048576K
  
[root@centos8 ~]#echo "scale=2;(4928+10944)/1024" |bc
15.50
#说明年轻代+老年代占用了所有heap空间, Metaspace实际不占heap空间,逻辑上存在于Heap
#默认JVM试图分配最大内存的总内存的1/4,初始化默认总内存为总内存的1/64

年轻代回收Minor GC

  1. 起始时,所有新建对象(特大对象直接进入老年代)都出生在eden,当eden满了,启动GC。这个称为Young GC 或者 Minor GC

  2. 标记eden存活对象,然后将存活对象复制到s0(假设本次是s0,也可以是s1,它们可以调换),eden剩余所有空间都清空。GC完成

  3. 继续新建对象,当eden再次满了,启动GC

  4. 先同时标记eden和s0中存活对象,然后将存活对象复制到s1。将eden和s0清空,此次GC完成

  5. 继续新建对象,当eden满了,启动GC

  6. 标记eden和s1中存活对象,然后将存活对象复制到s0。将eden和s1清空,此次GC完成

以后就重复上面的步骤。

通常场景下,大多数对象都不会存活很久,而且创建活动非常多,新生代就需要频繁垃圾回收。

但是,如果一个对象一直存活,它最后就在from、to来回复制,如果from区中对象复制次数达到阈值

(默认15次,CMS为6次,可通过java的选项 -XX:MaxTenuringThreshold=N 指定),就直接复制到老年

代。

老年代回收 Major GC

进入老年代的数据较少,所以老年代区被占满的速度较慢,所以垃圾回收也不频繁。

如果老年代也满了,会触发老年代GC,称为Old GC或者 Major GC

由于老年代对象一般来说存活次数较长,所以较常采用标记压缩算法。

当老年代满时,会触发 Full GC,即对所有"代"的内存进行垃圾回收

Minor GC比较频繁,Major GC较少。但一般Major GC时,由于老年代对象也可以引用新生代对象,所

以先进行一次Minor GC,然后在Major GC会提高效率。可以认为回收老年代的时候完成了一次Full

GC。

所以可以认为 MajorGC = FullGC

GC触发条件

Minor GC 触发条件:当eden区满了触发

Full GC 触发条件:

老年代满了

System.gc()手动调用。不推荐

年轻代

存活时长低

适合复制算法

老年代

区域大,存活时长高

适合标记压缩算法

JVM内存常用相关参数

Java 命令行参考文档: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

帮助:man java

选项分类

  • 选项名称 此为标准选项,所有HotSpot都支持

  • -X选项名称 此为稳定的非标准选项

  • -XX选项名称 非标准的不稳定选项,下一个版本可能会取消

范例: 查看java的选项帮助

#查看java命令标准选项
[root@centos ~]#java 
Usage: java [-options] class [args...]
           (to execute a class)
   or java [-options] -jar jarfile [args...]
           (to execute a jar file)
where options include:
    -d32 use a 32-bit data model if available
    -d64 use a 64-bit data model if available
    -server to select the "server" VM
                 The default VM is server.
    -cp <class search path of directories and zip/jar files>
    -classpath <class search path of directories and zip/jar files>
                 A : separated list of directories, JAR archives,
                 and ZIP archives to search for class files.
    -D<name>=<value>
                  set a system property
    -verbose:[class|gc|jni]
                 enable verbose output
    -version     print product version and exit
    -version:<value>
                 Warning: this feature is deprecated and will be removed
                  in a future release.
                 require the specified version to run
    -showversion print product version and continue
    -jre-restrict-search | -no-jre-restrict-search
                 Warning: this feature is deprecated and will be removed
                  in a future release.
                 include/exclude user private JREs in the version search
    -? -help     print this help message
    -X           print help on non-standard options  #非标准选项
    -ea[:<packagename>...|:<classname>]
    -enableassertions[:<packagename>...|:<classname>]
                 enable assertions with specified granularity
    -da[:<packagename>...|:<classname>]
    -disableassertions[:<packagename>...|:<classname>]
                 disable assertions with specified granularity
    -esa | -enablesystemassertions
                 enable system assertions
    -dsa | -disablesystemassertions
                 disable system assertions
    -agentlib:<libname>[=<options>]
                 load native agent library <libname>, e.g. -agentlib:hprof
                 see also, -agentlib:jdwp=help and -agentlib:hprof=help
    -agentpath:<pathname>[=<options>]
                 load native agent library by full pathname
    -javaagent:<jarpath>[=<options>]
                 load Java programming language agent, see java.lang.instrument
    -splash:<imagepath>
                 show splash screen with specified image
See http://www.oracle.com/technetwork/java/javase/documentation/index.html for
more details.
[root@t1 ~]# #查看java的非标准选项
[root@centos8 ~]#java -X
    -Xmixed           mixed mode execution (default)
    -Xint             interpreted mode execution only
    -Xbootclasspath:<directories and zip/jar files separated by :>
                      set search path for bootstrap classes and resources
    -Xbootclasspath/a:<directories and zip/jar files separated by :>
                     append to end of bootstrap class path
    -Xbootclasspath/p:<directories and zip/jar files separated by :>
                     prepend in front of bootstrap class path
    -Xdiag           show additional diagnostic messages
    -Xnoclassgc       disable class garbage collection
    -Xincgc           enable incremental garbage collection
    -Xloggc:<file>   log GC status to a file with time stamps
    -Xbatch           disable background compilation
    -Xms<size>        set initial Java heap size
    -Xmx<size>        set maximum Java heap size
    -Xss<size>        set java thread stack size
    -Xprof           output cpu profiling data
    -Xfuture         enable strictest checks, anticipating future default
    -Xrs             reduce use of OS signals by Java/VM (see documentation)
    -Xcheck:jni       perform additional checks for JNI functions
    -Xshare:off       do not attempt to use shared class data
    -Xshare:auto     use shared class data if possible (default)
    -Xshare:on       require using shared class data, otherwise fail.
    -XshowSettings   show all settings and continue
    -XshowSettings:all
                     show all settings and continue
    -XshowSettings:vm show all vm related settings and continue
    -XshowSettings:properties
                     show all property settings and continue
    -XshowSettings:locale
                     show all locale related settings and continue
The -X options are non-standard and subject to change without notice.
#查看所有不稳定选项的当前生效值
[root@centos8 ~]#java -XX:+PrintFlagsFinal
[Global flags]
     intx ActiveProcessorCount                      = -1                         
        {product}
   uintx AdaptiveSizeDecrementScaleFactor          = 4                         
          {product}
   uintx AdaptiveSizeMajorGCDecayTimeScale         = 10                         
        {product}
   uintx AdaptiveSizePausePolicy                   = 0                         
          {product}
   uintx AdaptiveSizePolicyCollectionCostMargin    = 50                         
        {product}
   uintx AdaptiveSizePolicyInitializingSteps       = 20                         
        {product}
   uintx AdaptiveSizePolicyOutputInterval          = 0                         
          {product}
   uintx AdaptiveSizePolicyWeight                  = 10                         
        {product}
   uintx AdaptiveSizeThroughPutPolicy              = 0                         
          {product}
 .......
#查看所有不稳定选项的默认值
[root@centos8 ~]#java -XX:+PrintFlagsInitial
[Global flags]
     intx ActiveProcessorCount                      = -1                         
        {product}
   uintx AdaptiveSizeDecrementScaleFactor          = 4                         
          {product}
   uintx AdaptiveSizeMajorGCDecayTimeScale         = 10                         
        {product}
   uintx AdaptiveSizePausePolicy                   = 0                         
          {product}
   uintx AdaptiveSizePolicyCollectionCostMargin    = 50                         
        {product}
   uintx AdaptiveSizePolicyInitializingSteps       = 20                         
        {product}
.......
#查看当前命令行的使用的选项设置
[root@centos8 ~]#java -XX:+PrintCommandLineFlags
-XX:InitialHeapSize=15598528 -XX:MaxHeapSize=249576448 -
XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops 
-XX:+UseParallelGC
#上面的-XX:+UseParallelGC 说明当前使用Parallel Scavenge + Parallel Old

范例: 查看和指定JVM内存分配

#默认JVM试图分配最大内存的总内存的1/4,初始化默认总内存为总内存的1/64
[root@centos8 ~]#cat Heap.java
public class Heap {
   public static void main(String[] args){
       //返回JVM试图使用的最大内存,字节单位
       long max = Runtime.getRuntime().maxMemory();
       //返回JVM初始化总内存
       long total = Runtime.getRuntime().totalMemory();
       System.out.println("max="+max+"字节\t"+(max/(double)1024/1024)+"MB");
       System.out.println("total="+total+"字节\t"+
(total/(double)1024/1024)+"MB");
   }
}#编译生成class文件
[root@centos8 ~]#javac Heap.java
#通过$CLASSPATH指定类文件路径,否则无法找到类,也可以通过 java -cp /path指定类路径
[root@centos8 ~]#echo $CLASSPATH
/usr/local/jdk/lib/:/usr/local/jdk/jre/lib/
[root@centos8 ~]#cp Heap.class /usr/local/jdk/lib
#查看当前内存默认值
[root@centos8 ~]#java -XX:+PrintGCDetails Heap
max=243269632字节 232.0MB
total=16252928字节 15.5MB
Heap
 def new generation   total 4928K, used 530K [0x00000000f1000000, 
0x00000000f1550000, 0x00000000f6000000)
 eden space 4416K,  12% used [0x00000000f1000000, 0x00000000f1084a60, 
0x00000000f1450000)
 from space 512K,   0% used [0x00000000f1450000, 0x00000000f1450000, 
0x00000000f14d0000)
 to   space 512K,   0% used [0x00000000f14d0000, 0x00000000f14d0000, 
0x00000000f1550000)
 tenured generation   total 10944K, used 0K [0x00000000f6000000, 
0x00000000f6ab0000, 0x0000000100000000)
   the space 10944K,   0% used [0x00000000f6000000, 0x00000000f6000000, 
0x00000000f6000200, 0x00000000f6ab0000)
 Metaspace       used 2525K, capacity 4486K, committed 4864K, reserved 1056768K
 class space   used 269K, capacity 386K, committed 512K, reserved 1048576K
#指定内存空间
[root@centos8 ~]#java -Xms1024m -Xmx1024m -XX:+PrintGCDetails Heap
max=1037959168字节 989.875MB
total=1037959168字节 989.875MB
Heap
 def new generation   total 314560K, used 11185K [0x00000000c0000000, 
0x00000000d5550000, 0x00000000d5550000)
 eden space 279616K,   4% used [0x00000000c0000000, 0x00000000c0aec408, 
0x00000000d1110000)
 from space 34944K,   0% used [0x00000000d1110000, 0x00000000d1110000, 
0x00000000d3330000)
 to   space 34944K,   0% used [0x00000000d3330000, 0x00000000d3330000, 
0x00000000d5550000)
 tenured generation   total 699072K, used 0K [0x00000000d5550000, 
0x0000000100000000, 0x0000000100000000)
   the space 699072K,   0% used [0x00000000d5550000, 0x00000000d5550000, 
0x00000000d5550200, 0x0000000100000000)
 Metaspace       used 2525K, capacity 4486K, committed 4864K, reserved 1056768K
 class space   used 269K, capacity 386K, committed 512K, reserved 1048576K
  
#以下计算结果和max一样,说明Metaspace逻辑存在,但物理上并不属于heap空间
[root@centos8 ~]#echo '(314560+699072)*1024'|bc
1037959168  

范例: 查看OOM

[root@centos8 ~]#cat HeapOom2.java
import java. util. Random;
public class HeapOom2 {
 public static void main(String[] args) {
 String str = "I am lao wang";
 while (true){
 str += str + new Random().nextInt(88888888); //生成0到88888888之间的随
机数字
 }
 }
}
[root@centos8 ~]#javac -cp . HeapOom2.java
[root@centos8 ~]#java -Xms100m -Xmx100m -XX:+PrintGCDetails -cp . HeapOom2
[GC (Allocation Failure) [DefNew: 27213K->2940K(30720K), 0.0026366 secs] 27213K-
>5624K(99008K), 0.0027124 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 24949K->0K(30720K), 0.0044922 secs] 27633K-
>8307K(99008K), 0.0045959 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [DefNew: 16550K->0K(30720K), 0.0037276 secs] 24857K-
>19041K(99008K), 0.0037898 secs] [Times: user=0.00 sys=0.01, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 21468K->0K(30720K), 0.0111687 secs] 40509K-
>40509K(99008K), 0.0112437 secs] [Times: user=0.00 sys=0.01, real=0.01 secs] 
[GC (Allocation Failure) [DefNew: 21951K->0K(30720K), 0.0084048 secs] 62460K-
>61977K(99008K), 0.0084641 secs] [Times: user=0.00 sys=0.01, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 21468K->21468K(30720K), 0.0000177 secs]
[Tenured: 61977K->35141K(68288K), 0.0068683 secs] 83445K->35141K(99008K), 
[Metaspace: 2479K->2479K(1056768K)], 0.0069358 secs] [Times: user=0.01 sys=0.00, 
real=0.01 secs] 
[Full GC (Allocation Failure) [Tenured: 35141K->32444K(68288K), 0.0091498 secs] 
35141K->32444K(99008K), [Metaspace: 2479K->2479K(1056768K)], 0.0091975 secs] 
[Times: user=0.01 sys=0.00, real=0.01 secs] 
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
 at java.util.Arrays.copyOf(Arrays.java:3332)
 at 
java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.jav
a:124)
 at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:674)
 at java.lang.StringBuilder.append(StringBuilder.java:208)
 at HeapOom2.main(HeapOom2.java:6)
Heap
 def new generation   total 30720K, used 1048K [0x00000000f9c00000, 
0x00000000fbd50000, 0x00000000fbd50000)
 eden space 27328K,   3% used [0x00000000f9c00000, 0x00000000f9d06158, 
0x00000000fb6b0000)
from space 3392K,   0% used [0x00000000fba00000, 0x00000000fba00000, 
0x00000000fbd50000)
 to   space 3392K,   0% used [0x00000000fb6b0000, 0x00000000fb6b0000, 
0x00000000fba00000)
 tenured generation   total 68288K, used 32444K [0x00000000fbd50000, 
0x0000000100000000, 0x0000000100000000)
   the space 68288K,  47% used [0x00000000fbd50000, 0x00000000fdcff248, 
0x00000000fdcff400, 0x0000000100000000)
 Metaspace       used 2510K, capacity 4486K, committed 4864K, reserved 1056768K
 class space   used 268K, capacity 386K, committed 512K, reserved 1048576K

Jprofiler定位OOM的问题原因

JProfiler是一款功能强大的Java开发分析工具,它可以快速的帮助用户分析出存在的错误,软件还可对需

要的显示类进行标记,包括了内存的分配情况和信息的视图等

JProfiler官网:http://www.ej-technologies.com/products/jprofiler/overview.html

范例: 安装jprofiler工具定位OOM原因和源码问题的位置

#安装OpenJDK
[root@centos8 ~]#dnf -y install java-1.8.0-openjdk-devel
[root@centos8 ~]#cat HeapOom.java 
import java.util.ArrayList;
import java.util.List;
public class HeapOom {
   public static void main(String[] args) {
       List<byte[]> list =new ArrayList<byte[]>();
       int i = 0;
       boolean flag = true;
        while(flag){
           try{
               i++;
               list.add(new byte[1024* 1024]);//每次增加一个1M大小的数组对象
               Thread.sleep(1000);
           }catch(Throwable e){
               e.printStackTrace();
               flag = false;
               System.out.println("count="+i);//记录运行的次数
           }
       }
   }
}
[root@centos8 ~]#javac HeapOom.java 
[root@centos8 ~]#java -cp . -Xms5m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError 
HeapOom
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid96271.hprof ...
Heap dump file created [8134070 bytes in 0.031 secs]
java.lang.OutOfMemoryError: Java heap space
 at HeapOom.main(HeapOom.java:12)
count=8
[root@centos8 ~]#cat HeapOom2.java 
import java. util. Random;
public class HeapOom2 {
 public static void main(String[] args) {
 String str = "I am lao wang";
 while (true){
 str += str + new Random().nextInt(88888888);
 }
 }
}
[root@centos8 ~]#javac HeapOom2.java 
[root@centos8 ~]#java -cp . -Xms5m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError 
HeapOom2
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid96339.hprof ...
Heap dump file created [4925877 bytes in 0.016 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
 at java.util.Arrays.copyOf(Arrays.java:3332)
 at 
java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.jav
a:124)
 at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:674)
 at java.lang.StringBuilder.append(StringBuilder.java:208)
 at HeapOom2.main(HeapOom2.java:6)

下载并安装JProfiler(傻瓜式安装略)后,双击打开前面生成的两个文件java_pid96271.hprof和

java_pid96339,可以看到下面显示,从中分析OOM原因

Tomcat的JVM参数设置

默认不指定,-Xmx大约使用了1/4的内存,当前本机内存指定约为1G。

在bin/catalina.sh中增加一行

......
# OS specific support. $var _must_ be set to either true or false.
#添加下面一行
JAVA_OPTS="-server -Xms128m -Xmx512m -XX:NewSize=48m -XX:MaxNewSize=200m"
                                                            
cygwin=false
darwin=false
........
-server:VM运行在server模式,为在服务器端最大化程序运行速度而优化
-client:VM运行在Client模式,为客户端环境减少启动时间而优化

重启观察tomcat

[root@tomcat ~]#ps aux|grep tomcat
tomcat    22194  5.1 15.1 2497008 123480 ?     Sl   13:31   0:03 
/usr/local/jdk/jre/bin/java -
Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -
Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -server -
Xmx512m -Xms128m -XX:NewSize=48m -XX:MaxNewSize=200m -
Djdk.tls.ephemeralDHKeySize=2048 -
Djava.protocol.handler.pkgs=org.apache.catalina.webresources -
Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -
Dignore.endorsed.dirs= -classpath
/usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar -
Dcatalina.base=/usr/local/tomcat -Dcatalina.home=/usr/local/tomcat -
Djava.io.tmpdir=/usr/local/tomcat/temp org.apache.catalina.startup.Bootstrap 
start

浏览器访问server status页面,可以看到以下页面

JVM工具概述

此处不多概述

Tomcat性能优化常用配置

内存空间优化

JAVA_OPTS="-server -Xms4g -Xmx4g -XX:NewSize= -XX:MaxNewSize= "
-server:服务器模式
-Xms:堆内存初始化大小
-Xmx:堆内存空间上限
-XX:NewSize=:新生代空间初始化大小 
-XX:MaxNewSize=:新生代空间最大值

生产案例

[root@centos8 ~]#vim /usr/local/tomcat/bin/catalina.sh 
JAVA_OPTS="-server -Xms4g -Xmx4g -Xss512k -Xmn1g -
XX:CMSInitiatingOccupancyFraction=65 -XX:+AggressiveOpts -XX:+UseBiasedLocking -
XX:+DisableExplicitGC -XX:MaxTenuringThreshold=10 -XX:NewRatio=2 -
XX:PermSize=128m -XX:MaxPermSize=512m -XX:CMSFullGCsBeforeCompaction=5 -
XX:+ExplicitGCInvokesConcurrent -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -
XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -
XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods"
#一台tomcat服务器并发连接数不高,生产建议分配物理内存通常4G到8G较多,如果需要更多连接,一般会利用
虚拟化技术实现多台tomcat

线程池调整

[root@centos8 ~]#vim /usr/local/tomcat/conf/server.xml
......
<Connector port="8080" protocol="HTTP/1.1"  connectionTimeout="20000"
redirectPort="8443" />
......

常用属性:

  • connectionTimeout :连接超时时长,单位ms
  • maxThreads:最大线程数,默认200
  • minSpareThreads:最小空闲线程数
  • maxSpareThreads:最大空闲线程数
  • acceptCount:当启动线程满了之后,等待队列的最大长度,默认100
  • URIEncoding:URI 地址编码格式,建议使用 UTF-8
  • enableLookups:是否启用客户端主机名的DNS反向解析,缺省禁用,建议禁用,就使用客户端IP就行
  • compression:是否启用传输压缩机制,建议 "on",CPU和流量的平衡
  • compressionMinSize:启用压缩传输的数据流最小值,单位是字节
  • compressableMimeType:定义启用压缩功能的MIME类型text/html, text/xml, text/css, text/javascrip
posted @ 2022-04-23 12:01  资深测试学习机  阅读(218)  评论(0)    收藏  举报