jvm启动参数
⼀、内存优化
Linux系统中tomcat的启动参数
export JAVA_OPTS="-server -Xms1400M -Xmx1400M -Xss512k -XX:+AggressiveOpts -XX:+UseBiasedLocking -XX:PermSize=128M -XX:MaxPermSize=256M -XX:+DisableExplicitGC -XX:MaxTenuringThreshold=31 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC  -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m  -
XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -Djava.awt.headless=true "
1、修改内存等 JVM相关配置(优化tomcat/conf配置⽂件)
Linux下修改TOMCAT_HOME/bin/catalina.sh
JAVA_OPTS="-server -XX:PermSize=512M -XX:MaxPermSize=1024m -Xms2048m -Xmx2048m
windows下修改TOMCAT_HOME/bin/catalina.bat
set JAVA_OPTS=-server -XX:PermSize=512M -XX:MaxPermSize=1024m -Xms2048m -Xmx2048m
2、参数介绍
-server 启⽤ JDK的 server 版本
-Xms Java虚拟机初始化时堆的最⼩内存(⼀般-Xms与-Xmx设成⼀样的值,避免JVM因为频繁的GC导致性能⼤起⼤落)
-Xmx Java虚拟机可使⽤堆的最⼤内存
-XX:PermSize Java虚拟机永久代⼤⼩
-XX:MaxPermSize Java虚拟机永久代⼤⼩最⼤值(设置过⼩会导致java.lang.OutOfMemoryError: PermGen space 就是内存益出)
-Xmn:young generation 年轻代的heap⼤⼩
整个堆⼤⼩=年轻代⼤⼩ + 年⽼代⼤⼩ + 持久代⼤⼩。所以增⼤年轻代后,将会减⼩年⽼代⼤⼩
-XX:NewSize 设置新⽣代初始化⼤⼩
-XX:MaxNewSize 设置新⽣代最⼤⼤⼩
-Xss  设定每个线程的堆栈⼤⼩
-XX:+AggressiveOpts
作⽤如其名(aggressive),启⽤这个参数,则每当JDK版本升级时,你的JVM都会使⽤最新加⼊的优化技术(如果有的话)
-XX:+UseBiasedLocking
启⽤⼀个优化了的线程锁,我们知道在我们的appserver,每个http请求就是⼀个线程,有的请求短有的请求长,就会有请求排队的现象,甚⾄还会出现线程阻塞,这个优化了的线程锁使得你的appserver内对线程处理⾃动进⾏最优调配。
-XX:+DisableExplicitGC
在程序代码中不允许有显⽰的调⽤”()”。看到过有两个极品⼯程中每次在DAO操作结束时⼿动调⽤()⼀下,觉得这样做好像能够解决它们的out ofmemory问题⼀样,付出的代价就是系统响应时间严重降低,就和我在关于Xms,Xmx⾥的解释的原理⼀样,这样去调⽤GC导致系统的JVM⼤起⼤落,性能不到什么地⽅去哟!
-XX:+UseParNewGC
对年轻代采⽤多线程并⾏回收,这样收得快。
-XX:+UseConcMarkSweepGC
即CMS gc,这⼀特性只有jdk1.5即后续版本才具有的功能,它使⽤的是gc估算触发和heap占⽤触发。
我们知道频频繁的GC会造⾯JVM的⼤起⼤落从⽽影响到系统的效率,因此使⽤了CMS GC后可以在GC次数增多的情况下,每次GC的响应时间却很短,⽐如说使⽤了CMS GC后经过jprofiler的观察,GC被触发次数⾮常多,⽽每次GC耗时仅为⼏毫秒。
-XX:MaxTenuringThreshold
设置垃圾最⼤年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进⼊年⽼代。对于年⽼代⽐较多的应⽤,可以提⾼效率。如果将此值设置为⼀个较⼤值,则年轻代对象会在Survivor区进⾏多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。
这个值的设置是根据本地的jprofiler监控后得到的⼀个理想的值,不能⼀概⽽论原搬照抄。
-XX:+CMSParallelRemarkEnabled
在使⽤UseParNewGC 的情况下, 尽量减少 mark 的时间
-XX:+UseCMSCompactAtFullCollection
在使⽤concurrent gc 的情况下, 防⽌ memoryfragmention, 对live object 进⾏整理, 使 memory 碎⽚减少。
-XX:LargePageSizeInBytes
指定 Java heap的分页页⾯⼤⼩
-XX:+UseFastAccessorMethods
get,set ⽅法转成本地代码
-XX:+UseCMSInitiatingOccupancyOnly
指⽰只有在 oldgeneration 在使⽤了初始化的⽐例后concurrent collector 启动收集
-XX:CMSInitiatingOccupancyFraction=70
CMSInitiatingOccupancyFraction,这个参数设置有很⼤技巧,基本上满⾜(Xmx-Xmn)*(100- CMSInitiatingOccupancyFraction)/100>=Xmn 就不会出现promotion failed。在我的应⽤中Xmx是6000,Xmn是512,那么Xmx-Xmn是5488兆,也就是年⽼代有5488
兆,CMSInitiatingOccupancyFraction=90说明年⽼代到90%满的时候开始执⾏对年⽼代的并发垃圾回收(CMS),这时还剩10%的空间是5488*10%=548兆,所以即使Xmn(也就是年轻代共512兆)⾥所有对象都搬到年⽼代⾥,548兆的空间也⾜够了,所以只要满⾜上⾯的公式,就不会出现垃圾回收时的promotion failed;
因此这个参数的设置必须与Xmn关联在⼀起。
-Djava.awt.headless=true
这个参数⼀般我们都是放在最后使⽤的,这全参数的作⽤是这样的,有时我们会在我们的J2EE⼯程中使⽤⼀些图表⼯具如:jfreechart,⽤于在web ⽹页输出GIF/JPG等流,在winodws环境下,⼀般我们的app server在输出图形时不会碰到什么问题,但是在linux/unix环境下经常会碰到⼀个exception导致你在winodws开发环境下图⽚显⽰的好好可是在linux/unix下却显⽰不出来,因此加上这个参数以免避这样的情况出现。
上述这样的配置,基本上可以达到:
系统响应时间增快
ü  JVM回收速度增快同时⼜不影响系统的响应率
ü  JVM内存最⼤化利⽤
ü  线程阻塞情况最⼩化
3、验证
可以利⽤JDK⾃带的⼯具进⾏验证,这些⼯具都在JAVA_HOME/bin⽬录下:
1)jps:⽤来显⽰本地的java进程,以及进程号,进程启动的路径等。
2)jmap:观察运⾏中的JVM 物理内存的占⽤情况,包括Heap size , Perm size等。
进⼊命令⾏模式后,进⼊JAVA_HOME/bin⽬录下,然后输⼊jps命令:
① jps
② jmap -heap pid
配置完成后可重启Tomcat ,通过以下命令进⾏查看配置是否⽣效:
  ⾸先查看Tomcat 进程号:sudo lsof -i:9027
