您现在的位置是:首页 > 行业发展

面试官:JVM三种核心内容有?并从实战角度解析

智慧创新站 2025-03-29【行业发展】223人已围观

简介01前言这章节主要从实战角度方面,去解读JVM。问:JVM三种核心内容有哪一些?答:类加载机制+JVM调优实战+代码优化jvm知识图谱:02类加载机制Java源代码经过编译器编译成字节码之后,最终都需要加载到虚拟机之后才能运行。虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解...

01前言

这章节主要从实战角度方面,去解读JVM。

问:JVM三种核心内容有哪一些?

答:类加载机制+JVM调优实战+代码优化

jvm知识图谱:


02类加载机制

Java源代码经过编译器编译成字节码之后,最终都需要加载到虚拟机之后才能运行。虚拟机把描述类的数据从
Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

2.1类加载时机

一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)七个阶段,其中验证、准备、解析三个部分统称为连接(Linking)。这七个阶段的发生顺序下图所示。

上图中,加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类型的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定特性(也称为动态绑定或晚期绑定)。

关于在什么情况下需要开始类加载过程的第一个阶段“加载”,《Java虚拟机规范》中并没有进行强制约束,这点可以交给虚拟机的具体实现来自由把握。

但是对于初始化阶段,《Java虚拟机规范》则是严格规定了有且只有六种情况必须立即对类进行“初始化”(而加
载、验证、准备自然需要在此之前开始):

遇到new、getstatic、putstatic或invokestatic这4条字节码指令;

使用包的方法对类进行反射调用的时候;

当初始化一个类的时候,发现其父类还没有进行初始化的时候,需要先触发其父类的初始化;

当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先初始化这个类;

当使用的动态语言支持时,如果一个实例最后的解析结果

REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有初始化。

当一个接口中定义了JDK8新加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现。类发生了初始化,那该接口要在其之前被初始化。

对于这六种会触发类型进行初始化的场景,《Java虚拟机规范》中使用了一个非常强烈的限定语——“有且只有”,这六种场景中的行为称为对一个类型进行主动引用。除此之外,所有引用类型的方式都不会触发初始化,称为被动引用。

比如如下几种场景就是被动引用:

通过子类引用父类的静态字段,不会导致子类的初始化;

通过数组定义来引用类,不会触发此类的初始化;

常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化;

2.2类加载过程

加载

在加载阶段,Java虚拟机需要完成以下三件事情:

通过一个类的全限定名来获取定义此类的二进制字节流。

将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

在内存中生成一个代表这个类的对象,作为方法区这个类的各种数据的访问入口。

验证

验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。

验证阶段大致上会完成下面4个阶段的检验动作:

文件格式验证第一阶段要验证字节流是否符合Class文件格式的规范,并且能够被当前版本的虚拟机处理。验证点主要包括:是否以魔数0xCAFEBABE开头;主、次版本号是否在当前虚拟机处理范围之内;常量池的常量中是否有不被支持的常量类型;Class文件中各个部分及文件本身是否有被删除的或者附加的其它信息等等。

元数据验证第二阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求,这个阶段的验证点包括:这个类是否有父类;这个类的父类是否继承了不允许被继承的类;如果这个类不是抽象类,是否实现了其父类或者接口之中要求实现的所有方法;类中的字段、方法是否与父类产生矛盾等等。

字节码验证第三阶段是整个验证过程中最复杂的一个阶段,主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。

符号引用验证最后一个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段--解析阶段中发生。符号引用验证可以看做是对类自身以外(常量池中的各种符号引用)的各类信息进行匹配性校验,通俗来说就是,该类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。

准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段。

解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

初始化

类初始化阶段是类加载过程中的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全是由虚拟机主导和控制的。

到了初始化阶段,才真正开始执行类中定义的Java程序代码。

2.3类加载器

类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远超类加载阶段。

对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。

双亲委派模型

从Java虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器(BootstrapClassLoader),这个类加载器使用C++来实现,是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都由Java来实现,独立于虚拟机外部,并且全都继承自抽象类。

从Java开发者的角度来看,类加载器可以划分为:

启动类加载器(BootstrapClassLoader):这个类加载器负责将存放在java_home\lib目录中的类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,那直接使用null代替即可;

