引言:java
咱们都知道JVM内存由几个部分组成: Java栈、程序计数器(ProgramCounter)寄存器、本地方法栈、堆、方法区、运行常量池。linux
JVM垃圾回收仅仅针对公共内存区域即:堆和方法区进行。算法
本文主要讨论两点,一是垃圾回收策略,二是调优的方法。windows
将堆和方法区按照对象出现的不一样时间进行分代:tomcat
u 堆中会频繁地建立对象,基于一种分代的思想,按照对象存活时间将堆划分为新生代和旧生代两部分,咱们不能一次垃圾回收新生代存活的对象就放入旧生代,而是要通过几回GC后还存活的对象,咱们才放入旧生代,因此咱们又把新生代再次划分为Eden区和两个Survivor区,让对象建立在Eden区,而后在两个Survivor之间反复复制,最后仍然存活的对象才复制到旧生代中。网络
u 方法区存放的是常量、加载的字节码文件信息等,信息相对稳定。由于不会频繁地建立对象,因此不须要分代,直接GC便可。多线程
由此咱们JVM垃圾回收要扫描的范围是:并发
注:图片来自网络app
新生代:工具
1.全部新对象建立发生在Eden区,Eden区满后触发新生代上的minor GC,将Eden区和非空闲Survivor区存活对象复制到另外一个空闲的Survivor区中。
2.永远保证一个Survivor是空的,新生代minor GC就是在两个Survivor区之间相互复制存活对象,直到Survivor区满为止。
旧生代:
1.Eden区满后触发minor GC将存活对象复制到Survivor区,Survivor区满后触发minor GC将存活对象复制到旧生代。
2.通过新生代的两个Survivor之间屡次复制,仍然存活下来的对象就是年龄相对比较老的,就能够放入到旧生代了,随着时间推移,若是旧生代也满了,将触发Full GC,针对整个堆(有新生代、旧生代和持久代)进行垃圾回收。
持久代:
持久代若是满,将触发Full GC
要执行gc关键在于两点,一是检测出垃圾对象,二是释放垃圾对象所占用的空间。
检测出垃圾对象通常有两种算法:
一、 引用计数法
二、 可达性分析
引用计数法由于没法检测对象之间相互循环引用的问题,基本没有被采用。如今主流的语言的垃圾收集中检测垃圾对象主要仍是“可达性分析”方法,下面也主要介绍JVM可达性分析方法检测垃圾对象。
“可达性分析”算法描述?
经过一系列的名为“GC Root”的对象做为起点,从这些节点向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时,则该对象不可达,该对象是不可以使用的,垃圾收集器将回收其所占的内存。因此JVM判断对象须要存活的原则是:可以被一个根对象到达的对象。
什么是可以到达呢?
就是对象A中引用了对象B,那么就称A到B可达。
GCRoot对象集合?
a.Java虚拟机栈(栈帧中的本地变量表)中的引用的对象。
b.方法区中的类静态属性引用的对象。
c.方法区中的常量引用的对象。
d.本地方法栈中JNI本地方法的引用对象。
前面已经介绍了如何检测出垃圾对象,在检测出垃圾对象以后,须要按照特定的垃圾回收算法进行内存回收,常见的垃圾回收算法有:
u 复制(Copying)
u 标记-清除(Mark-Sweep)
u 标记-整理(Mark-Compact)
u 分代(Generational Collection),借助前面三种算法实现
这里就不一一详述,感兴趣能够自行百度。
上面算法都是理论性的东西,Java虚拟机规范没有规定垃圾收集器具体如何实现,所以不一样厂商、不一样版本虚拟机提供的垃圾收集器可能有所差别。下面列举HotSpot(Sun JDK和Open JDK自带)虚拟机提供的六种垃圾收集器实现:
收集器名称 |
应用目标 |
采用算法 |
引入版本 |
运行方式 |
Serial |
新生代 |
复制算法 |
Jdk1.3.1前 |
串行,单线程 |
ParNew |
新生代 |
复制算法 |
并行,多线程 |
|
Parallel Scavenge |
新生代 |
复制算法 |
Jdk1.4 |
并行,多线程 |
Serial Old |
旧生代 |
标记-整理 |
串行,单线程 |
|
Parallel Old |
旧生代 |
标记-整理 |
Jdk1.6 |
并行,多线程 |
CMS |
旧生代 |
标记-清除 |
Jdk1.5 |
并发,多线程 |
并行(Parallel):多条垃圾收集线程并行工做,而用户线程仍处于等待状态。
并发(Concurrent):垃圾收集线程与用户线程一段时间内同时工做(不是并行,而是交替执行)。
总结:
一、两个串行收集器、三个并行收集器、一个并发收集器。
二、ParNew收集器是Serial的多线程版本。
三、Serial Old收集器是Serial收集器的旧生代版本。
四、Parallel Scavenge收集器以吞吐量为目标,适合在后台运算而不须要太多交互的任务。
五、Parallel Old收集器是Parallel Scavenge的旧生代版本。
六、Parallel Scavenge收集器和Parallel Old收集器是名副其实的“吞吐量优先”组合。
七、除CMS外,其余收集器工做时都须要暂停其余全部线程,CMS是第一款真正意义上的并发(Concurrent)收集器,第一次实现了让垃圾收 集器线程与 用户线程同时工做,是一款以最短停顿时间为目标的收集器,适合交互性较多的场景,这也是与Parallel Scavenge/Parallel Old吞吐量优先组合的区别。
八、新生代由于回收留下的对象少,因此采用标记-复制法。
九、旧生代由于回收留下的对象多,因此采用标记-清除/标记-整理算法。
虚拟机提供了参数,以便用户根据本身的需求设置所需的垃圾收集器:
JVM运行参数 |
新生代 |
旧生代 |
-XX:+UseSerialGC(Client模式默认值) |
Serial |
Serial Old |
-XX:+UseParNewGC |
ParNew |
Serial Old |
-XX:+UseConcMarkSweepGC |
ParNew |
CMS(Serial Old备用) |
-XX:+UseParallelGC(Server模式默认值) |
Parallel Scavenge |
Serial Old |
-XX:+UseParallelOldGC |
Parallel Scavenge |
Parallel Old |
减小minor gc的频率、以及full gc的次数。
1.使用JDK提供的内存查看工具,如JConsole和Java VisualVM
2.控制堆内存各个部分所占的比例
3.采用合适的垃圾收集器
n -verbose.gc:显示GC的操做内容。打开它,能够显示最忙和最空闲收集行为发生的时间、收集先后的内存大小、收集须要的时间等。
n -xx:+printGCdetails:详细了解GC中的变化。
n -XX:+PrintGCTimeStamps:了解这些垃圾收集发生的时间,自JVM启动之后以秒计量。
n -xx:+PrintHeapAtGC:了解堆的更详细的信息。
若是新生代过小,会致使频繁GC,并且大对象对直接进入旧生代引起full gc
若是新生代太大,会诱发旧生代full gc,并且新生代的gc耗时会延长
建议新生代占整个堆1/3合适,相关JVM参数以下:
n -Xms:初始堆大小
n -Xmx:最大堆大小
n - Xmn:新生代大小
n -XX:PermSize=n:持久代最大值
n -XX:MaxPermSize=n:持久代最大值
n -XX:NewRatio=n:设置新生代和旧生代的比值。如:为3,表示新生代与旧生代比值为1:3,新生代占整个新生代旧生代和的1/4。
若是Eden过小,会致使频繁GC。
若是Eden太大,会致使大对象直接进入旧生代,下降对象在新生代存活时间。
n -XX:SurvivorRatio=n:新生代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
n -XX:PretenureSizeThreshold:直接进入旧生代中的对象大小,设置此值后,大于这个参数的对象将直接在旧生代中进行内存分配。
n -XX:MaxTenuringThreshold:对象转移到旧生代中的年龄,每一个对象经历过一次新生代GC(Minor GC)后,年龄就加1,到超过设置的值后,对象转移到旧生代。
经过JVM参数设置所使用的垃圾收集器参考前面的介绍,这里关注其余一些设置。
并行收集器设置
n -XX:ParallelGCThreads=n:设置并行收集器收集时并行收集线程数。
n -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间,仅对ParallelScavenge生效。
n -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比,仅对Parallel Scavenge生效。
并发收集器设置
n -XX:CMSInitiatingOccupancyFraction:默认设置下,CMS收集器在旧生代使用了68%的空间后就会被激活。此参数就是 设置旧生代空间被使用多少后触发垃圾收集。注意要是CMS运行期间预留的内存没法知足程序须要,就会出现concurrent mode failure,这时候就会启用Serial Old收集器做为备用进行旧生代的垃圾收集。
n -XX:+UseCMSCompactAtFullCollection:空间碎片过可能是标记-清除算法的弊端,此参数设置在FULL GC后再进行一个碎片整理过程
n -XX:CMSFullGCsBeforeCompaction:设置在若干次垃圾收集以后再启动一次内存碎片整理
一、修改 tomcat\bin\Catalina.bat 文件
windows环境下:
在166行左右
rem Execute Java with the applicable properties ”如下每行
%_EXECJAVA% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -Djava.endorsed.dirs="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%
在 %DEBUG_OPTS% 后面添加-Xms256m -Xmx512m
linux环境下:
打开在Tomcat的安装目录的bin文件的catalina.sh文件,进入编辑状态.
在注释后面加上以下脚本:
JAVA_OPTS='-Xms512m -Xmx1024m'
JAVA_OPTS="$JAVA_OPTS -server -XX:PermSize=64M -XX:MaxPermSize=256m"
其中 JAVA_OPTS='-Xms512m -Xmx1024m' 是设置Tomcat使用的内存的大小.
-XX:PermSize=64M -XX:MaxPermSize=256m 指定类空间(用于加载类)的内存大小
保存后,从新以命令行的方式运行 tomcat ,便可,而后经过最后面介绍的如何观察tomcat现有内存状况的方法进行查看是否已经变动成功。