我们可以看到Tomcat 进程号是 12222 。
  查看是否配置⽣效:sudo jmap – heap 12222
我们可以看到MaxHeapSize 等参数已经⽣效。
⼀、Tomcat内存使⽤ 
整个堆⼤⼩=年轻代⼤⼩ + 年⽼代⼤⼩ + 持久代⼤⼩。
Java应⽤每创建⼀个线程,在JVM的内存⾥也会创建⼀个Thread对象,但是同时也会在操作系统⾥创建⼀个真正的物理线程(参考JVM规范),操作系统会在TOMCAT余下的内存⾥创建这个物理线程,⽽不是在JVM的Xmx设置的内存堆⾥创建。
-Xss128k:设置每个线程的堆栈⼤⼩。结论:要想创建更多的线程,必须减少分配给JVM的最⼤内存。创建线程的最⼤个数的估算公式:  (MaxProcessMemory-JVMMemory-ReservedOsMemory)/(ThreadStackSize)=Number of threads   
(1)MaxProcessMemory:指的是⼀个进程的最⼤内存
(2)JVMMemory:JVM内存
(3)ReservedOsMemory:保留的操作系统内存
(4)ThreadStackSize:线程栈的⼤⼩。
①线程栈的⼤⼩是个双刃剑,如果设置过⼩,可能会出现栈溢出,特别是在该线程内有递归、⼤的循环时出现溢出的可能性更⼤
②如果该值设置过⼤,就有影响到创建栈的数量,如果是多线程的应⽤,就会出现内存溢出的错误。
⼆、Tomcat运⾏占⽤内存
Tomcat运⾏占⽤内存= Xmx占⽤的内存 + Perm Generation(永久保存区域)占⽤内存 + 所有Java应⽤创建线程数x 1M。
1). Young Generation(年轻代):⽤于存放“早逝”对象(即瞬时对象)。
例如:在创建对象时或者调⽤⽅法时使⽤的临时对象或局部变量。
年轻代分三个区。⼀个Eden区,两个Survivor区。⼤部分对象在Eden区中⽣成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的⼀个),当这个Survivor区满时,此区的存活对象将被复制到另外⼀个Survivor区,当这个Survivor去也满了的时候,从第⼀个Survivor区复制过来的并且此时还存活的对象,将被复制“年⽼区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同⼀个区中可能同时存在从Eden复制过来对象,和从前⼀个Survivor复制过来的对象,⽽复制到年⽼区的只有从第⼀个Survivor去过来的对象。⽽且,Survivor区总有⼀个是空的。
2). Tenured Generation(年⽼代):⽤于存放“驻留”对象(即较长时间被引⽤的对象)。往往体现为⼀个⼤型程序中的全局对象或长时间被使⽤的对象。
3). Perm Generation(永久保存区域):⽤于存放“永久”对象。这些对象管理着运⾏于JVM中的类和⽅法。持久代对垃圾回收没有显著影响,但是有些应⽤可能动态⽣成或者调⽤⼀些class,例如Hibernate等,在这种时候需要设置⼀个⽐较⼤的持久代空间来存放这些运⾏过程中新增的类。持久代⼤⼩通过-XX:MaxPermSize=进⾏设置。
三、顺便提及⼀下在JVM中两种垃圾回收⽅式:
1). ⼀种叫做Minor(次收集)。Minor在YoungGeneration(年轻代)的空间被对象全部占⽤后执⾏,主要是对YoungGeneration中的对象进⾏垃圾收集。
2). ⼀种叫做Major(主收集)。Major是针对于整个Heap size(Xms和Xmx设置为JVM使⽤的内存,但不包括永久保存区域使⽤的内存)的垃圾收集。其中Minor⽅式的收集经常发⽣,并且Minor收集所占⽤的系统时间⼩。⽽Major⽅式的垃圾收集则是⼀种“昂贵”的垃圾收集⽅式,因为在Major要对整个Heap size进⾏垃圾收集,这会使得应⽤停顿的时间变得较长。
-Xms<size>
JVM初始化堆的⼤⼩
-Xmx<size>
JVM堆的最⼤值
初始化堆的⼤⼩执⾏了虚拟机在启动时向系统申请的内存的⼤⼩
当应⽤程序需要的内存超出堆的最⼤值时虚拟机就会提⽰内存溢出,并且导致应⽤服务崩溃。因此⼀般建议堆的最⼤值设置为可⽤内存的最⼤值的80%。
常见的 Java 内存溢出有以下四种
1、堆内存溢出(Java heap space)
现象:
  (1)压测执⾏⼀段时间后,系统处理能⼒下降。这时⽤JConsole、JVisualVM等⼯具连上服务器查看GC情况,每次GC回收都不彻底
