JVM Optimize Practice

  • JVM堆内存的分代: 虚拟机的堆内存共划分为三个代:年轻代(Young Generation)、年老代(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java类的类信息,与垃圾收集器要收集的Java对象关系不大。所以,年轻代和年老代的划分才是对垃圾 收集影响比较大的。
  • 年轻代: 所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当一个Survivor区满 时,此区的存活对象将被复制到另外一个Survivor区,当另一个Survivor区也满了的时候,从前一个Survivor区复制过来的并且此时还存 活的对象,将被复制“年老区”。需要注意,两个Survivor区是对称的,没先后关系,所以同一个Survivor区中可能同时存在从Eden区复制过来对象,和从另一个Survivor区复制过来的对象;而复制到年老区的只有从前一个Survivor区(相对的)过来的对象。而且,Survivor区总有一个是空的。特殊的情况下,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。
  • 年老代: 在年轻代中经历了N(可配置-XX:MaxTenuringThreshold)次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
  • 持久代: 用于存放静态数据,如 Java Class, Method 等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些Class,例如 Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中动态增加的类型。持久代大小通过 -XX:MaxPermSize进行设置。
常用命令
jps(Java Virtual Machine Process Status Tool)jps主要用来输出JVM中运行的进程状态信息
jps [options] [hostid]
-q 不输出类名Jar名和传入main方法的参数
-m 输出传入main方法的参数
-l 输出main类或Jar的全限名
-v 输出传入JVM的参数
jstack 如果是在64位机器上,需要指定选项”-J-d64”, 输出 jvm自身线程、用户线程等
jstack [ option ] pid
jstack [ option ] executable core
jstack [ option ] [server-id@]remote-hostname-or-IP
-F当jstack [-l] pid没有相应的时候强制打印栈信息
-l 长列表. 打印关于锁的附加信息,例如属于java.util.concurrent的ownable synchronizers列表.
-m 打印java和native c/c++框架的所有栈信息.
-h | -help打印帮助信息
pid 需要被打印配置信息的java进程id,可以用jps查询.
-线程状态
死锁Deadlock重点关注
等待资源Waiting on condition重点关注
等待获取监视器Waiting on monitor entry重点关注
阻塞Blocked重点关注
执行中Runnable
暂停Suspended
对象等待中Object.wait()  TIMED_WAITING
停止Parked
jmap 查看内存
jmap [option] pid
jmap [option] executable core
jmap [option] [server-id@]remote-hostname-or-IP
-dump:[live]format=b,file=<filename> 使用hprof二进制形式,输出jvm的heap内容到文件假如指定live选项,那么只输出活的对象到文件.
-finalizerinfo 打印正等候回收的对象的信息.
-heap 打印heap的概要信息GC使用的算法heap的配置及wise heap的使用情况.
-histo[:live] 打印每个class的实例数目,内存占用,类全名信息. 如果live子参数加上后,只统计活的对象数量.
-permstat 打印classload和jvm heap持久层的信息. 包含每个classloader的名字,活泼性,地址,父classloader和加载的class数量. 另外,内部String的数量和占用内存数也会打印出来.
-h | -help 打印辅助信息
-J 传递参数给jmap启动的jvm.
pid 需要被打印配相信息的java进程id,可以用jps查看

jmap -histo pid  每个class的实例数目,内存占用,类全名信息
jmap -histo 4939 > a.txt
jmap -dump:format=b,file=fileName pid
jstat 性能分析
jstat [option] [pid]
-class	用于查看类加载情况的统计
-compiler	用于查看HotSpot中即时编译器编译情况的统计
-gc	用于查看JVM中堆的垃圾收集情况的统计
-gccapacity	用于查看新生代老生代及持久代的存储容量情况
-gccause	用于查看垃圾收集的统计情况这个和-gcutil选项一样),如果有发生垃圾收集它还会显示最后一次及当前正在发生垃圾收集的原因
-gcnew	用于查看新生代垃圾收集的情况
-gcnewcapacity	用于查看新生代的存储容量情况
-gcold	用于查看老生代及持久代发生GC的情况
-gcoldcapacity	用于查看老生代的容量
-gcpermcapacity	用于查看持久代的容量
-gcutil	用于查看新生代老生代及持代垃圾收集的情况
-printcompilation	HotSpot编译方法的统计

