JVM部分数据整理
一、运行时数据区域
Java运行时内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区域【Java堆、方法区】、直接内存(不受JVM GC管理)
1、线程私有部分
1.1、程序计数器
程序计数器(Program Counter Register)一块较小的内存空间,是当前线程所执行的字节码的行号指示器。
1.2、虚拟机栈
生命周期与线程相同。Java虚拟机栈(Java Virtual Machine Stacks)是描述Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
1.3、本地方法栈
本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,区别是虚拟机栈为执行Java方法服务,而本地方法栈则为Native方法服务。HotSpot VM将本地方法栈和虚拟机栈合二为一。
2、线程共享
2.1、堆区(Heap)
创建的对象和数组都保存在Java堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域。现代VM基本都采用分代收集算法,所以Java堆中还可以细分为:新生代(Eden区、From Survivor区和To Survivor区)和老年代。Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。最新的垃圾收集器ZGC、Shenandoah等已经不再使用分代模型,G1收集器只是逻辑分代,物理上不分代。
2.2、方法区/永久代(元空间)
永久代(Permanent Generation)用于存储被JVM加载的类信息、常量、静态变量、JIT编译后的代码等数据, HotSpot VM把GC分代收集扩展至方法区, 即使用Java堆的永久代来实现方法区, 这样HotSpot的垃圾收集器就可以像管理Java堆一样管理这部分内存, 而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收和类型的卸载, 因此收益一般很小);
在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所代替。区别在于:元空间并不在虚拟机中,而是使用本地内存。
2.3、运行时常量池
运行时常量池(Runtime Constant Pool)是属于方法区的一部分。Class文件中除了有类的版 本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。 Java虚拟机对Class文件的每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会 被虚拟机认可、装载和执行。1.7版本之前,字符串常量池位于PermSpace(永久代),FGC并不会进行清理,1.8之后,字符串常量池位于堆区,会触发FGC的清理
2.4、直接内存
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域。在JDK1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。
二、垃圾回收与算法
1、如何确定垃圾?
什么样的对象被称为垃圾?
没有任何引用指向的对象被称为垃圾。
1.1、引用计数法
通过引用计数(Reference Counting)来判断一个对象是否可以回收,
1.2、可达性分析
Java通过可达性分析(Reachability Analysis)来判定对象是否存活的。通过一系列的“GC Roots”对象作为起点搜索。如果一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。【ps:不可用对象(不可达对象)不等价于可回收对象,不可用对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收】
备注:可以作为GC Roots
的结点?
- 线程栈中引用的对象
- 方法区类静态属性引用的对象
- 方法区常量池引用的对象
- 本地方法栈
JNI
(一般是所谓的Native
方法)引用的对象
2、标记清除算法
“标记-清除”(Mark-Sweep)算法,算法分为“标记”和“清除”两个阶段;标记阶段标记处所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。
不足之处:
- 效率偏低(需要扫描两遍)
- 空间问题(位置不连续,会产生内存碎片)
3、复制算法
“复制”(Copying)算法,将内存按容量划分为大小相等的两块,每次只适用其中的一块。当这一块的内存用完了,就将还存在着的对象复制到另外一块上面,然后再把已使用过的内存空间一次性清理掉。
- 没有碎片,但是浪费空间
4、标记整理算法
“标记-整理”(Mark-Compact)算法,标记过程与Mark-Sweep算法一样,后续步骤是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
- 没有碎片,但是效率偏低(需要移动指针进行调整)
5、分代收集算法
“分代收集”(Generational Collection)算法,是根据对象存活周期的不同将内存划分为几块。根据各个年代的特点采用最适当的收集算法。一般在新生代中采用复制算法,老年代中采用“标记-清除”或者“标记-整理”算法进行回收;
6、垃圾收集类型
新生代的被称为Minor GC
(或者Young GC
);
老年代的被称为Major GC
;
整体性的被称为Full GC
;
在G1收集器中老年代被称为Mixed GC
;
三、Java中四种引用类型
1、强引用(Strong Reference)
普遍存在于程序中,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
2、软引用(Soft Reference)
用来描述一些还有用但并非必需的对象,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收。
3、弱引用(Weak Reference)
也是用来描述非必需对象的,对于弱引用的对象来说,只要垃圾收集器一运行,不管JVM的内存空间是否足够,总会回收该对象占用的内存。
4、虚引用(Phantom Reference)
被称为幽灵引用或者幻影引用,必须和引用队列联合使用,唯一目的就是能在这个对象被收集器回收时收到一个系统通知。(跟踪对象被垃圾回收的状态)
四、垃圾收集器
垃圾收集算法是内存回收的方法论,垃圾收集器是内存回收的具体实现。Java堆内存被划分为新生代和老年代两部分,新生代主要使用 Copying 和 Mark-Sweep 垃圾回收算法;老年代主要使用 Mark-Compact 垃圾回收算法。
0、垃圾收集器与内存大小的关系
- Serial 几十MB
- PS 上百兆 - 几个GB
- CMS - 20GB 左右
- G1 - 上百GB
- ZGC - 4T - 16T(JDK13)
1、Serial收集器
是一个单线程的收集器(新生代采用 Copying 算法,老年代采用 Mark-Compact 算法),在进行垃圾回收的时候,必须暂停其他所有的工作线程,直到它收集结束。这项工作由虚拟机在后台自动发起和自动完成。
2、ParNew 垃圾收集器
是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集室外,其余的行为和Serial 收集器完全一样。
3、Parallel Scavenge 收集器
属于新生代收集器,也使用复制算法,同样是使用多线程,他的关注点是程序达到一个可控制的吞吐量(Throughput),被称为“吞吐量优先”的收集器。
4、Serial Old 收集器
是Serial收集器的老年代版本,同样是单线程收集器,使用“Mark-Compact”算法。
5、Parallel Old 收集器
是 Parallel Scavenge 收集器的老年代版本,使用多线程和“Mark-Compact”算法。
6、CMS 收集器
Concurrent mark sweep(CMS)收集器是一种以获取最短回收停顿时间为目标的收集器,基于“Mark-sweep”算法,
分为以下四个步骤:
-
初始标记:只是标记以下 GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。
-
并发标记:进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程。
-
重新标记:修正在并发标记期间因用户线程继续运作而导致标记产生变动的那一部分对象的标记记录,需要进行STW。
-
并发清除:清除 GC Roots 不可达对象,和用户线程一起工作,不需要暂停工作线程。
7、G1收集器
G1收集器是一款面向服务端应用的垃圾收集器
具备的特点:
- 并行与并发
- 分代收集
- 空间整合
- 可预测的停顿
使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region)
G1收集器的运作大致流程:
- 初始标记
- 并发标记
- 最终标记
- 筛选回收
8、Shenandoah收集器
Shenandoah
使用转发指针和读屏障来实现并发整理。
- 初始标记
- 并发标记
- 最终标记
- 并发清理
- 并发回收
- 初始引用更新
- 并发引用更新
- 最终引用更新
- 并发清理
9、ZGC收集器
ZGC收集器是一款基于Region
内存布局的,(暂时)不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的,以低延迟为首要目标的一款垃圾收集器。
ZGC收集器主要使用染色指针技术来实现。
-
小型
Region
:容量固定为 2MB,用于放置小于 256KB 的小对象。 -
中型
Region
:容量固定为 32MB,用于放置大于登录 256kb 但小于4MB 的对象。 -
大型
Region
:容量不固定,可以动态变化,但必须为2MB的整数倍,用于存放4MB以上的大对象。 -
并发标记
-
并发预备重分配
-
并发重分配
-
并发重映射
五、JVM调优思路
调优是针对特定场景、特定目的的事情,对于GC调优来说,首先就需要清楚调优的目标是什么?从性能的角度看,通常关注三个方面,内存占用(footprint)、延时 (latency)和吞吐量(throughput),大多数情况下调优会侧重于其中一个或者两个方面的目标,很少有情况可以兼顾三个不同的角度。当然,除了上面通常的三个方面,也可能需要考虑其他GC相关的场景,例如,OOM也可能与不合理的GC相关参数有关;或者,应用启动速度方面的需求,GC也会是个考虑的方面。
什么是调优?
- 根据需求进行 JVM 规划和预调优
- 优化运行 JVM 运行环境(慢,卡顿)
- 解决 JVM 运行过程中出现的各种问题(OOM等)
基本调优思路为
- 理解应用需求和问题,确定调优目标。
- 掌握JVM和GC的状态,定位具体的问题,确定真的有GC调优的必要。
- 选择的GC类型是否符合我们的应用特征,如果是,具体问题表现在哪里,是Minor GC过长,还是Mixed GC等出现异常停顿情况;如果不是,考虑切换到什么类 型,如CMS和G1都是更侧重于低延迟的GC选项。
- 通过分析确定具体调整的参数或者软硬件配置。
- 验证是否达到调优目标,如果达到目标,即可以考虑结束调优;否则,重复完成分析、调整、验证这个过程。
JVM
参数调优
- 堆初始值和堆内存最大值保持一致,减少垃圾回收机制次数。
- 设置新生代与老年代回收比例(新生代与老年代1/3或1/4)
六、GC常用参数
GC常用参数
- -Xmn -Xms -Xmx -Xss
年轻代 最小堆 最大堆 栈空间 - -XX:+UseTLAB
使用TLAB,默认打开 - -XX:+PrintTLAB
打印TLAB的使用情况 - -XX:TLABSize
设置TLAB大小 - -XX:+DisableExplictGC
System.gc()不管用 ,FGC - -XX:+PrintGC
- -XX:+PrintGCDetails
- -XX:+PrintHeapAtGC
- -XX:+PrintGCTimeStamps
- -XX:+PrintGCApplicationConcurrentTime (低)
打印应用程序时间 - -XX:+PrintGCApplicationStoppedTime (低)
打印暂停时长 - -XX:+PrintReferenceGC (重要性低)
记录回收了多少种不同引用类型的引用 - -verbose:class
类加载详细过程 - -XX:+PrintVMOptions
- -XX:+PrintFlagsFinal -XX:+PrintFlagsInitial
必须会用 - -Xloggc:opt/log/gc.log
- -XX:MaxTenuringThreshold
升代年龄,最大值15 - 锁自旋次数 -XX:PreBlockSpin 热点代码检测参数-XX:CompileThreshold 逃逸分析 标量替换 ...
这些不建议设置
Parallel常用参数
- -XX:SurvivorRatio
- -XX:PreTenureSizeThreshold
大对象到底多大 - -XX:MaxTenuringThreshold
- -XX:+ParallelGCThreads
并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同 - -XX:+UseAdaptiveSizePolicy
自动选择各区大小比例
CMS常用参数
- -XX:+UseConcMarkSweepGC
- -XX:ParallelCMSThreads
CMS线程数量 - -XX:CMSInitiatingOccupancyFraction
使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收) - -XX:+UseCMSCompactAtFullCollection
在FGC时进行压缩 - -XX:CMSFullGCsBeforeCompaction
多少次FGC之后进行压缩 - -XX:+CMSClassUnloadingEnabled
- -XX:CMSInitiatingPermOccupancyFraction
达到什么比例时进行Perm回收 - GCTimeRatio
设置GC时间占用程序运行时间的百分比 - -XX:MaxGCPauseMillis
停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代
G1常用参数
- -XX:+UseG1GC
- -XX:MaxGCPauseMillis
建议值,G1会尝试调整Young区的块数来达到这个值 - -XX:GCPauseIntervalMillis
?GC的间隔时间 - -XX:+G1HeapRegionSize
分区大小,建议逐渐增大该值,1 2 4 8 16 32。
随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长
ZGC做了改进(动态区块大小) - G1NewSizePercent
新生代最小比例,默认为5% - G1MaxNewSizePercent
新生代最大比例,默认为60% - GCTimeRatio
GC时间建议比例,G1会根据这个值调整堆空间 - ConcGCThreads
线程数量 - InitiatingHeapOccupancyPercent
启动G1的堆空间占用比例