并且可⽤堆内存越来越少。
  (2)压测持续下去,最终在⽇志中有报错信息:java.lang.OutOfMemoryError.Java heap space。
排查⼿段:
  (1)使⽤jmap -histo pid > 命令将堆内存使⽤情况保存到⽂件中,打开⽂件查看排在前50的类中有没有熟悉的或者是公司标注的类名,如果有则⾼度怀疑内存泄漏是这个类导致的。
  (2)如果没有,则使⽤命令:jmap -dump:live,format=b,file=test.dump pid⽣成test.dump⽂件,然后使⽤MAT进⾏分析。
  (3)如果怀疑是内存泄漏,也可以使⽤JProfiler连上服务器在开始跑压测,运⾏⼀段时间后点击“Mark Current Values”,后续的运⾏就会显⽰增量,这时执⾏⼀下GC,观察哪个类没有彻底回收,基本就可以判断是这个类导致的内存泄漏。
解决⽅式:优化代码,对象使⽤完毕,需要置成null。
JVM 在启动的时候会⾃动设置 JVM Heap 的值,可以利⽤ JVM提供的 -Xmn -Xms -Xmx 等选项可进⾏设置。Heap 的⼤⼩是 Young Generation 和 Tenured Generaion 之和。在 JVM 中如果 98%的时间是⽤于 GC,且可⽤的 Heap size 不⾜ 2%的时候将抛出此异常信息。
解决⽅法:⼿动设置 JVM Heap(堆)的⼤⼩。(⼀般调整Tomcat的-Xms和-Xmx即可解决问题,通常将-Xms和-Xmx设置成⼀样,堆的最⼤值设置为物理可⽤内存的最⼤值的80%)
2、永久代 / ⽅法区溢出(PermGen space)
现象:压测执⾏⼀段时间后,⽇志中有报错信息:java.lang.OutOfMemoryError: PermGen space。
产⽣原因:由于类、⽅法描述、字段描述、常量池、访问修饰符等⼀些静态变量太多,将持久代占满导致持久代溢出。
解决⽅法:修改JVM参数,将XX:MaxPermSize参数调⼤。尽量减少静态变量。
PermGen space 的全称是 Permanent Generation space,是指内存的永久保存区域。为什么会内存溢出,这是由于这块内存主要是被 JVM 存放Class 和 Meta 信息的,Class 在被 Load 的时候被放⼊ Perm
Gen space 区域,它和存放 Instance 的 Heap 区域不同,sun 的 GC 不会在主程序运⾏期对 PermGen space 进⾏清理,所以如果你的 APP 会载⼊很多 CLASS 的话,就很可能出现 PermGen space 溢出。
3、栈内存溢出(StackOverflowError)
现象:压测执⾏⼀段时间后,⽇志中有报错信息:java.lang.StackOverflowError。
产⽣原因:线程请求的栈深度⼤于虚拟机所允许的最⼤深度,递归没返回,戒者循环调⽤造成。
解决⽅法:修改JVM参数,将Xss参数改⼤,增加栈内存。栈内存溢出⼀定是做批量操作引起的,减少批处理数据量。
栈溢出了,JVM 依然是采⽤栈式的虚拟机,这个和 C 与 Pascal 都是⼀样的。函数的调⽤过程都体现在堆栈和退栈上了。调⽤构造函数的“层”太多了,以致于把栈区溢出了。通常来讲,⼀般栈区远远⼩于堆区的,因为函数调⽤过程往往不会多于上千层,⽽即便每个函数调⽤需要 1K 的空间(这个⼤约相当于在⼀个 C 函数内声明了 256 个 int 类型的变量),那么栈区也不过是需要 1MB 的空间。通常栈的⼤⼩是 1-2MB 的。通常递归也不要递归的层次过多,很容易溢出。
4、系统内存溢出(unable to create new native thread)
现象:压测执⾏⼀段时间后,⽇志中有报错信息:java.lang.OutOfMemoryError: unable to create new native thread。
产⽣原因:操作系统没有⾜够的资源来产⽣返个线程造成的。系统创建线程时,除了要在Java堆中分配内存外,操作系统本⾝也需要分配资源来创建线程。因此,当线程数量⼤到⼀定程度以后,堆中或许还有空间,但是操作系统分配不出资源来了,就出现这个异常了。
解决⽅法:
  (1)减少堆内存jvm调优参数
  (2)减少线程数量
  (3)如果线程数量不能减少,则减少每个线程的堆栈⼤⼩,通过-Xss减⼩单个线程⼤⼩,以便能⽣产更多的线程。