jstat -gcutil [pid]
-gcutil  新生代老生代及持代垃圾收集的情况

列名	说明
S0	Heap上的 Survivor space 0 区已使用空间的百分比
S1	Heap上的 Survivor space 1 区已使用空间的百分比
E	Heap上的 Eden space 区已使用空间的百分比
O	Heap上的 Old space 区已使用空间的百分比
P	Perm space 区已使用空间的百分比
YGC	从应用程序启动到采样时发生 Young GC 的次数
YGCT	从应用程序启动到采样时 Young GC 所用的时间(单位秒)
FGC	从应用程序启动到采样时发生 Full GC 的次数
FGCT	从应用程序启动到采样时 Full GC 所用的时间(单位秒)
GCT	从应用程序启动到采样时用于垃圾回收的总时间(单位秒)它的值等于YGC+FGC
堆内存 = 年轻代 + 年老代 + 永久代 年轻代 = Eden区 + 2Sourvivor区(From/To)
JVM 参数设置
堆设置
-Xmx3550m设置JVM最大堆内存为3550M
-Xms3550m设置JVM初始堆内存为3550M此值可以设置与-Xmx相同以避免每次垃圾回收完成后JVM重新分配内存
-Xss128k 设置每个线程的栈大小JDK5.以后每个线程栈大小为1M之前每个线程栈大小为256K
        应当根据应用的线程所需内存大小进行调整在相同物理内存下减小这个值能生成更多的线程
        但是操作系统对一个进程内的线程数还是有限制的不能无限生成经验值在 3000~5000 左右
-Xmn2g设置堆内存年轻代大小为2G整个堆内存大小 = 年轻代大小 + 年老代大小 + 持久代大小 
        持久代一般固定大小为64m所以增大年轻代后将会减小年老代大小此值对系统性能影响较大Sun官方推荐配置为整个堆的3/8
-XX:PermSize=256M设置堆内存持久代 初始值为256M(貌似是Eclipse等IDE的初始化参数)
-XX:MaxNewSize=size新生成的对象能占用内存的最大值
-XX:MaxPermSize=512M设置持久代最大值为512M
-XX:NewRatio=4设置堆内存年轻代包括Eden和两个Survivor区与堆内存年老代的比值除去持久代 
        设置为4则年轻代所占与年老代所占的比值为1:4
-XX:SurvivorRatio=4设置堆内存年轻代中Eden区与Survivor区大小的比值 
        设置为4则两个Survivor区JVM堆内存年轻代中默认有2个Survivor区与一个Eden区的比值为2:4
        一个Survivor区占 整个年轻代的1/6
-XX:MaxTenuringThreshold=7表示一个对象如果在救助空间Survivor区移动7次还没有被回收就放入年老代
        如果设置为0的话则年轻代对象不经过Survivor区直接进入年老代对于年老代比较多的应用这样做可以提高效率
        如果将此值设置为一个较大值则年轻代对象会在Survivor区进行多次复制这样可以增加对象在年轻代存活时间
        增加对象在年轻代即被回收的概率
回收器选择
串行收集器

-XX:+UseSerialGC:设置串行收集器

并行收集器(吞吐量优先)
-XX:+UseParallelGC选择垃圾收集器为并行收集器此配置仅对年轻代有效即上述配置下年轻代使用并发收集而年老代仍旧使用串行收集
-XX:ParallelGCThreads=20配置并行收集器的线程数同时多少个线程一起进行垃圾回收此值最好配置与处理器数目相等
-XX:+UseParallelOldGC配置年老代垃圾收集方式为并行收集JDK6.0支持对年老代并行收集
-XX:MaxGCPauseMillis=100设置每次年轻代垃圾回收的最长时间单位毫秒),如果无法满足此时间JVM会自动调整年轻代大小以满足此值
-XX:+UseAdaptiveSizePolicy设置此选项后并行收集器会自动选择年轻代区大小和相应的Survivor区比例
          以达到目标系统规定的最低响应时间或者收集频率等此参数建议使用并行收集器时一直打开
