JVM优化
1 JVM虚拟机基本结构
类加载子系统
类加载子系统负责从文件系统或者网络中加载Class信息,加载的类信息存放于一块称为方法区的内存空间。
方法区
就是存放类信息、常量信息、常量池信息、包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)等。
Java堆
java堆在虚拟机启动的时候建立,它是java程序最主要的内存工作区域。几乎所有的java对象实例都存放在java堆中。堆空间是所有线程共享的,这是一块与java应用密切相关的内存空间。
直接内存
java的NIO库允许java程序使用直接内存,从而提高性能,通常直接内存会优于Java堆。读写频繁的场合可能会优先考虑使用。
垃圾回收系统
垃圾回收(Garbage Collection,简称GC)系统是java虚拟机的重要组成部分,垃圾回收器可以对方法区、java堆和直接内存进行回收。其中,java堆是垃圾收集器的工作重点。对于不再使用的垃圾对象,垃圾回收系统会在后台默默工作,默默查找、标识并释放垃圾对象,完成包括java堆、方法区和直接内存中的全自动化管理。
Java栈
JVM 是基于栈的。
Java 虚拟机的内存模型分为两部分:
(1)一部分是线程共享的,包括 Java 堆和方法区;
(2)另一部分是线程私有的,包括虚拟机栈和本地方法栈,以及程序计数器这一小部分内存。
虚拟机栈的栈元素是栈帧,当有一个方法被调用时,代表这个方法的栈帧入栈;当这个方法返回时,其栈帧出栈。因此,虚拟机栈中栈帧的入栈顺序就是方法调用顺序。
每一个java虚拟机线程都有一个私有的java栈,一个线程的java栈在线程创建的时候被创建,java栈中保存着帧信息,java栈中保存着局部变量、方法参数,同时和java方法的调用、返回密切相关。
局部变量表
方法中定义的局部变量以及方法的参数就存放在这张表中。局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象应用(reference类型——不同对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
64位长度的long和double类型的数据会占用2个局部变量空间,其余的数据类型只占用1个。局部变量表所需要的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
操作数栈
用来存放操作数,Java 程序编译之后就变成了一条条字节码指令,其形式类似汇编,但和汇编有不同之处:汇编指令的操作数存放在数据段和寄存器中,可通过存储器或寄存器寻址找到需要的操作数;而 Java 字节码指令的操作数存放在操作数栈中,当执行某条带 n 个操作数的指令时,就从栈顶取 n 个操作数,然后把指令的计算结果(如果有的话)入栈。因此,当我们说 JVM 执行引擎是基于栈的时候,其中的“栈”指的就是操作数栈。
动态连接
每一个栈帧都包含指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。在Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化称为静态解析。另外一部分将在每一次的运行期期间转化为直接引用,这部分称为动态连接。
方法返回地址
帧数据区
栈还需要一些数据来支持常量池的解析,这里的帧数据区保存着访问常量池的指针,方便程序访问常量池,另外,当函数返回或者出现异常时,虚拟机必须有一个异常处理表,方便发送异常的时候找到异常的代码,因此异常处理表也是帧数据区的一部分。
推荐:https://blog.youkuaiyun.com/shen_ming/article/details/81712391
本地方法栈
本地方法栈和java栈非常类似,最大的不同在于java栈用于方法的调用,而本地方法栈则用于本地方法的调用,作为对java虚拟机的重要扩展,java虚拟机允许java直接调用本地方法(通常使用C编写)
PC寄存器
PC(Program Counter)寄存器也是每一个线程私有的空间,java虚拟机会为每一个java线程创建PC寄存器。如果当前方法不是本地方法,PC寄存器就会指向当前正在被执行的指令。如果当前方法是本地方法,那么PC寄存器的值就是undefined
执行引擎
虚拟机最核心的组件就是执行引擎,负责执行虚拟机的字节码。一般会先进行编译成机器码后执行。现代虚拟机为了提高执行效率,会使用即时编译(just in time)技术将方法编译成机器码后再执行。
Java HotSpot Client VM(-client),为在客户端环境中减少启动时间而优化的执行引擎;本地应用开发使用。(如:eclipse)
Java HotSpot Server VM(-server),为在服务器环境中最大化程序执行速度而设计的执行
引擎。应用在服务端程序。(如:tomcat)
Java HotSpot Client模式和Server模式的区别:
当虚拟机运行在-client模式的时候,使用的是一个代号为C1的轻量级编译器, 而-server模式启动的虚拟机采用相对重量级,代号为C2的编译器. C2比C1编译器编译的相对彻底,服务起来之后,性能更高
在jre/lib/jvm.cfg文件中可以设置 -server KNOWN -client IGNORE
2 堆、栈、方法区概念和联系
堆解决的是数据的存储的问题,即数据怎么放、放在哪
栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。
方法区则是辅助堆栈的快永久区(Perm),解决堆栈信息的产生,是先决条件。
例如:User user=new User()
new User()实例化出来的对象存储在java堆中。
User类信息、静态信息都存在于方法区中,而实例化的对象存储在java堆中
user 存放在java栈中,即User真实对象的一个引用。
3 堆结构及对象分代
3.1 分代介绍及必要性
java堆完全自动化管理,通过垃圾回收机制自动清理垃圾对象
Java虚拟机根据对象存活的周期不同,把堆内存划分为几块,一般分为新生代、老年代和永久代(对HotSpot虚拟机而言),这就是JVM的内存分代策略。
堆内存是虚拟机管理的内存中最大的一块,也是垃圾回收最频繁的一块区域,我们程序所有的对象实例都存放在堆内存中。给堆内存分代是为了提高对象内存分配和垃圾回收的效率。
新创建的对象会在新生代中分配内存,经过多次回收仍然存活下来的对象存放在老年代中,静态属性、类信息等存放在永久代中,新生代中的对象存活时间短,只需要在新生代区域中频繁进行GC,老年代中对象生命周期长,内存回收的频率相对较低,不需要频繁进行回收,永久代中回收效果太差,一般不进行垃圾回收,还可以根据不同年代的特点采用合适的垃圾收集算法来提升了收集效率。
3.2 分代的划分
Java虚拟机将堆内存划分为新生代、老年代和永久代,永久代是HotSpot虚拟机特有的概念(JDK1.8之后为metaspace替代永久代),它采用永久代的方式来实现方法区,其他的虚拟机实现没有这一概念,而且HotSpot也有取消永久代的趋势,在JDK 1.7中HotSpot已经
开始了“去永久化”,把原本放在永久代的字符串常量池移出。永久代主要存放常量、类信息、静态变量等数据,与垃圾回收关系不大,新生代和老年代是垃圾回收的主要区域。
新生代分为Eden区、s0区、s1区,s0和s1也被称为from和to区域,s0、s1是两块大小相等并且可以互换的空间。
绝大数情况下,对象首先分配在eden区,经过一次回收后,如果对象还存在则进入s0或者s1,之后每经过一次新生代回收,如果对象存活则它的年龄+1,当对象年龄达到一定年龄后进入老年代
Eden区 | s0区 | S1区 | Tenured区 |
---|---|---|---|
3.2.1 新生代
对象优先存放在新生代中,新生代对象朝生夕死,存活率很低,在新生代中,常规应用进行一次垃圾收集一般可以回收70% ~ 95% 的空间,回收效率很高。
HotSpot将新生代划分为三块,一块较大的Eden空间和两块较小的Survivor空间,默认比例为8:1:1。新生成的对象在Eden区分配(大对象除外,大对象直接进入老年代),当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。
GC开始时,对象只会存在于Eden区和From Survivor区,To Survivor区是空的(作为保留区域)。GC进行时,Eden区中所有存活的对象都会被复制到To Survivor区,而在From Survivor区中,仍存活的对象会根据它们的年龄值决定去向,年龄值达到年龄阀值(默认为15,新生代中的对象每熬过一轮垃圾回收,年龄值就加1,GC分代年龄存储在对象的header中)的对象会被移到老年代中,没有达到阀值的对象会被复制到To Survivor区。接着清空Eden区和From Survivor区,新生代中存活的对象都在To Survivor区。接着, From Survivor区和To Survivor区会交换它们的角色,也就是新的To Survivor区就是上次GC清空的From Survivor区,新的From Survivor区就是上次GC的To Survivor区,总之,不管怎样都会保证To Survivor区在一轮GC后是空的。GC时当To Survivor区没有足够的空间存放上一次新生代收集下来的存活对象时,需要依赖老年代进行分配担保,将这些对象存放在老年代中。
3.2.2 老年代
在新生代中经历了多次GC后仍然存活下来的对象会进入老年代中。老年代中的对象生命周期较长,存活率比较高,在老年代中进行GC的频率相对而言较低,而且回收的速度也比较慢。
什么样的对象会进入老年代:
(1)大对象:所谓的大对象是指需要大量连续内存空间的java对象,最典型的大对象就是那种很长的字符串以及数组,大对象对虚拟机的内存分配就是坏消息,尤其是一些朝生夕灭的短命大对象,写程序时应避免。
(2)长期存活的对象:虚拟机给每个对象定义了一个对象年龄(Age)计数器,如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1,。对象在Survivor区中每熬过一次Minor GC,年龄就增加1,当他的年龄增加到一定程度(默认是15岁), 就将会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。
(3)动态对象年龄判定:为了能更好地适应不同程度的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升到老年代,如果在Survivor空间中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
(4)在一次安全Minor GC 中,仍然存活的对象不能在另一个Survivor 完全容纳,则会通过担保机制进入老年代。
3.2.3 永久代
永久代存储类信息、常量、静态变量、即时编译器编译后的代码等数据,对这一区域而
言,Java虚拟机规范指出可以不进行垃圾收集,一般而言不会进行垃圾回收。
4 垃圾回收算法及分代垃圾收集器
优缺点:https://blog.youkuaiyun.com/code_mzh/article/details/108609792
4.1 垃圾收集器的分类
4.1.1 次收集器
Scavenge GC,指发生在新生代的GC,因为新生代的Java对象大多都是朝生夕死,所以Scavenge GC非常频繁,一般回收速度也比较快。当Eden空间不足以为对象分配内存时,会触发Scavenge GC。
一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
当年轻代堆空间紧张时会被触发
相对于全收集而言,收集间隔较短
4.1.2 全收集器
Full GC,指发生在老年代的GC,出现了Full GC一般会伴随着至少一次的Minor GC(老年代的对象大部分是Scavenge GC过程中从新生代进入老年代),比如:分配担保失败。Full GC的速度一般会比Scavenge GC慢10倍以上。当老年代内存不足或者显式调用System.gc()方法时,会触发Full GC。
当老年代或者持久代堆空间满了,会触发全收集操作
可以使用System.gc()方法来显式的启动全收集
全收集一般根据堆大小的不同,需要的时间不尽相同,但一般会比较长。
4.1.3 垃圾回收器的常规匹配
jdk1.8下测试下,搭配和图中有些差别
默认收集器Parallel Scavenge -XX:+UseParallelGC -XX:+UseParallelOldGC PS Scavenge/PS MarkSweep CMS垃圾收集器 -XX:+UseConcMarkSweepGC ParNew/ConcurrentMarkSweep Serial垃圾收集器 -XX:+UseSerialGC Copy/MarkSweepCompact ParNew垃圾收集器 -XX:+UseParNewGC ParNew/MarkSweepCompact jdk1.8已经弃用 G1垃圾收集器 -XX:+UseG1GC G1 Young Generation/G1 Old Generation
Young | Tenured | JVM options |
---|---|---|
Serial | Serial Old | -XX:+UseSerialGC |
Parallel Scavenge | Serial Old | -XX:+UseParallelGC |
Parallel New | Serial Old | -XX:+UseParNewGC |
Serial | Parallel Old | N/A |
Parallel Scavenge | Parallel Old | -XX:+UseParallelGC -XX:+UseParallelOldGC |
Parallel New | Parallel Old | N/A |
Serial | CMS | -XX:-UseParNewGC -XX:+UseConcMarkSweepGC |
Parallel Scavenge | CMS | N/A |
Parallel New | CMS | -XX:+UseParNewGC -XX:+UseConcMarkSweepGC |
G1 | G1 | -XX:+UseG1GC |
总结:serial垃圾回收器能够搭配serial old
parnew能够搭配serial old和cms
parallel scavenge能够搭配parallel old
G1垃圾收集器能够同时处理新生代和老年代
4.2 垃圾回收器算法
引用计数法(Reference Counting)
比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。
标记-清除算法(Mark-Sweep)
此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。垃圾回收后的空间不是连续的,不连续的内存空间的工作效率要低于连续的内存空间。
复制算法(Copying)
此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,把正在使用中的对象复制到另外一个区域中,之后清楚之前正在使用的内存块中所有的对象。此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,反复去交换两个内存的角色,完成垃圾收集,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。(java中新生代的from和to空间就是使用这个算法)
标记压缩/整理算法(Mark-Compact)
在标记算法基础上做了优化,把存活的对象压缩到内存一端,而后进行垃圾清理。(java中老年代默认使用的就是标记压缩算法)
分代算法
就是根据对象的特点把内存分成N块,而后根据每个内存的特点使用不同的算法
对于新生代和老年代,新生代回收频率很高,但是每次回收耗时都很短,而老年代回收频率很低,但是耗时会相对较长,所以应该尽量减少咯阿年代的GC。
分区算法
其主要就是讲整个内存分为N多个小的空间,每个小空间都可以独立使用,这样细粒度的控制一次回收少多少个小空间,而不是对整个空间进行GC,从而提升性能,并减少GC的停顿时间。
4.3 分代垃圾回收器
4.3.1 串行收集器(Serial)
Serial收集器是Hotspot运行在Client模式下的默认新生代收集器, 它的特点是:只用一个CPU(计算核心)/一条收集线程去完成GC工作, 且在进行垃圾收集时必须暂停其他所有的工作线程(“Stop The World” -后面简称STW)。可以使用-XX:+UseSerialGC打开。
优点 | 缺点 | 应用场景 |
---|---|---|
虽然是单线程收集, 但它却简单而高效, 在VM管理内存不大的情况下(收集几十M~一两百M的新生代), 停顿时间完全可以控制在几十毫秒~一百多毫秒内。没有线程交互(切换)开销 | 只用一个CPU(计算核心)/一条收集线程去完成GC工作, 且在进行垃圾收集时必须暂停其他所有的工作线程(STW) | 在用户的桌面应用场景中,可用内存一般不大(几十M至一两百M),可以在较短时间内完成垃圾收集(几十MS至一百多MS),只要不频繁发生,这是可以接受的 |
4.3.2 并行收集器(ParNew)
ParNew收集器其实是前面Serial的多线程版本, 除使用多条线程进行GC外, 包括Serial可用的所有控制参数、收集算法、STW、对象分配规则、回收策略等都与Serial完全一样(也是VM启用CMS收集器-XX: +UseConcMarkSweepGC的默认新生代收集器)。
由于存在线程切换的开销, ParNew在单CPU的环境中比不上Serial, 且在通过超线程技术实现的两个CPU的环境中也不能100%保证能超越Serial. 但随着可用的CPU数量的增加, 收集效率肯定也会大大增加(ParNew收集线程数与CPU的数量相同, 因此在CPU数量过大的环境中, 可用-XX:ParallelGCThreads=<N>参数控制GC线程数)。
优点 | 缺点 | 应用场景 |
---|---|---|
多CPU下, 收集效率会比Serial大大增加 | 存在线程切换的开销,在单CPU的环境中比不上Serial,且在通过超线程技术实现的两个CPU的环境中也不能100%保证能超越Serial | 在Server模式下,ParNew收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与CMS收集器配合工作; |
4.3.3 Parallel Scavenge收集器
与ParNew类似, Parallel Scavenge也是使用复制算法, 也是并行多线程收集器. 但与其他收集器关注尽可能缩短垃圾收集时间不同, Parallel Scavenge更关注系统吞吐量:
系统吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
停顿时间越短就越适用于用户交互的程序-良好的响应速度能提升用户的体验;而高吞吐量则适用于后台运算而不需要太多交互的任务-可以最高效率地利用CPU时间,尽快地完成程序的运算任务. Parallel Scavenge提供了如下参数设置系统吞吐量:
参数:
-XX:MaxGCPauseMillis (毫秒数) 收集器将尽力保证内存回收花费的时间不超过设定值, 但如果太小将会导致GC的频率增加
-XX:GCTimeRatio (整数:0 < GCTimeRatio < 100) 是垃圾收集时间占总时间的比率
XX:+UseAdaptiveSizePolicy 启用GC自适应的调节策略: 不再需要手工指定-Xmn、-XX:SurvivorRatio、-XX:PretenureSizeThreshold等细节参数, VM会根据当前系统的运行情况收集性能监控信息, 动态调整这些参数以提供最合适的停顿时间或最大的吞吐量
4.3.4 Serial Old收集器
Serial Old是Serial收集器的老年代版本, 同样是单线程收集器,使用“标记-整理”算法
4.3.5 Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本, 使用多线程和“标记-整理”算法, 吞吐量优先, 主要与Parallel Scavenge配合在注重吞吐量及CPU资源敏感系统内使用
4.3.6 CMS收集器(Concurrent Mark Sweep)
CMS(Concurrent Mark Sweep)收集器是一款具有划时代意义的收集器, 一款真正意义上的并发收集器, 虽然现在已经有了理论意义上表现更好的G1收集器, 但现在主流互联网企业线上选用的仍是CMS(如Taobao、微店).
CMS是一种以获取最短回收停顿时间为目标的收集器(CMS又称多并发低暂停的收集器), 基于”标记-清除”算法实现, 整个GC过程分为以下4个步骤:
-
初始标记(CMS initial mark)
-
并发标记(CMS concurrent mark: GC Roots Tracing过程)
-
重新标记(CMS remark)
-
并发清除(CMS concurrent sweep: 已死对象将会就地释放, 注意:此处没有压缩)
其中1,3两个步骤(初始标记、重新标记)仍需STW. 但初始标记仅只标记一下GC Roots能直接关联到的对象, 速度很快; 而重新标记则是为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录, 虽然一般比初始标记阶段稍长, 但要远小于并发标记时间.
CMS特点:
1 CMS默认启动的回收线程数=(CPU数目+3)4
当CPU数>4时, GC线程一般占用不超过25%的CPU资源, 但是当CPU数<=4时, GC线程可能就会过多的占用用户CPU资源, 从而导致应用程序变慢, 总吞吐量降低.
2 无法处理浮动垃圾
可能出现Promotion Failure、Concurrent Mode Failure而导致另一次Full GC的产生: 浮动垃圾是指在CMS并发清理阶段用户线程运行而产生的新垃圾. 由于在GC阶段用户线程还需运行, 因此还需要预留足够的内存空间给用户线程使用, 导致CMS不能像其他收集器那样等到老年代几乎填满了再进行收集.
-XX:CMSInitiatingOccupancyFraction参数来设置GC的触发百分比
-XX:+UseCMSInitiatingOccupancyOnly来启用该触发百分比
3 内存碎片整理
-XX:+UseCMSCompactAtFullCollection开关参数, 用于在Full GC后再执行一个碎片整理过程
-XX:CMSFullGCsBeforeCompaction用于设置在执行N次不进行内存整理的Full GC后, 跟着来一次带整理的(默认为0: 每次进入Full GC时都进行碎片整理).
4.3.7 分区收集- G1收集器
G1(Garbage-First)是一款面向服务端应用的收集器, 主要目标用于配备多颗CPU的服务器治理大内存.
-XX:+UseG1GC启用G1收集器.
与其他基于分代的收集器不同, G1将整个Java堆划分为多个大小相等的独立区域(Region), 虽然还保留有新生代和老年代的概念, 但新生代和老年代不再是物理隔离的了, 它们都是一部分Region(不需要连续)的集合
每块区域既有可能属于O区、也有可能是Y区, 因此不需要一次就对整个老年代/新生代回收. 而是当线程并发寻找可回收的对象时, 有些区块包含可回收的对象要比其他区块多很多. 虽然在清理这些区块时G1仍然需要暂停应用线程, 但可以用相对较少的时间优先回收垃圾较多的Region. 这种方式保证了G1可以在有限的时间内获取尽可能高的收集效率.
G1的新生代收集跟ParNew类似: 存活的对象被转移到一个/多个Survivor Regions. 如果存活时间达到阀值, 这部分对象就会被提升到老年代.如图:
G1新生代特点:
1 一整块堆内存被分为多个Regions.
2 存活对象被拷贝到新的Survivor区或老年代.
3 年轻代内存由一组不连续的heap区组成, 这种方法使得可以动态调整各代区域尺寸.
4 Young GC会有STW事件, 进行时所有应用程序线程都会被暂停.
5 多线程并发GC.
G1\老年代GC\特点如下:
并发标记阶段
1 在与应用程序并发执行的过程中会计算活跃度信息.
2 这些活跃度信息标识出那些regions最适合在STW期间回收(which regions will be best to reclaim during an evacuation pause).
3 不像CMS有清理阶段.
再次标记阶段
1 使用Snapshot-at-the-Beginning(SATB)算法比CMS快得多.
2 空region直接被回收.
拷贝/清理阶段(Copying/Cleanup Phase)*
1 年轻代与老年代同时回收.
2 老年代内存回收会基于他的活跃度信息.
5 jvm 优化
5.1 JDK优化命令
5.1.2 jps
选项 | 说明 |
---|---|
-q | 只显示pid,不显示class名称,jar文件名和传递给main方法的参数 |
-m | 输出传递给main方法的参数,在嵌入式jvm上可能是null |
-l | 输出应用程序main class的完整package名或者应用程序的jar文件完整路径名 |
-v | 输出传递给JVM的参数(线程id、执行线程的主类名和JVM配置信息) |
-V | 隐藏输出传递给JVM的参数 |
5.1.2 jstat
用于提供与JVM性能相关的统计信息,例如垃圾收集,编译活动。 jstat的主要优势在于,它可以在运行JVM且无需任何先决条件的情况下动态捕获这些指标。
jstat -选项 线程id 执行时间(单位毫秒) 执行次数
选项 | 说明 |
---|---|
-class | 显示ClassLoad的相关信息; |
-compiler | 显示JIT编译的相关信息; |
-gc | 显示和gc相关的堆信息; |
-gccapacity | 显示各个代的容量以及使用情况; |
-gcmetacapacity | 显示metaspace的大小 |
-gcnew | 显示新生代信息; |
-gcnewcapacity | 显示新生代大小和使用情况; |
-gcold | 显示老年代和永久代的信息; |
-gcoldcapacity | 显示老年代的大小; |
5.1.3 jinfo
java配置信息工具,实时查看和调整虚拟机各项参数。使用jps命令的-v参数可以查看虚拟机启动时显示指定的参数列表,jinfo -flag可以进行查询,java -XX:+PrintFlagsFinal查看参数默认值
用法:jinfo [option] <pid>
例如:jinfo -flags CMSInitiatingOccupancyFraction 88666 结果:-XX:CMSInitiatingOccupancyFraction=-1
选项 | 说明 |
---|---|
-flag <name> | 用于打印虚拟机标记参数的值,name表示虚拟机标记参数的名称。 |
-flag [+|-]<name> | 用于开启或关闭虚拟机标记参数。+表示开启,-表示关闭。 |
-flag <name>=<value> | 用于设置虚拟机标记参数,但并不是每个参数都可以被动态修改的。 |
-flags | 打印虚拟机参数。 |
-sysprops | 打印系统参数。 |
5.1.4 jmap
java内存映射工具。
命令:jmap -heap pid 描述:显示Java堆详细信息
jmap [选项] pid
选项 | 说明 |
---|---|
no option | 查看进程的内存映像信息,类似 Solaris pmap 命令。 |
-heap | 显示Java堆详细信息 |
-histo[:live] | 显示堆中对象的统计信息 |
-clstats | 打印类加载器信息 |
-dump:<dump-options> | 生成堆转储快照 jmap -dump:format=b,file=ecli.bin 88666 |
-J<flag> | 指定传递给运行jmap的JVM的参数 |
5.1.5 jhat
虚拟机转存快照分析工具,一般不用此命令。
jhat 快照文件.dump
5.1.6 jvisualvm
一个JDK内置的图形化VM监视管理工具
5.1.7 visualgc插件
6 代码分析
Github:https://github.com/leelun/jvmdemo
6.1 查看GC收集器
List<GarbageCollectorMXBean> lists=ManagementFactory.getGarbageCollectorMXBeans(); for(GarbageCollectorMXBean bean:lists){ System.out.println(bean.getName()); }
6.2 JVM内存配置及分析
JVM参数设置:
-Xms5m -Xmx20m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags
代码:main()方法
//申请1M内存 byte[] b1=new byte[110241024]; //申请5M内存 byte[] b2=new byte[510241024]; //申请5M内存 byte[] b3=new byte[510241024];
GC打印信息:
1 [GC (Allocation Failure) DefNew: 759K->127K(1152K), 0.0013231 secs 759K->533K(1920K), [Metaspace: 2657K->2657K(1056768K)], 0.0028295 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 2 [GC (Allocation Failure) DefNew: 19K->0K(1152K), 0.0001956 secs 1577K->1557K(2948K), [Metaspace: 2657K->2657K(1056768K)], 0.0015740 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 3 [GC (Allocation Failure) DefNew: 21K->0K(1216K), 0.0002020 secs 6698K->6677K(8936K), [Metaspace: 2657K->2657K(1056768K)], 0.0016114 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
注意:这里前两个内存申请做分析
内存阶段分析:
阶段1:申请1M内存空间分析(申请1M之前、申请失败后GC回收空间、申请1M空间之后)
(1)申请1M空间之前 新生代759k(1152k) 老年代405k(768k) 堆内存759k(1920k) Metaspace2657K(1056768K)
(2)申请1M内存失败(Allocation Failure),GC回收空间新生代759K->127K(1152K),老年代405K->533K(768K),堆内存759K->533K(1920K)及Metaspace: 2657K->2657K(1056768K)
(3)申请1M空间后内存变化 新生代19K(1152K) 老年代1557K(1796K) 堆内存1577K(2948K) Metaspace2657K(1056768K)
阶段2:申请5M内存空间分析
(1)申请5M空间之前 新生代19K(1152k) 老年代1557K(1796K) 堆内存1577K(2948K) Metaspace2657K(1056768K)
(2)申请5M内存失败(Allocation Failure),GC回收空间新生代19K->0K(1152K),老年代1557K->1557K(1796K),堆内存1577K->1557K(2948K)及Metaspace: 2657K->2657K(1056768K)
(3)申请5M空间后内存变化 新生代21K(1216K) 老年代6677K(7720K) 堆内存6698K(8936K) Metaspace2657K(1056768K)
阶段3:申请5M内存空间分析
这里就不做分析了
下面是添加-XX:+PrintGCDetails后的打印消息
Heap def new generation total 5120K, used 182K [0x00000000fec00000, 0x00000000ff180000, 0x00000000ff2a0000) eden space 4608K, 3% used [0x00000000fec00000, 0x00000000fec2d8a0, 0x00000000ff080000) from space 512K, 0% used [0x00000000ff080000, 0x00000000ff080000, 0x00000000ff100000) to space 512K, 0% used [0x00000000ff100000, 0x00000000ff100000, 0x00000000ff180000) tenured generation total 13696K, used 11797K [0x00000000ff2a0000, 0x0000000100000000, 0x0000000100000000) the space 13696K, 86% used [0x00000000ff2a0000, 0x00000000ffe25608, 0x00000000ffe25800, 0x0000000100000000) Metaspace used 2664K, capacity 4486K, committed 4864K, reserved 1056768K class space used 288K, capacity 386K, committed 512K, reserved 1048576K
分析:
新生代 空间5120k 使用182k
Eden 空间4608K 3%使用率
From(s0) 空间512K 0%使用率
To(s1) 空间512k 0%使用率
Tenured 空间13696K 使用11797k 使用率86%
计算分析:
新生代
0x00000000ff180000-0x00000000fec00000=5632K
5632k=eden+from+to 因为from和to使用复制算法的结构,当一方有内存使用时,另一方空闲。所以 new generation 总大小=5632k- (from或者to)
Eden from to 总大小=第三个参数-第一个参数 使用大小=第二个参数-第一个参数
附表:
JVM常见参数
https://blog.youkuaiyun.com/huaxin_sky/article/details/79435599
选项和默认值 | 描述 |
---|---|
-XX:+PrintGC | 虚拟机启动后,遇到GC就会打印日志 |
-XX:+PrintGCDetails | 查看详细信息,包括各个区的信息 |
-Xms:初始堆大小 | JVM启动的时候,给定堆空间大小。 |
-Xmx:最大堆大小 | JVM运行过程中,如果初始堆空间不足的时候,最大可以扩展到多少。 |
-XX:+PrintCommandLineFlags | 可以将隐式或者显式传给虚拟机的参数输出 |
-Xmn:设置年轻代大小。 | 整个堆大小=年轻代大小+年老代大小+持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。(java8已无持久代) |
-Xss: 设置每个线程的Java栈大小。 | JDK5.0以后每个线程Java栈大小为1M,以前每个线程堆栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。 |
-XX:NewSize=n | 设置年轻代大小 |
-XX:NewRatio=n | 设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代+年老代和的1/4 |
-XX:SurvivorRatio=n | 年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5 |
-XX:MaxPermSize=n | 设置持久代大小(java8已弃用) |
-XX:MaxTenuringThreshold:设置垃圾最大年龄。 | 如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。 |
-XX:MaxDirectMemorySize 默认为-Xms大小 | 直接内存配置参数,直接内存达到上限会触发GC,如果有效的释放空间,也会出现OOM |
-XX:MaxTenuringThreshold | 控制新生代对象的最大年龄,当超过这个年龄范围就会晋升老年代,默认情况下15. |
-XX:PretenureSizeThreshold | 设置对象的大小超过在指定的大小之后,直接晋升老年代。但是TLAB区域优先分配内存。 |
-XX:+UseTLAB | TLAB(Thread Local Allocation Buffer):线程本地分配缓存,一个线程专用的内存分配区域,为了加速对象分配而生。每一个线程都会产生一个TLAB,该线程独享的工作区域,java虚拟机使用TLAB区来避免使用多线程冲突问题,提高了对象分配的效率。TLAB空间一般不会太大,当大对象无法在TLAB分配时,则会直接分配到堆上。 |
-XX:TLABSize | TLAB大小 |
-XX:TLABRefillWasteFraction | 设置维护进入TLAB空间的单个对象的大小,默认64,如果对象大于整个空间的1/64,则在堆创建对象。 |
-XX:+PrintTLAB | 查看TLAB信息 |
-XX:ResizeTLAB | 自调整TLABRefillWasteFraction阈值 |
-XX:TLABWasteTargetPercent | TLAB占eden区的百分比 |
XX:HeapDumpPath=./java_pid<pid>.hprof | 指定HeapDump的文件路径或目录 |
-XX:-HeapDumpOnOutOfMemoryError | 当抛出OOM时进行HeapDump |
-XX:MaxDirectMemorySize | 直接内存大小,默认与Java堆最大值(-Xmx)一样 |
-XX:+UseSerialGC | 配置串行回收器 Serial |
-XX:+UseParallelGC | Parallel Scavenge |
-XX:-UseParallelOldGC | Serial Old |
-XX:+UseParNewGC | Parallel New |
-XX:+UseConcMarkSweepGC | CMS |
-XX:+UseG1GC | G1 |