程序创建的线程数超过了操作系统的限制。对于Linux系统,我们可以通过ulimit -u来查看此限制。
给虚拟机分配的内存过⼤,导致创建线程的时候需要的native内存太少。我们都知道操作系统对每个进程的内存是有限制的,我们启动Jvm,相当于启动了⼀个进程,假如我们⼀个进程占⽤了4G的内存,那
么通过下⾯的公式计算出来的剩余内存就是建⽴线程栈的时候可以⽤的内存。线程栈总可⽤内存=4G-(-Xmx的值)- (-XX:MaxPermSize的值)- 程序计数器占⽤的内存通过上⾯的公式我们可以看出,-Xmx 和MaxPermSize的值越⼤,那么留给线程栈可⽤的空间就越⼩,在-Xss参数配置的栈容量不变的情况下,可以创建的线程数也就越⼩。因此如果是因为这种情况导致的unable to create native thread,那么要么我们增⼤进程所占⽤的总内存,或者减少-Xmx或者-Xss来达到创建更多线程的⽬的。
# 总核数 = 物理CPU个数 X 每颗物理CPU的核数
# 总逻辑CPU数 = 物理CPU个数 X 每颗物理CPU的核数 X 超线程数
# 查看物理CPU个数
cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l
或grep 'physical id' /proc/cpuinfo | sort -u | wc -l
# 查看每个物理CPU中core的个数(即核数)
cat /proc/cpuinfo| grep "cpu cores"| uniq
或者grep 'core id' /proc/cpuinfo | sort -u | wc -l
# 查看逻辑CPU的个数
cat /proc/cpuinfo| grep "processor"| wc -l
或者grep 'processor' /proc/cpuinfo | sort -u | wc -l
# 查看CPU信息(型号)
cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c
或者dmidecode -s processor-version
#查看内存信息
cat /proc/meminfo
参数c:当某些程序发⽣错误时,系统可能会将该程序在内存中的信息写成⽂件(除错⽤),这种⽂件就被称为核⼼⽂件(core file)。此为限制每个核⼼⽂件的最⼤容量
参数d:每个进程数据段的最⼤值
参数f:当前shell可创建的最⼤⽂件容量
参数l:可以锁定的物理内存的最⼤值
参数m:可以使⽤的常驻内存的最⼤值
参数n:每个进程可以同时打开的最⼤⽂件句柄数
参数p:管道的最⼤值
参数s:堆栈的最⼤值
参数t:每个进程可以使⽤CPU的最⼤时间
参数u:每个⽤户运⾏的最⼤进程并发数
参数v:当前shell可使⽤的最⼤虚拟内存
-a 列出所有当前资源极限
-c 设置core⽂件的最⼤值.单位:blocks
-d 设置⼀个进程的数据段的最⼤值.单位:kbytes
-
f Shell 创建⽂件的⽂件⼤⼩的最⼤值,单位:blocks
-h 指定设置某个给定资源的硬极限。如果⽤户拥有 root ⽤户权限,可以增⼤硬极限。任何⽤户均可减少硬极限
-l 可以锁住的物理内存的最⼤值
-m 可以使⽤的常驻内存的最⼤值,单位:kbytes
-n 每个进程可以同时打开的最⼤⽂件数
-p 设置管道的最⼤值,单位为block,1block=512bytes
-s 指定堆栈的最⼤值:单位:kbytes
-S 指定为给定的资源设置软极限。软极限可增⼤到硬极限的值。如果 -H 和 -S 标志均未指定,极限适⽤于以上⼆者
-t 指定每个进程所使⽤的秒数,单位:seconds
-u 可以运⾏的最⼤并发进程数
-v Shell可使⽤的最⼤的虚拟内存,单位:kbytes
输出的每⼀⾏由资源名字、(单位,ulimit命令的参数)、软限制组成。详细解释:
core file size core ⽂件的最⼤值为100 blocks,
data seg size 进程的数据段可以任意⼤
file size ⽂件可以任意⼤
pending signals 最多有2047个待处理的信号
max locked memory ⼀个任务锁住的物理内存的最⼤值为32kB
max memory size ⼀个任务的常驻物理内存的最⼤值
open files ⼀个任务最多可以同时打开1024的⽂件
pipe size 管道的最⼤空间为4096字节
POSIX message queues POSIX的消息队列的最⼤值为819200字节
stack size 进程的栈的最⼤值为8192字节
cpu time 进程使⽤的CPU时间
max user processes  当前⽤户同时打开的进程(包括线程)的最⼤个数为2047
virtual memory 没有限制进程的最⼤地址空间
file locks 所能锁住的⽂件的最⼤个数没有限制
查询系统⽀持的最⼤进程数与线程数,⼀般会很⼤,相当于理论值
/proc/sys/kernel/pid_max
/proc/sys/kernel/threads-max
系统限制某⽤户下最多可以运⾏多少进程或线程
当前⽤户可⽤最⼤线程数:ulimit -u
查询当前某程序的线程或进程数
# pstree -p `ps -e | grep java | awk '{print $1}'` | wc -l
# pstree -p 进程号 | wc -l
上⾯⽤的是管道,关于管道:管道符号"|"左边命令的输出作为右边命令的输⼊
查询当前整个系统已⽤的线程或进程数
pstree -p | wc -l
如果是查看系统中总的线程数,直接⽤top -H选项
如果是查看系统中某个进程的线程数,直接⽤top -H -p 进程pid选项
cat /proc/17592/status
ll /proc/10501/task|wc -l
tail -n 1000 spring.log | grep "2019-03-10"
tail -n 1000的意思是显⽰最后1000⾏。spring.log是我的⽇志⽂件的名称。grep的意思是查⽂件⾥符合条件的字符串。如果你希望动态地查看⽇志,可以输⼊例如命令:
tail -f spring.log