并发收集器(响应时间优先)
-XX:+UseParNewGC设置年轻代为并发收集可与CMS收集同时使用JDK5.0以上JVM会根据系统配置自行设置所以无需再设置此值
          CMS 全称Concurrent Low Pause Collector是jdk1.4后期版本开始引入的新gc算法在jdk5和jdk6中得到了进一步改进
          它的主要适合场景是对响应时间的重要性需求 大于对吞吐量的要求能够承受垃圾回收线程和应用线程共享处理器资源
          并且应用中存在比较多的长生命周期的对象的应用CMS是用于对tenured generation的回收也就是年老代的回收
          目标是尽量减少应用的暂停时间减少FullGC发生的几率利用和应用程序线程并发的垃圾回收线程来 标记清除年老代
-XX:+UseConcMarkSweepGC设置年老代为并发收集
          测试中配置这个以后-XX:NewRatio=4的配置失效了所以此时年轻代大小最好用-Xmn设置
-XX:CMSFullGCsBeforeCompaction=由于并发收集器不对内存空间进行压缩整理
          所以运行一段时间以后会产生碎片”,使得运行效率降低此参数设置运行次FullGC以后对内存空间进行压缩整理
-XX:+UseCMSCompactAtFullCollection打开对年老代的压缩可能会影响性能但是可以消除内存碎片
-XX:+CMSIncrementalMode设置为增量收集模式一般适用于单CPU情况
-XX:CMSInitiatingOccupancyFraction=70表示年老代空间到70%时就开始执行CMS确保年老代有足够的空间接纳来自年轻代的对象
如果使用 throughput collector  concurrent low pause collector 这两种垃圾收集器需要适当的挺高内存大小为多线程做准备

其它
-XX:+ScavengeBeforeFullGC新生代GC优先于Full GC执行
-XX:-DisableExplicitGC禁止调用System.gc()但JVM的gc仍然有效
-XX:+MaxFDLimit最大化文件描述符的数量限制
-XX:+UseThreadPriorities启用本地线程优先级API即使 java.lang.Thread.setPriority() 生效反之无效
-XX:SoftRefLRUPolicyMSPerMB=0:“软引用的对象在最后一次被访问后能存活0毫秒默认为1秒)。
-XX:TargetSurvivorRatio=90允许90%的Survivor空间被占用默认为50%)。提高对于Survivor的使用率——超过就会尝试垃圾回收
辅助信息
-XX:-CITime打印消耗在JIT编译的时间
-XX:ErrorFile=./hs_err_pid.log保存错误日志或者数据到指定文件中
-XX:-ExtendedDTraceProbes开启solaris特有的dtrace探针
-XX:HeapDumpPath=./java_pid.hprof指定导出堆信息时的路径或文件名
-XX:-HeapDumpOnOutOfMemoryError当首次遭遇内存溢出时导出此时堆中相关信息
-XX:OnError=";"出现致命ERROR之后运行自定义命令
-XX:OnOutOfMemoryError=";"当首次遭遇内存溢出时执行自定义命令
-XX:-PrintClassHistogram遇到Ctrl-Break后打印类实例的柱状信息与jmap -histo功能相同
-XX:-PrintConcurrentLocks遇到Ctrl-Break后打印并发锁的相关信息与jstack -l功能相同
-XX:-PrintCommandLineFlags打印在命令行中出现过的标记
-XX:-PrintCompilation当一个方法被编译时打印相关信息
-XX:-PrintGC每次GC时打印相关信息
-XX:-PrintGC Details每次GC时打印详细信息
-XX:-PrintGCTimeStamps打印每次GC的时间戳
-XX:-TraceClassLoading跟踪类的加载信息
-XX:-TraceClassLoadingPreorder跟踪被引用到的所有类的加载信息
-XX:-TraceClassResolution跟踪常量池
-XX:-TraceClassUnloading跟踪类的卸载信息
-XX:-TraceLoaderConstraints跟踪类加载器约束的相关信息
JVM服务调优实战
服务器8 cup, 8G mem
java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
调优方案
-Xmx5g设置JVM最大可用内存为5G
-Xms5g设置JVM初始内存为5G此值可以设置与-Xmx相同以避免每次垃圾回收完成后JVM重新分配内存
-Xmn2g设置年轻代大小为2G整个堆内存大小 = 年轻代大小 + 年老代大小 + 持久代大小 持久代一般固定大小为64m
        所以增大年轻代后将会减小年老代大小此值对系统性能影响较大Sun官方推荐配置为整个堆的3/8
