Java的内存模型
Young,年轻代(易被 GC)。Young 区被划分为三部分,Eden 区和两个大小严格相同的 Survivor 区,其中 Survivor 区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在 Young 区间变满的时候,minor GC 就会将存活的对象移到空闲的Survivor 区间中,根据 JVM 的策略,在经过几次垃圾收集后,任然存活于 Survivor 的对象将被移动到 Tenured 区间。
Tenured,老年代。Tenured 区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在 Young 复制转移一定的次数以后,对象就会被转移到 Tenured 区,一般如果系统中用了 application 级别的缓存,缓存中的对象往往会被转移到这一区间。
Perm,永久代。主要保存 class,method,filed 对象,这部门的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到 java.lang.OutOfMemoryError : PermGen space 的错误,造成这个错误的很大原因就有可能是每次都重新部署,但是重新部署后,类的 class 没有被卸载掉,这样就造成了大量的 class 对象保存在了 perm 中,这种情况下,一般重新启动应用服务器可以解决问题。
JVM内存调整
vim /usr/local/tomcat/bin/catalina.sh
1 JAVA_OPTS="-Djava.awt.headless=true -Dfile.encoding=UTF-8 -server -Xms4096m -Xmx4096m -XX:NewSize=512m -XX:MaxNewSize=512m "
JVM 常用参数详解
-Dfile.encoding
:默认文件编码server
:一定要作为第一个参数,在多个CPU时性能佳-Xms
: JVM最小内存,此值可以设置与-Xmx相同,避免每次垃圾回收完成后JVM重新分配内存。-Xmx
:JVM最大可用内存(堆内存),一般不大于物理内存的80%-Xmn
:设置JVM新生代大小(JDK1.4之后版本)。一般-Xmn的大小是-Xms的1/2左右,不要设置的过大或过小,过大导致老年代变小,频繁Full GC,过小导致minor GC频繁。如果不设置-Xmn,可以采用-XX:NewRatio=2来设置,也是一样的效果-XX:NewSize
:年轻代初始大小-XX:MaxNewSize
:年轻代占整个堆内存的最大值-XX:PermSize:永久代(非堆)初始大小,tomcat 8此参数已经移除-XX:MaxPermSize:永久代占整个堆内存的最大值,tomcat8 此参数已经移除-XX:NewRatio
:设置年轻代(包括 Eden 和两个 Survivor 区)与终身代的比值(除去永久代)。设置为 4,则年轻代与老年代所占比值为 1:4,年轻代占整个堆栈的 1/5-XX:MaxTenuringThreshold
:设置垃圾最大年龄,默认为:15。如果设置为 0 的话,则年轻代对象不经过 Survivor 区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在 Survivor 区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。-XX:+DisableExplicitGC
:这个将会忽略手动调用 GC 的代码使得 System.gc() 的调用就会变成一个空调用,完全不会触发任何 GC
如果服务器只运行一个 Tomcat
机子内存如果是 8G,一般 PermSize 配置是主要保证系统能稳定起来就行:JAVA_OPTS="-Dfile.encoding=UTF-8 -server -Xms6144m -Xmx6144m -XX:NewSize=1024m -XX:MaxNewSize=2048m -XX:PermSize=512m -XX:MaxPermSize=512m -XX:MaxTenuringThreshold=10 -XX:NewRatio=2 -XX:+DisableExplicitGC"
机子内存如果是 16G,一般 PermSize 配置是主要保证系统能稳定起来就行:JAVA_OPTS="-Dfile.encoding=UTF-8 -server -Xms13312m -Xmx13312m -XX:NewSize=3072m -XX:MaxNewSize=4096m -XX:PermSize=512m -XX:MaxPermSize=512m -XX:MaxTenuringThreshold=10 -XX:NewRatio=2 -XX:+DisableExplicitGC"
机子内存如果是 32G,一般 PermSize 配置是主要保证系统能稳定起来就行:JAVA_OPTS="-Dfile.encoding=UTF-8 -server -Xms29696m -Xmx29696m -XX:NewSize=6144m -XX:MaxNewSize=9216m -XX:PermSize=1024m -XX:MaxPermSize=1024m -XX:MaxTenuringThreshold=10 -XX:NewRatio=2 -XX:+DisableExplicitGC"
如果是开发机-Xms550m -Xmx1250m -XX:PermSize=550m -XX:MaxPermSize=1250m
涉及堆(Heap)和非堆(Non-Heap) 内存知识
堆内存是存储对象,例如类实例、数组等,在JVM中堆之外的内存称为非堆内存。可以理解为非堆内存留给JVM 自己用。
堆中存的是对象,栈中存的是基本数据类型和堆中对象的引用。在栈中,一个对象对应一个4byte的引用。
栈是运行时的单位,而堆是存储的单位。
垃圾回收
垃圾回收(GC,Garbage Collection )算法:
1. 标记-清除算法(Mark-Sweep)
分为两个阶段,标记和清除。收集器从根节点开始标记所有被引用对象,并标记可用对象,然后对未标记对象执行清除。回收后的空间是不连续的。缺点是暂停整个应用,同时,会产生碎片。
2. 复制算法(copying)
将内存分为两块,每次只使用其中一块,垃圾回收时,将标记的对象拷贝到另外一块中,然后完全清除原来使用的那块内存。复制后的空间是连续的。缺点是需要两块内存空间。
3. 标记-整理算法(Mark-compact)
结合标记-清除和复制两个算法优点。也分为两个阶段,第一个阶段从根节点开始标记所有被引用对象,第二个阶段遍历整个堆,清除未标记对象,并且把存活对象压缩到堆的其中一块,按顺序排放。此方法避免标记- 清除的碎片问题,同时也避免了复制算法的空间问题。
垃圾收集器技术
1. 串行收集
使用单线程处理所有垃圾回收工作,实现容易,效率高,但无法使用多处理器优势,所以这种适合单处理器服务器、数据量(100M左右)比较小和响应时间无要求的场景。
2. 并行收集
使用多线程处理垃圾回收工作,速度快,效率高。理论上处理器越多,性能越好。适合数据量大,响应时间无要求场景。
3. 并发收集(CMS)
前面两个在垃圾回收工作时,需要暂停整个应用,暂停时间跟堆大小决定。先使用多线程来扫描堆内存,标记需要回收的对象,再清除被标记的,某些情况下也会暂停应用。适合数据量大,多处理器,响应时间有高要求的场景。
4. G1 GC
将多个内存分割成多个独立区域,然后并对他们进行垃圾回收。释放内存后,G1还可以压缩空闲的堆内存。
选择哪种垃圾收集器,需要根据应用场景、硬件资源以及吞吐量来决定。一般采用CMS。
指定垃圾回收技术
在catalina.sh中的JAVA_OPS指定
-XX:+UseSerialGC 串行垃圾回收器
-XX:+UseParallelGC 并行垃圾回收器
-XX:+UseConcMarkSweepGC 并发标记扫描垃圾回收器
-XX:ParallelCMSThreads= 并发标记扫描垃圾回收器 =为使用的线程数量
打印垃圾回收信息:
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
maxThreads连接数
默认值:
1
2
3
4 <!--
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
-->
修改为:
1
2
3
4
5
6
7
8
9 <Executor
name="tomcatThreadPool"
namePrefix="catalina-exec-"
maxThreads="500"
minSpareThreads="100"
maxIdleTime="60000"
prestartminSpareThreads = "true"
maxQueueSize = "100"
/>
参数解释:
maxThreads
:最大并发数,默认设置 200,一般建议在 500 ~ 800,根据硬件设施和业务来判断minSpareThreads
:Tomcat 初始化时创建的线程数,默认设置 25maxIdleTime
:如果当前线程大于初始化线程,那空闲线程存活的时间,单位毫秒,默认60000=60秒=1分钟。prestartminSpareThreads
:在 Tomcat 初始化的时候就初始化 minSpareThreads 的参数值,如果不等于 true,minSpareThreads 的值就没啥效果了maxQueueSize
:最大的等待队列数,超过则拒绝请求
http://tomcat.apache.org/tomcat-8.5-doc/config/executor.html
Connector 参数优化
vim /usr/local/tomcat/conf/server.xml
1
2
3
4 <Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
/>
修改为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 <Connector port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol"
connectionTimeout="20000"
maxConnections="10000"
redirectPort="8443"
enableLookups="false"
acceptCount="100"
maxPostSize="10485760"
maxHttpHeaderSize="8192"
disableUploadTimeout="true"
acceptorThreadCount="2"
compression="on"
compressionMinSize="2048"
compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/javascript"
URIEncoding="utf-8"
processorCache="20000"
tcpNoDelay="true"
connectionLinger="5"
server="Sun Server 7"
/>
参数解释:
connectionTimeout
:Connector接受一个连接后等待的时间(milliseconds),默认值是60000。maxConnections
:这个值表示最多可以有多少个socket连接到tomcat上enableLookups
:禁用DNS查询acceptCount
:当tomcat起动的线程数达到最大时,接受排队的请求个数,默认值为100。maxPostSize
:设置由容器解析的URL参数的最大长度,-1(小于0)为禁用这个属性,默认为2097152(2M) 请注意, FailedRequestFilter 过滤器可以用来拒绝达到了极限值的请求。maxHttpHeaderSize
:http请求头信息的最大程度,超过此长度的部分不予处理。一般8K。disableUploadTimeout
:这个标志允许servlet容器使用一个不同的,通常长在数据上传连接超时。 如果不指定,这个属性被设置为true,表示禁用该时间超时。acceptorThreadCount
:用于接受连接的线程数量。增加这个值在多CPU的机器上,尽管你永远不会真正需要超过2。 也有很多非维持连接,您可能希望增加这个值。默认值是1。compression
:是否启用GZIP压缩 on为启用(文本数据压缩) off为不启用, force 压缩所有数据compressionMinSize
:最小压缩大小,单位BytecompressableMimeType
:压缩的数据类型URIEncoding
:网站一般采用UTF-8作为默认编码。processorCache
:协议处理器缓存的处理器对象来提高性能。 该设置决定多少这些对象的缓存。-1意味着无限的,默认是200。 如果不使用Servlet 3.0异步处理,默认是使用一样的maxThreads设置。 如果使用Servlet 3.0异步处理,默认是使用大maxThreads和预期的并发请求的最大数量(同步和异步)。tcpNoDelay
:如果设置为true,TCP_NO_DELAY选项将被设置在服务器套接字,而在大多数情况下提高性能。这是默认设置为true。connectionLinger
:秒数在这个连接器将持续使用的套接字时关闭。默认值是 -1,禁用socket 延迟时间。server
:隐藏Tomcat版本信息,首先隐藏HTTP头中的版本信息
Tomcat 6、7 设置 nio 更好:protocol:org.apache.coyote.http11.Http11NioProtocol
Tomcat 8 设置 nio2 好:protocol:org.apache.coyote.http11.Http11Nio2Protocol
Tomcat 8 设置 APR 性能更好:protocol:org.apache.coyote.http11.Http11AprProtocol
Tomcat 使用APR设置: http://www.sundayle.com/2018/07/12/tomcat-apr/
Tomcat安全设置
禁用8005端口
vim /usr/local/Tomcat/conf/server.xml
默认值:
1 ><Server port="8005" shutdown="SHUTDOWN">
修改为:
1 <Server port="-1" shutdown="SHUTDOWN">
或修改为其他端口和shutdown
1 <Server port="8015" shutdown="SHUTDOWNXXXX">
修改AJP端口
默认值:
1 <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
修改为:
1 <Connector port="8019" protocol="AJP/1.3" redirectPort="8443" />
使用普通用户启动tomcat
1
2 useradd -s /bin/false tomcat
sudo -u tomcat /bin/bash $tomcat_HOME/bin/startup.sh
关闭自动部署
防止被植入木马等恶意程序
默认值:
1
2 <Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
修改为:
1
2 <Host name="localhost" appBase="webapps"
unpackWARs="false" autoDeploy="false" reloadable="false">
注释或删除tomcat-user.xml所有用户权限
1
2
3
4
5
6 "1.0" encoding="UTF-8" xml version=
<tomcat-users xmlns="http://tomcat.apache.org/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
version="1.0">
</tomcat-users>
隐藏或修改tomcat版本号
1
2
3
4 cd /usr/local/tomcat/lib/
unzip catalina.jar
cd org/apache/catalina/util
vim ServerInfo.properties
1
2
3
4
5
6
7
8
9
10
11
12 server.info=Apache Tomcat/8.5.32
server.number=8.5.32.0
server.built=Jun 20 2018 19:50:35 UTC
<!--
server.info=Apache tomcat/8.5.43
server.number=8.5.43.0
server.built=Jul 4 2019 20:53:15 UTC
-->
server.info=EnjoyMusic
server.number=x.x.x.x
server.built=Today
将以上去掉或修改信息,压缩回jar包
1 jar uvf catalina.jar org/apache/catalina/util/ServerInfo.properties
APR 库性能优化
http://www.sundayle.com/2018/07/12/tomcat-apr/
动静分离
使用Apache与Tomcat整合,因为Tomcat 处理静态文件能力远不足Apache,因此让Apache来处理静态文件,Tomcat处理动态jsp 文件,可以有效提高处理速度。同时也会涉及到一个问题,怎么保存Session?
TomcatSessionID持久化三种方法:
Session粘性:通过浏览器Cookie绑定SessionID ,通过sticky模式将同一Session请求分配到同一Tomcat上。
Session复制:Tomcat通过广播形式将Session 同步到其他Tomcat节点,并且Linux下要手动开启开放广播地址。不易后端节点过多
Session保存数据库(memcache、redis ):将SessionID保存在共享的数据库中。
Out of Memory
OOM(Out of Memory)异常常见有以下几个原因:
老年代内存不足:java.lang.OutOfMemoryError:Javaheapspace
永久代内存不足:java.lang.OutOfMemoryError:PermGenspace
代码bug,占用内存无法及时回收。
1)错误提示:java.lang.OutOfMemoryError:Java heap space
Tomcat默认可以使用的内存为128MB,在较大型的应用项目中,这点内存是不够的,有可能导致系统无法运行。常见的问题是报Tomcat内存溢出错误,Outof Memory(系统内存不足)的异常,从而导致客户端显示500错误,一般调整Tomcat的-Xms和-Xmx即可解决问题,通常将-Xms和-Xmx设置成一样,堆的最大值设置为物理可用内存的最大值的80%。
set JAVA_OPTS=-Xms512m-Xmx512m
2)错误提示:java.lang.OutOfMemoryError: PermGenspace
PermGenspace的全称是Permanent Generationspace,是指内存的永久保存区域,这块内存主要是被JVM存放Class和Meta信息的,Class在被Loader时就会被放到PermGenspace中,它和存放类实例(Instance)的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGenspace进行清理,所以如果你的应用中有很CLASS的话,就很可能出现PermGen space错误,这种错误常见在web服务器对JSP进行precompile的时候。如果你的WEB APP下都用了大量的第三方jar, 其大小超过了jvm默认的大小(4M)那么就会产生此错误信息了。解决方法:
setJAVA_OPTS=-XX:PermSize=128M
3)在使用-Xms和-Xmx调整tomcat的堆大小时,还需要考虑垃圾回收机制。如果系统花费很多的时间收集垃圾,请减小堆大小。一次完全的垃圾收集应该不超过3-5 秒。如果垃圾收集成为瓶颈,那么需要指定代的大小,检查垃圾收集的详细输出,研究垃圾收集参数对性能的影响。一般说来,你应该使用物理内存的 80% 作为堆大小。当增加处理器时,记得增加内存,因为分配可以并行进行,而垃圾收集不是并行的。
https://renwole.com/archives/357
https://www.jianshu.com/p/c8613d17e5fe
http://www.zyops.com/java-tomcat/