调优的目的是什么呢?
1.就是让系统更加的丝滑,让用户体验变得更好。
2.提升系统的性能,提高效率,充分利用jvm内存。
更多参考->JVM常用调优参数
一.JVM参数配置位置
1.java.lang.OutOfMemoryError: Java heap space 解决方法,Java堆异常,需要配置jvm参数,尽量减少GC垃圾回收的次数
2.java.lang.OutOfMemoryError: Java heap space和java.lang.StackOverflowError:java.lang.OutOfMemoryError:java堆空间和栈溢出
这个问题的根源是jvm虚拟机的默认Heap大小是64M,可以通过设置其最大和最小值来实现.设置的方法主要是几个.
1.可以在windows 更改系统环境变量
加上
JAVA_OPTS=-Xms64m -Xmx512m
2.如果用的tomcat,在windows下,可以在
C:\tomcat5.5.9\bin\catalina.bat 中加上:
set JAVA_OPTS=-Xms64m -Xmx256m
位置在: rem Guess CATALINA_HOME if not defined 这行的下面加合适,或者是在setlocal下面添加,便于查看
3.如果是linux系统
Linux 在{tomcat_home}/bin/catalina.sh的前面,加
set JAVA_OPTS='-Xms64 -Xmx512'
spingboot项目启动,可在VM options项配置,tomcat修改catalina.bat(.sh),或者是配置系统环境变量均可,看你喜欢
二.JVM参数详细配置
对于大多数应用来说,Java 堆(Java Heap)是Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。
1).JVM内存结构
由上图可以清楚的看到JVM的内存空间分为3大部分:
-
堆内存 :所有new对象都放在堆里,这也是JVM优化的重点。
-
栈内存:也叫线程栈,是每个线程独立分配的内存空间,存储的包括局部变量,操作数,动态链接,方法出口。它和堆的关系其实是堆的指针引用。
-
本地方法栈:为虚拟机用到的Native方法服务。Native方法是java通过JNI直接调用本地C/C++库,可以认为是暴露给java的本地接口。
-
方法区:类装载系统会把class信息放到方法区,通过字节码引擎执行。会放常量(运行时常量池,那么什么是静态常量池),静态变量,类元信息。jdk8以后就叫元空间。
其中栈内存可以再细分为java虚拟机栈和本地方法栈,堆内存可以划分为新生代和老年代,新生代中还可以再次划分为Eden区、From Survivor区和To Survivor区。
其中一部分是线程共享的,包括 Java 堆和方法区;另一部分是线程私有的,包括虚拟机栈和本地方法栈,以及程序计数器这一小部分内存。
2) 堆内存(Heap)
所有的new对象最开始都放到Eden(伊甸)区,当Eden区沾满之后,JVM会进行一次minor gc,将发现依然存活的对象放到Survivor区,同时讲分代年龄+1(存储在object header里面),会清理eden和form区域,通过复制等算法再次将存活对象放到to区域,依次类推来回复制,当年轻代年龄达到15时,会挪到老年代,比如静态变量,数据库连接池,缓存就属于“老不死”。当老年代内存被占满时,就会触发full gc,full gc会对整个堆进行垃圾回收,因此非常耗时,时间很长,我们要尽量降低full gc的频次。
full gc:完整垃圾回收,整个堆进行垃圾回收
minor gc:局部回收,参考下图,仅回收新生代中的Eden
注意:old区内存满了后,就会触发full gc
minor gc如何进行垃圾回收呢,这里面运用到一个可达性分析算法,里面有一个GC roots根的概念很关键,要理解一下,gc会根据该算法找出该对象的整条GC root链,定义为有效存活对象,会移动到survivor区域;剩下的对象由于找不到对象引用,因此定义为无效对象,从而进行回收。具体如下
- 对于大多数应用来说,Java 堆(Java Heap)是Java 虚拟机所管理的内存中最大的一块。
- Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
- 堆内存是所有线程共有的,可以分为两个部分:年轻代和老年代
- 下图中的Perm代表的是永久代,但是注意永久代并不属于堆内存中的一部分,同时jdk1.8之后永久代已经被移除,JDK1.8之后永久代部分称为Metaspace(元空间)。
- 新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),默认的,Eden : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
当old区内存满了后,就会触发full gc,但是发现引用还在(list还是运行),因此没法回收,但是死循环又一直在new对象,old区无内存可用,于是就OOM。
- 在full gc的时候针对整个heap进行寻找垃圾对象,因此会停顿用户线程(STW)。为什么要停止呢,如果不停止的话,意味着回收线程和用户线程同步执行,回收线程会认为用户线程的内存不是垃圾对象,因此不予理会,但如果用户线程很快执行完,它使用的对象就变成垃圾对象了,这时候回收线程不知道该如何处理,因此为了更好的控制内存,full gc的时候,必须暂停用户线程,回收完成后,再恢复用户线程。
- 因此问题就出现了,如果频繁的full gc,而且收集时间非常长(因为针对整个堆内存回收)就会频繁的停止用户线程,导致系统功能暂时不可用,也就是卡顿现象或无响应。因此优化的点就一目了然:减少full gc次数,让年轻代去收集释放垃圾对象。
3) 栈的核心组成部分
- 局部变量表: 用于存放方法中的局部变量和参数
- 操作数栈: 用于存放方法执行过程中产生的中间结果
- 动态链接:方法区里面的符号引用转为直接引用(即:给出地址)
- 方法出口:记录当前执行方法执行完毕后返回主方法的下一行位置
4) 方法区(Method Area)
方法区也称"永久代",它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。
在JDK8之前的HotSpot JVM,存放这些”永久的”的区域叫做“永久代(permanent generation)”。永久代是一片连续的堆空间,在JVM启动之前通过在命令行设置参数-XX:MaxPermSize来设定永久代最大可分配的内存空间,默认大小是64M(64位JVM默认是85M)。
随着JDK8的到来,JVM不再有 永久代(PermGen)。但类的元数据信息(metadata)还在,只不过不再是存储在连续的堆空间上,而是移动到叫做“Metaspace”的本地内存(Native memory。
方法区或永久代相关设置
- -XX:PermSize=64MB 最小尺寸,初始分配
- -XX:MaxPermSize=256MB 最大允许分配尺寸,按需分配
- XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled 设置垃圾不回收 默认大小
- -server选项下默认MaxPermSize为64m
- -client选项下默认MaxPermSize为32m
5) JVM内存参数设置
- -Xms设置堆的最小空间大小。
- -Xmx设置堆的最大空间大小。
- -Xmn:设置年轻代大小
- -XX:NewSize设置新生代最小空间大小。
- -XX:MaxNewSize设置新生代最大空间大小。
- -XX:PermSize设置永久代最小空间大小。
- -XX:MaxPermSize设置永久代最大空间大小。
- -Xss设置每个线程的堆栈大小
- -XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
- -XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
典型JVM参数配置参考:
- java-Xmx3550m-Xms3550m-Xmn2g-Xss128k
- -XX:ParallelGCThreads=20
- -XX:+UseConcMarkSweepGC-XX:+UseParNewGC
-Xmx3550m:设置JVM最大可用内存为3550M。
-Xms3550m:设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g:设置年轻代大小为2G。整个堆大小=年轻代大小+年老代大小+持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,官方推荐配置为整个堆的3/8。
-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大 小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000 左右。
三.Visual GC 插件安装及使用
jvm分析工具,安装的jdk的bin目录下
jdk1.8.0_31\bin
1.jvisualvm.exe
2.jconsole.exe
1.下载安装
点击下载VisualVM软件
由于我的是mac系统,因此我下载下面这个dmg文件,如果你是windows系统,下载上面那个zip文件,windows系统的是zip压缩包,解压即可,无需安装,找到bin目录下的visualvm.exe文件双击即可启动;如果是mac系统的dmg文件,需要下载后安装启动。
2.安装Visual GC插件
上面下载完成之后,解压缩包,执行文件夹里面bin下面的visualvm.exe应用程序
点击菜单Tools,选择Pulgins,在第二个选项中找到Visual GC,选中,点击Install,然后一路下一步即可安装成功,由于本人已经安装,所以未展示Visual GC,如下图:
插件界面介绍
安装完插件后重启软件,然后打开idea,随后便可以在左边了Local中看到启动的idea应用,双击即可进入监控页面,如下图:
右边就是Visual GC 插件的主要界面了,我们可以看到软件运行时的内存变化情况:
下面对上图中的各个窗口区域做简单介绍,整个界面分为三个区域,分别为:Spaces、Graphs和Histogram。
1.Spaces窗口
上图是呈现了程序运行时我们比较关注的几个区域的内存使用情况:
- Metaspace:方法区,如果JDK1.8之前的版本,就是Perm,JDK7和之前的版本都是以永久
- (PermGen)来实现方法区的,JDK8之后改用元空间来实现(MetaSpace)。
- Old:老年代
- Eden: 新生代Eden区
- S0和S1:新生代的两个 Survivor 区
2.Graphs窗口
该窗口区域包含8个图标,以时间为横坐标动态展示各个指标的运行状态:
下面从上往下对上图中的各个图标表及其状态进行说明:
- Compile Time:编译情况 24266 compoles - 39.416s 表示编译总数为24266,编译总耗时为39.416s。 一个脉冲表示一次JIT编译,脉冲越宽表示编译时间越长。
- Class Loader Time:类加载情况 49052 loaded,39 unloaded - 29.937s表示已加载的数量为49052,卸载的数量为39,耗时为29.537s。
- GC Time:总的(包含新生代和老年代)gc情况记录 123 collections,859.203ms Last Cause:Allocation Failure表示一共经历了123次gc(包含Minor GC和Full GC),总共耗时859.203ms。
- Eden Space:新生代Eden区内存使用情况 (200.00M,34.125M): 31.52M,109 collections,612.827ms表示Eden区的最大容量为200M,当前容量为34.125M,当前已使用31.52M,从开始监控到现在在该内存区域一共发生了109次gc(Minor GC),gc总耗时为612.827ms。
- Survivor 0和Survivor 1:新生代的两个Survivor区内存使用情况 (25.000M,4.250M):1.757M表示该Survivor区的最大容量为25M(默认为Eden区的1/8),当前已用1.757M。
- Old Gen:老年代内存使用情况 (500.000M,255.195M):206.660M,14 collections,246.375ms表示老年区的最大容量为500M,当前容量为255.195M,当前已用206.660M,从开始监控到现在在该内存区域一共发生了14次gc(Full GC),gc总耗时为246.375ms,换算下可以看出单次Full GC要比Minor GC耗时长很多。
- Metaspace:方法区内存使用情况 (1.053G,278.250M):262.345M表示方法区最大容量为1.053G,当前容量为278.250M,当前使用量为262.345MM。
3.Histogram窗口
Histogram窗口是对当前正在被使用的Survivor区内存使用情况的详细描述,如下:
- Tenuring
Threshold:我们知道Survivor区中的对象有一套晋升机制,就是其中的每个对象都有一个年龄标记,每当对象在一次Minor GC中存活下来,其年龄就会+1,当对象的年龄大于一个阈值时,就会进入老年代,这个阈值就是Tenuring Threshold,要注意这个值不是固定不变的,一般情况下Tenuring Threshold会与Max Tenuring Threshold大小保持一致,可如果某个时刻Servivor区中相同年龄的所有对象的内存总等于Survivor空间的一半,那Tenuring
Threshold就会等于该年龄,同时大于或等于该年龄的所有对象将进入老年代。
- Max Tenuring
Threshold:表示新生代中对象的最大年龄值,这个值在JDK1.8中默认为6,在JDK1.7及之前的版本中默认为15,可以通过参数-XX:MaxTenuringThreshold来指定。
- Desired Survivor Size
Survivor空间大小验证阈值(默认是survivor空间的一半),用于给Tenuring
Threshold判断对象是否提前进入老年代。
- Current Survivor Size:
当前Survivor空间大小,单位为字节(Byte,B)。
- Histogram柱状图:
表示Survivor中不同年龄段对象分布。