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
在 程序代码中不允许有显示的调用”System.gc()”。看到过有两个极品工程中每次在DAO操作结束时手动调用System.gc()一下,觉得这样 做好像能够解决它们的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 > test.txt命令将堆内存使用情况保存到test.txt文件中,打开文件查看排在前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 的时候被放入 PermGen 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)减少堆内存
(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