-XX:+UseParNewGC设置年轻代为并行收集可与CMS收集同时使用JDK5.0以上JVM会根据系统配置自行设置所以无需再设置此值
-XX:ParallelGCThreads=8配置并行收集器的线程数同时多少个线程一起进行垃圾回收此值最好配置与处理器数目相等
-XX:SurvivorRatio=6设置年轻代中Eden区与Survivor区的大小比值根据经验设置为6则两个Survivor区与一个Eden区的比值为2:6
        一个Survivor区占整个年轻代的1/8
-XX:MaxTenuringThreshold=30 设置垃圾最大年龄次数)。如果设置为0的话则年轻代对象不经过Survivor区直接进入年老代
        对于年老代比较多的应用可以提高效率如果将此值 设置为一个较大值则年轻代对象会在Survivor区进行多次复制
        这样可以增加对象再年轻代的存活时间增加在年轻代即被回收的概率设置为30表示
        一个对象如果在Survivor空间移动30次还没有被回收就放入年老代
-XX:+UseConcMarkSweepGC设置年老代为并发收集测试配置这个参数以后参数-XX:NewRatio=4就失效了
        所以此时年轻代大小最好用-Xmn设置因此这个参数不建议使用
堆溢时会将堆栈信息输出到文件中的配置
  • -XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=/path/to/heap/dump
G1 2018-01-29 G1-doc
  • 由于CMS有以下缺点所以引出了G1垃圾收集器
    • 缺点:
    • GC过程中会出现STW(Stop-The-World),若Old区对象太多,STW耗费大量时间。
    • CMS收集器对CPU资源很敏感。
    • CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。
    • CMS导致内存碎片问题。
  • 在G1中,堆被划分成 许多个连续的区域(region).每个区域大小相等,在1M~32M之间。JVM最多支持2000个区域,可推算G1能支持的最大内存为2000*32M=62.5G。区域(region)的大小在JVM初始化的时候决定,也可以用-XX:G1HeapReginSize设置。

  • 在G1中没有物理上的Yong(Eden/Survivor)/Old Generation,它们是逻辑的,使用一些非连续的区域(Region)组成的。

  • 初始并行阶段(Initial Marking Phase) 属于Young GC范畴,是stop-the-world活动。对持有老年代对象引用的Survivor区(Root区)进行标记。

  • Root区扫描(Root Region Scanning) 扫描Survivor区中的老年代对象引用,该阶段发生在应用运行时,必须在Young GC前完成。

  • 并行标记(Concurrent Marking) 找出整个堆中存活的对象,对于空区标记为“X”。该阶段发生在应用运行时,同时该阶段活动会被Young GC打断。

  • 重标记(Remark) 清除空区,重计算所有区的存活状态(liveness),是stop-the-world活动。

  • 清除(Cleanup)
    • 选择出存活状态低的区进行收集。
    • 计算存活对象和空区,是stop-the-world活动。
    • 更新记录表,是stop-the-world活动。
    • 重置空区,将其加入空闲列表,是并行活动。
  • 复制(Copying)该阶段是stop-the-world活动,负责将存活对象复制到新的未使用的区。可以发生在年轻代区,日志记录为[GC pause (young)]。 也可以同时发生在年轻代区和老年代区,日志记录为[GC Pause (mixed)]。
总结
  • 与CMS对比:
    • 总体来说,G1跟CMS一样,是一块低延时的收集器,同样牺牲了吞吐量,不过二者之间得到了很好的权衡。
  • G1与CMS对比有一下不同:
    1. 分代: CMS中,堆被分为PermGen,YoungGen,OldGen;而YoungGen又分了两个survivo区域。在G1中,堆被平均分成几个区域(region),在每个区域中,虽然也保留了新老代的概念,但是收集器是以整个区域为单位收集的。
    2. 算法: 相对于CMS的“标记——清理”算法,G1会使用压缩算法,保证不产生多余的碎片。收集阶段,G1会将某个区域存活的对象拷贝的其他区域,然后将整个区域整个回收。
    3. 停顿时间可控:为了缩短停顿时间,G1建立可预存停顿模型,这样在用户设置的停顿时间范围内,G1会选择适当的区域进行收集,确保停顿时间不超过用户指定时间。