原理
JVM在执行Java程序时会把它所管理的内存划分为若干个不同的运行时数据区域,主要包括:程序计数器、方法区、虚拟机栈、本地方法栈和堆:
- 程序计数器可以看作时当前线程所执行的字节码的行号指示器。
- 方法区用于存储被JVM加载的类信息、常量、静态变量等数据。
- 虚拟机栈存储的时Java方法执行的线程内存模型,每一个方法被调用到执行完毕的过程,就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。
- 本地方法栈和虚拟机栈的功能相同,差别是本地方法栈只为本地方法调用服务。
- 堆是JVM管理内存中占用比例最大的一块,用于存储Java程序对象实例,几乎所有的对象实例内存都在这里分配。从内存回收角度和经典垃圾收集器分带理论上看,堆内存空间一般被分为:新生代、老年代、永久代、Eden空间、From/To Survivor等区域。各代功能和划分说明可以参考:Java虚拟机:JVM内存分代策略 - 风中程序猿 - 博客园 。
针对堆空间的优化是Java性能调优的重点之一。如果没有设置JVM堆空间大小,JVM会根据服务器物理内存大小设置默认堆大小的值。例如,在64位的服务器端,当物理内存小于192MB时,JVM堆大小默认选为物理内存的一半;当物理内存大192MB且小于128GB时,JVM堆大小默认选为物理内存的四分之一;当物理内存大于等于128GB时,都为32GB。通常情况下,Java应用程序的会通过参数指定堆大小,具体方法下文会有说明。
应用程序选用多大的堆空间大小及配比,一般要根据程序的GC情况和服务器内存资源进行综合评估,是个循序渐进不断优化的过程,如果垃圾回收(GC)频繁触发,可以尝试增加堆空间缓解。
推荐配置原则:
- 应用程序运行时,计算老年代存活对象的占用空间大小X。程序整个堆大小(Xmx和Xms)设置为X的3~4倍;永久代PermSize和MaxPermSize设置为X的1.2~1.5倍。年轻代Xmn的设置为X的1~1.5倍。老年代内存大小设置为X的2~3倍。
- JDK官方建议年轻代占整个堆大小空间的3/8左右。
- 完成一次Full GC后,应该释放出70%的堆空间(30%的空间仍然占用)。
修改方式
在Java应用程序启动时,添加如下参数并设置大小:
-Xmx | 设置JVM最大可用堆内存大小。 |
-Xms | 设置初始堆大小,一般和Xmx保持一致。 |
-Xmn | 设置年轻代堆大小。 |
-Xss | 设置每个线程的堆大小。JDK 1.5以后每个线程堆栈大小默认为1MB,1.5以前为256K。 |
-XX:NewRatio= | 设置年轻代(包括Eden和两个Survivor区)与老年代的比值(不包括永久代)。如设置为4,则年轻代与老年代所占比值为1:4,年轻代占整个堆栈的1/5。 |
-XX:SurvivorRatio= | 设置年轻代中Eden区与Survivor区的大小比值。如设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6。 |
-XX:MaxPermSize= | 设置永久代堆大小。 |
举例:
java -Xmx3600m -Xms3600m -Xmn2g -Xss128k
设置最大堆空间为3600MB,初始化堆大小为3600MB,年轻代大小为2GB,线程堆大小为128KB。
选择合适的垃圾回收器
原理
垃圾回收器是内存回收的具体实现,JDK自带的垃圾回收器已经完成集成垃圾回收和清理算法,业务程序可以通过设置参数选择垃圾回收器,虚拟机用到的7种经典的垃圾回收器如下表。根据适用内存区域不同,JDK自带的垃圾回收器可分为新生代回收器和老年代回收器,两者可以配合使用。新生代回收器用于堆空间中新生代区域的垃圾回收,老年代回收器用于堆空间中老年代区域的垃圾回收。G1是一种新型的堆内垃圾收集器,既可以用于新生代也可以用于老年代垃圾回收。
下表列举了经典的7种垃圾回收器的说明和功能分类:
名称 | 说明 | 收集模式 | 分代适用类型 |
---|---|---|---|
Serial | 单线程串行收集器 | 串行收集器 | 新生代 |
ParNew | 多线程并行Serial收集器 | 并行收集器 | 新生代 |
Parallel Scavenge | 并行吞吐量优先收集器 | 并行收集器 | 新生代 |
Serial Old | Serial单线程收集器老年代版本 | 串行收集器 | 老年代 |
CMS(Concurrent Mark Sweep) | 并行最短停顿时间收集器 | 并发收集器 | 老年代 |
Parallel Old | Parallel Scavenge并行收集器老年代版本 | 并行收集器 | 老年代 |
G1 | 面向局部收集和基于Region内存布局的新型低延时收集器 | 并发/并行收集器 | 新生代/老年代 |
下图展示了新生代GC和老年代GC配合使用方法,有连线的表示可以配合使用。注意ParNew和Parallel Old是不能同时使用的。
垃圾回收器的选择方法没有通用的准则,要结合项目应用的实际并对GC运行数据的检测来决定。
根据收集模式经典垃圾回收器可分为三类:串行收集器、并行收集器、并发收集器。串行收集器只适用于小数据量的情况,选择主要针对并行收集器和并发收集器。默认情况下,JDK1.5以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK1.5以后,JVM会根据当前系统配置进行判断。
垃圾回收器选择建议:
- 业务应用对吞吐量要求较高,对响应时间没有特别要求的,推荐使用并行收集器。如:科学计算和后台处理程序等等。
- 对响应时间要求较高的中大型应用程序,推荐使用并发收集器。如:web服务器等。
- 对应JDK版本1.8以上,多CPU处理器且内存资源不是瓶颈,建议优先考虑使用G1回收器。
- 单线程应用使用串行收集器。
修改方式
以下表格汇总了各种回收器的分类、特点和修改参数:
名称 | 修改参数 | 特点 |
---|---|---|
Serial | -XX:+UseSerialGC | 用于新生代的单线程收集器,采用复制算法进行垃圾收集。Serial 进行垃圾收集时,所有的用户线程必须暂停(Stop The World)。 |
ParNew | -XX:+UseParNewGC | Serial的多线程版本,在单核CPU环境并不会比Serial更优,它默认开启的收集线程数和CPU核数,可以通过-XX:ParallelGCThreads来设置垃圾收集的线程数。 |
Parallel Scavenge | -XX:+UseParallelGC jdk1.7、jdk1.8 新生代默认使用 | 用于新生代的多线程收集器,ParNew的目标是尽可能缩短垃圾收集时用户线程的停顿时间,Parallel Scavenge的目标是达到一个可控制的吞吐量。通过-XX:MaxGCPauseMillis来设置收集器尽可能在多长时间内完成内存回收,通过-XX:GCTimeRatio来精确控制吞吐量。 |
Serial Old | -XX:+UseSerialOldGC | Serial的老年代版本,采用标记-整理算法单线程收集器。 |
CMS | -XX:+UseConMarkSweepGC | 一种以最短回收停顿时间为目标的收集器,尽量做到最短用户线程停顿时间。CMS是基于标记-清除算法,所以垃圾回收后会产生空间碎片,通过-XX:UseCMSCompactAtFullCollection开启碎片整理(默认开启)。用-XX:CMSFullGCsBeforeCompaction设置执行多少次不压缩(不进行碎片整理)的Full GC之后,跟着来一次带压缩(碎片整理)的Full GC。-XX:ParallelCMSThreads:设定CMS的线程数量。 |
Parallel Old | -XX:+UseParallelOldGC jdk1.7、jdk1.8老年代默认使用 | Parallel Scavenge的老年代版本,使用-XX:ParallelGCThreads限制线程数量。 |
G1 | -XX:+UseG1GC jdk1.7以后才提供,jdk1.9默认 | 一款全新的收集器,兼顾并行和并发功能,能充分利用多CPU资源,运行期间不会产生内存碎片。通过-XX:ParallelGCThreads设置限制线程数量;-XX:MaxGCPauseMillis设置最大停顿时间。 |
关于信创国产平台的JDK的说明要点:
1. TongWeb6支持JDK6及以上版本,TongWeb7支持JDK7及以上版本。
2. 目前国产平台多以Open JDK8为主。统信UOS操作系统带Open JDK11。
3. 国产平台Open JDK由操作系统自带,在安装操作系统时,别忘记安装JDK。
注:国产平台有龙芯、飞腾、鲲鹏、申威、海光、兆芯等CPU,所以国内没有统一提供这些平台版本的OpenJDK,通常由国产操作系统自带。
常见问题:
1. 考虑JDK版本,除TongWeb外,还要考虑部署在TongWeb上的应用支持的JDK版本?
2. 个别情况应用需要JDK5,6,7低版本的支持,但目前国产平台基本不提供这些低版本JDK。
3. 国产各个平台没有提供对外的JDK下载地址,不像Oralce JDK提供统一对外下载:Java Downloads | Oracle 一定要嘱咐在装操作系统时装好JDK。
4. 实在不行,海光、兆芯平台可下载Oracle JDK x86版本,飞腾、鲲鹏可下载Oracle JDK arm版本试试,但不保证兼容性。华为提供毕昇JDK。
5. 某些国产平台提供JDK问题分析帮助, 如:龙芯JDK崩溃,可看官网