扩展类加载器(ExtensionClassLoader):这个类加载器由$ExtClassLoader实现,它负责加载java_home\lib\ext目录中,或者被系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器;

应用程序类加载器(ApplicationClassLoader):这个类加载器由$AppClassLoader实现。getSystemClassLoader()方法返回的就是这个类加载器,因此也被称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库。开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

我们的应用程序都是由这3种类加载器互相配合进行加载的,在必要时还可以自己定义类加载器。它们的关系如下图所示:

双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。

双亲委派模型的工作过程是:

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类

而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此

因此所有的加载请求最终都应该传送到最顶层的启动类加载器中

这样做的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如,它放在中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型顶端的启动类加载器来加载,因此Object类在程序的各种类加载器环境中都是同一个类。

相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object类,Java类型体系中最基本的行为也就无法保证了。

双亲委派模型对于保证Java程序的稳定运作极为重要,但它的实现却异常简单,用以实现双亲委派的代码只有短短十余行,全部集中在的loadClass()方法之中:

protectedsynchronizedClass?loadClass(Stringname,booleanresolve)throwsClassNotFoundException{//首先,检查请求的类是不是已经被加载过Class?c=findLoadedClass(name);if(c==null){try{if(parent!=null){c=(name,false);}else{c=findBootstrapClassOrNull(name);}}catch(ClassNotFoundExceptione){//如果父类抛出ClassNotFoundException说明父类加载器无法完成加载}if(c==null){//如果父类加载器无法加载,则调用自己的findClass方法来进行类加载c=findClass(name);}}if(resolve){resolveClass(c);}returnc;}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.
03JVM调优实战3.1JVM运行参数

在jvm中有很多的参数可以进行设置,这样可以让jvm在各种环境中都能够高效的运行。绝大部分的参数保持默认即可。

三种参数类型

标准参数

-help

-version

-X参数(非标准参数)

-Xint

-Xcomp

XX参数(使用率较高)

-XX:newSize

-XX:+UseSerialGC

-X参数

jvm的-X参数是非标准参数,在不同版本的jvm中,参数可能会有所不同,可以通过java-X查看非标准参数。

-XX参数

-XX参数也是非标准参数,主要用于jvm的调优和debug操作。

-XX参数的使用有2种方式,一种是boolean类型,一种是非boolean类型:

boolean类型

格式:-XX:[+-]表示启用或禁用属性

如:-XX:+DisableExplicitGC表示禁用手动调用gc操作,也就是说调用()无效

非boolean类型

格式:-XX:=表示属性的值为

如:-XX:NewRatio=4表示新生代和老年代的比值为1:4

-Xms和-Xmx参数

-Xms与-Xmx分别是设置jvm的堆内存的初始大小和最大大小。
-Xmx2048m:等价于-XX:MaxHeapSize,设置JVM最大堆内存为2048M。
-Xms512m:等价于-XX:InitialHeapSize,设置JVM初始堆内存为512M。
适当的调整jvm的内存大小,可以充分利用服务器资源,让程序跑得更快。
示例:

[root@node01test]也可以指定打印的间隔和次数,每1秒中打印一次,共打印5次F:\tjstat-gc1207610005

说明:
S0C:第一个Survivor区的大小(KB)
S1C:第二个Survivor区的大小(KB)
S0U:第一个Survivor区的使用大小(KB)
S1U:第二个Survivor区的使用大小(KB)
EC:Eden区的大小(KB)
EU:Eden区的使用大小(KB)
OC:Old区大小(KB)
OU:Old使用大小(KB)
MC:方法区大小(KB)
MU:方法区使用大小(KB)
CCSC:压缩类空间大小(KB)
CCSU:压缩类空间使用大小(KB)
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间

3.2Jmap的使用以及内存溢出分析

前面通过jstat可以对jvm堆的内存进行统计分析,而jmap可以获取到更加详细的内容,如:内存使用情况的汇总、对内存溢出的定位与分析。

查看内存使用情况

[root@node01~]堆内存配置信息MinHeapFreeRatio=0MaxHeapFreeRatio=100MaxHeapSize=488636416(466.0MB)NewSize=10485760(10.0MB)MaxNewSize=162529280(155.0MB)OldSize=20971520(20.0MB)NewRatio=2SurvivorRatio=8MetaspaceSize=21807104(20.796875MB)CompressedClassSpaceSize=1073741824(1024.0MB)MaxMetaspaceSize=415MBG1HeapRegionSize=0(0.0MB)HeapUsage:年轻代EdenSpace:capacity=123731968(118.0MB)used=1384736(1.320587158203125MB)free=122347232(116.67941284179688MB)1.11937%usedFromSpace:capacity=9437184(9.0MB)used=0(0.0MB)free=9437184(9.0MB)0.0%usedToSpace:capacity=9437184(9.0MB)used=0(0.0MB)free=9437184(9.0MB)0.0%usedPSOldGeneration查看所有对象,包括活跃以及非活跃的jmap-histopid|morejmap-histo:live6219|morenumbytesclassname----------------------------------------------1:374377914608[C2:34916837984:884654848[B4:$Node5:3674424968:6322395512[;7:3738328944:1028208048[$Node;9:2247144264[I10:4305137760$Node11:1270109080[;12:6484128[$Node;13:171482272:328570072[;15:288869312:398363728:127161008:151860720$Entry19:167153472:8850880[$Entry;21:61849440:154549440$Entry23:102741080$Entry24:84640608:14238032[S26:94637840:22636816[[C。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。用法:jmap-dump:format=b,file=dumpFileNamepid用法:jhat-portportfilejhat-port9999/tmp//tmp/:04:21CST2018Snapshotread,resolvingResolving204094objectsChasingreferences,

打开浏览器进行访问:

在最后由OQL查询功能

3.3Jmp使用以及内存溢出分析

使用MAT对内存溢出的定位与分析

内存溢出在实际的生产环境中经常会遇到,比如,不断地将数据写入到一个集合中,出现了死循环,读取超大的文件等等,都可能会造成内存溢出。

如果出现了内存溢出,首先我们需要定位到发生内存溢出的环节,并且进行分析,是正常还是非正常情况,如果是正常的需求,就应该考虑加大内存的设置,如果是非正常需求,那么就要对代码进行修改,修复这个bug。首先,我们得先学会如何定位问题,然后再进行分析。如何定位问题呢,我们需要借助于jmap与MAT工具进行定位分析。

接下来,我们模拟内存溢出的场景。

模拟内存溢出

编写代码,向List集合中添加100万个字符串,每个字符串由1000个UUID组成。如果程序能够正常执行,最后打印ok。

publicclassTestJvmOutOfMemory{publicstaticvoidmain(String[]args){ListObjectlist=newArrayList();for(inti=0;i10000000;i++){Stringstr="";for(intj=0;j1000;j++){str+=().toString();}(str);}("ok");}}1.2.3.4.5.6.7.8.9.10.11.12.13.

为了演示效果,我们将设置执行的参数

用法:jstackpid[root@node01bin]24daemonprio=9os_prio=0tid=0x00007fabb4001000nid=0x906waitingoncondition[0x0000000000000000]:RUNNABLE"http-bio-8080-exec-5"22daemonprio=5os_prio=0tid=0x00007fab9c113800nid=0x8e0waitingoncondition[0x00007fabd06b9000]:WAITING(parking)(NativeMethod)-parkingtowaitfor0x00000000f8508360($ConditionObject)(:175)$(:2039)(:442)(:104)(:32)(:1074)(:1134)$(:624)$(:61)(:748)"http-bio-8080-exec-3"20daemonprio=5os_prio=0tid=0x0000000001aea000nid=0x8dewaitingoncondition[0x00007fabd0abb000]:WAITING(parking)(NativeMethod)-parkingtowaitfor0x00000000f8508360($ConditionObject)(:175)$(:2039)(:442)(:104)(:32)(:1074)(:1134)$(:624)$(:61)(:748)"http-bio-8080-exec-1"17daemonprio=5os_prio=0tid=0x00007fabe8128000nid=0x8d0waitingoncondition[0x00007fabd0ece000]:TIMED_WAITING(sleeping)(NativeMethod)$(:152)(:748)"ajp-bio-8009-Acceptor-0"15daemonprio=5os_prio=0tid=0x00007fabe82d1800nid=0x8cewaitingoncondition[0x00007fabd10d0000]:TIMED_WAITING(sleeping)(NativeMethod)$(:152)(:748)"http-bio-8080-Acceptor-0"13daemonprio=5os_prio=0tid=0x00007fabe82ce000nid=0x8ccwaitingoncondition[0x00007fabd12d2000]:TIMED_WAITING(sleeping)(NativeMethod)$(:1513)(:748)"GCDaemon"7daemonprio=9os_prio=0tid=0x00007fabe80c3800nid=0x8a5runnable[0x0000000000000000]:RUNNABLE"C1CompilerThread1"5daemonprio=9os_prio=0tid=0x00007fabe80b3800nid=0x8a3waitingoncondition[0x0000000000000000]:RUNNABLE"SignalDispatcher"3daemonprio=8os_prio=0tid=0x00007fabe807f000nid=0()[0x00007fabd2a67000]:WAITING(onobjectmonitor)(NativeMethod)-waitingon0x00000000e3162918($Lock)(:143)-locked0x00000000e3162918($Lock)(:164)$(:209)"ReferenceHandler"1prio=5os_prio=0tid=0x00007fabe8009000nid=0x89crunnable[0x00007fabed210000]:(NativeMethod)(:409)(:545)(:513)(:453)(:777)(:723)(NativeMethod)(:62)(:43)(:498)(:321)(:455)"VMThread"os_prio=0tid=0x00007fabe8073000nid=0x89frunnable"GCtaskthread1(ParallelGC)"os_prio=0tid=0x00007fabe8020000nid=0x89erunnable"VMPeriodicTaskThread"os_prio=0tid=0x00007fabe80d6800nid=0x8a6waitingonconditionJNIglobalreferences:431.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.80.81.82.83.84.85.86.87.88.89.90.91.92.93.94.95.96.97.98.99.100.101.102.103.104.105.106.107.108.109.110.111.112.113.114.115.116.117.118.119.120.121.122.123.124.125.126.127.128.129.130.131.132.133.134.135.136.137.138.139.140.141.142.143.144.145.146.147.148.149.150.151.152.153.154.155.156.157.158.159.160.161.162.163.164.165.166.167.168.169.170.171.172.173.174.175.176.177.178.179.180.181.182.183.184.185.186.187.188.189.190.191.192.193.194.195.196.197.198.199.200.201.202.203.204.205.206.207.208.209.210.211.212.213.
3.5VisualVM工具的使用

VisualVM,能够监控线程,内存情况,查看方法的CPU时间和内存中的对象,已被GC的对象,反向查看分配的堆栈(如100个String对象分别由哪几个对象分配出来的)。

VisualVM使用简单,几乎0配置,功能还是比较丰富的,几乎囊括了其它JDK自带命令的所有功能。

内存信息

线程信息

Dump堆(本地进程

Dump线程(本地进程)

打开堆Dump。堆Dump可以用jmap来生成

打开线程Dump

生成应用快照(包含内存信息、线程信息等等)

性能分析。CPU分析(各个方法调用时间,检查哪些方法耗时多),内存分析(各类对象占用的内存,检查哪些类占用内存多)

启动

在jdk的安装目录的bin目录下,找到,双击打开即可。

查看CPU、内存、类、线程运行信息

参看线程信息

监控远程JVM

VisualJVM不仅是可以监控本地jvm进程,还可以监控远程的jvm进程,需要借助于JMX技术实现。

什么是JMX

JMX(JavaManagementExtensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活地开发无缝集成的系统、网络和服务管理应用。

监控Tomcat

想要监控远程的tomcat,就需要在远程的tomcat进行对JMX配置,方法如下:

这几个参数的意思是:-=9999:JMX远程连接端口-=false:不使用

使用VisualJVM远程连接Tomcat

添加主机

在一个主机下可能会有很多的jvm需要监控,所以接下来要在该主机上添加需要监控的jvm:

连接成功。使用方法和前面就一样了,就可以和监控本地jvm进程一样,监控远程的tomcat进程。

3.6可视化GC日志分析工具

GC日志输出参数

前面通过-XX:+PrintGCDetails可以对GC日志进行打印,我们就可以在控制台查看,这样虽然可以查看GC的信息,但是并不直观,可以借助于第三方的GC日志分析工具进行查看。

在日志打印输出涉及到的参数如下:

-XX:+PrintGC输出GC日志-XX:+PrintGCDetails输出GC的详细日志-XX:+PrintGCTimeStamps输出GC的时间戳(以基准时间的形式)-XX:+PrintGCDateStamps输出GC的时间戳(以日期的形式,如2013-05-04T21:53:59.234+0800)-XX:+PrintHeapAtGC在进行GC的前后打印出堆的信息-Xloggc:../logs/日志文件的输出路径1.2.3.4.5.6.7.8.9.10.11.

测试:

-XX:+UseG1GC-XX:MaxGCPauseMillis=100-Xmx256m-XX:+PrintGCDetails-XX:+PrintGCTimeStamps-XX:+PrintGCDateStamps-XX:+PrintHeapAtGC-Xloggc:F://test//

运行后就可以在E盘下生成文件。

使用GCEasy

它是一款在线的可视化工具,易用、功能强大,网站:

3.7调优实战

先部署一个web项目(自行准备)

压测

下面我们通过jmeter进行压力测试,先测得在初始状态下的并发量等信息,然后我们在对jvm做调优处理,再与初始状态测得的数据进行比较,看调好了还是调坏了。

首先需要对jmeter本身的参数调整,jmeter默认的的内存大小只有1g,如果并发数到达300以上时,将无法正常执行,会抛出内存溢出等异常,所以需要对内存大小做出调整。修改文件:setHEAP=-Xms1g-Xmx4g-XX:MaxMetaspaceSize=512m在该文件中可以看到,jmeter默认使用的垃圾收集器是'-XX:+UseG1GC-XX:MaxGCPauseMillis=100-XX:G1ReservePercent=20'1.2.3.4.5.6.

添加gc相关参数

#内存设置较小是为了更频繁的gc,方便观察效果,实际要比此设置的更大JAVA_OPTS="-XX:+UseParallelGC-XX:+UseParallelOldGC-Xms64m-Xmx128m-XX:+PrintGCDetails-XX:+PrintGCTimeStamps-XX:+PrintGCDateStamps-XX:+PrintHeapAtGC-Xloggc:../logs/=9999-==false"1.

重启tomcat

创建测试用例进行压测

调优方向主要从以下几个方面:

调整内存

更换垃圾收集器

对于JVM的调优,给出大家几条建议:

生产环境的JVM一定要进行参数设定,不能全部默认上生产。

对于参数的设定,不能拍脑袋,需要通过实际并发情况或压力测试得出结论。

对于内存中对象临时存在居多的情况,将年轻代调大一些。如果是G1或ZGC,不需要设定。

仔细分析gceasy给出的报告,从中分析原因,找出问题。

对于低延迟的应用建议使用G1或ZGC垃圾收集器。

不要将焦点全部聚焦jvm参数上,影响性能的因素有很多,比如:操作系统、tomcat本身的参数等。

PerfMa

PerfMa提供了JVM参数分析、线程分析、堆内存分析功能,界面美观,功能强大,我们在做jvm调优时,可以作为一个辅助工具。官网:

04Tomcat8优化4.1禁用AJP连接

在服务状态页面中可以看到,默认状态下会启用AJP服务,并且占用8009端口。

什么是AJP呢?

AJP(ApacheJServerProtocol)AJPv13协议是面向包的。WEB服务器和Servlet容器通过TCP连接来交互;为了节省SOCKET创建的昂贵代价,WEB服务器会尝试维护一个永久TCP连接到servlet容器,并且在多个请求和响应周期过程会重用连接。

我们一般是使用Nginx+tomcat的架构,所以用不着AJP协议,所以把AJP连接器禁用。

修改conf下的文件,将AJP服务禁用掉即可。

Connectorport="8009"protocol="AJP/1.3"redirectPort="8443"/1.

4.2执行器(线程池)

在tomcat中每一个用户请求都是一个线程,所以可以使用线程池提高性能。

修改文件:

!--将注释打开--Executorname="tomcatThreadPool"namePrefix="catalina-exec-"maxThreads="500"minSpareThreads="50"prestartminSpareThreads="true"maxQueueSize="100"/!--参数说明:maxThreads:最大并发数,默认设置200,一般建议在500~1000,根据硬件设施和业务来判断minSpareThreads:Tomcat初始化时创建的线程数,默认设置25prestartminSpareThreads:在Tomcat初始化的时候就初始化minSpareThreads的参数值,如果不等于true,minSpareThreads的值就没啥效果了maxQueueSize,最大的等待队列数,超过则拒绝请求--!--在Connector中设置executor属性指向上面的执行器--Connectorexecutor="tomcatThreadPool"port="8080"protocol="HTTP/1.1"connectionTimeout="20000"redirectPort="8443"/1.2.3.4.5.

保存退出,重启tomcat,查看效果。

4.3三种运行模式

tomcat的运行模式有3种:

1.bio默认的模式,性能非常低下,没有经过任何优化处理和支持.

2.nionio(newI/O),是及后续版本提供的一种新的I/O操作方式(即包及其子包)。Javanio是一个基于缓冲区、并能提供非阻塞I/O操作的JavaAPI,因此nio也被看成是non-blockingI/O的缩写。它拥有比传统I/O操作(bio)更好的并发运行性能。

3.apr安装起来最困难,但是从操作系统级别来解决异步的IO问题,大幅度的提高性能.

推荐使用nio,不过,在tomcat8中有最新的nio2,速度更快,建议使用nio2.

设置nio2

Connectorexecutor="tomcatThreadPool"port="8080"protocol=""connectionTimeout="20000"redirectPort="8443"/1.
05代码优化建议

(1)尽可能使用局部变量

调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中速度较快,其他变量,如静态变量、实例变量等,都在堆中创建,速度较慢。另外,栈中创建的变量,随着方法的运行结束,这些内容就没了,不需要额外的垃圾回收。

(2)尽量减少对变量的重复计算

明确一个概念,对方法的调用,即使方法中只有一句语句,也是有消耗的。所以例如下面的操作:

for(inti=0;();i++){}1.

建议替换为:

intlength=();for(inti=0,ilength;i++){}1.

这样,在()很大的时候,就减少了很多的消耗。

(3)尽量采用懒加载的策略,即在需要的时候才创建

Stringstr="aaa";if(i==1){(str);}//建议替换成if(i==1){Stringstr="aaa";(str);}1.2.3.4.5.6.7.8.

(4)异常不应该用来控制流程

异常对性能不利。抛出异常首先要创建一个新的对象,Throwable接口的构造函数调用名为fifillInStackTrace()的本地同步方法,fifillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,Java虚拟机就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。异常只能用于错误处理,不应该用来控制程序流程。

(5)不要将数组声明为publicstaticfinal

因为这毫无意义,这样只是定义了引用为staticfinal,数组的内容还是可以随意改变的,将数组声明为public更是一个安全漏洞,这意味着这个数组可以被外部类所改变。

(6)不要创建一些不使用的对象,不要导入一些不使用的类

这毫无意义,如果代码中出现"Thevalueofthelocalvariableiisnotused"、"",那么请删除这些无用的内容

(7)程序运行过程中避免使用反射

反射是Java提供给用户一个很强大的功能,功能强大往往意味着效率不高。不建议在程序运行过程中使用尤其是频繁使用反射机制,特别是Method的invoke方法。

如果确实有必要,一种建议性的做法是将那些需要通过反射加载的类在项目启动的时候通过反射实例化出一个对象并放入内存。

(8)使用数据库连接池和线程池

这两个池都是用于重用对象的,前者可以避免频繁地打开和关闭连接,后者可以避免频繁地创建和销毁线程。

(9)容器初始化时尽可能指定长度

容器初始化时尽可能指定长度,如:newArrayList(10);newHashMap(32);避免容器长度不足时,扩容带来的性能损耗。

(10)ArrayList随机遍历快,LinkedList添加删除快

(11)使用Entry遍历map

(12)不要手动调用System().gc;

(13)String尽量少用正则表达式

正则表达式虽然功能强大,但是其效率较低,除非是有需要,否则尽可能少用。

replace()不支持正则replaceAll()支持正则

如果仅仅是字符的替换建议使用replace()。

(14)日志的输出要注意级别

(15)对资源的close()建议分开操作

最后

另外还整理成了40多套PDF文档:全套的Java面试宝典手册,“性能调优+微服务架构+并发编程+开源框架+分布式”等七大面试专栏,包含Tomcat、JVM、MySQL、SpringCloud、SpringBoot、Dubbo、并发、Spring、SpringMVC、MyBatis、Zookeeper、Ngnix、Kafka、MQ、Redis、MongoDB、memcached等等。如果你对这个感兴趣,小编可以免费分享。

很赞哦!(158)