JVM笔记

2018-09-11  本文已影响0人  SithCait

JVM的基本结构

image.png
image.png
新生代GC
https://blog.csdn.net/oopsoom/article/details/40348125

jdk1.7
JVM堆内存分为2块:Permanent Space 和 Heap Space。
Permanent 即 持久代(Permanent Generation),主要存放的是Java类定义信息,与垃圾收集器要收集的Java对象关系不大。
Heap = { Old + NEW = {Eden, from, to} },Old 即 年老代(Old Generation),New 即 年轻代(Young Generation)。年老代和年轻代的划分对垃圾收集影响比较大。
jdk1.8
永久代(PermGen)的概念被废弃掉了,取而代之的是一个称为Metaspace的存储空间。

GC回收器

Serial收集器

最古老,最稳定
效率高
可能会产生较长的停顿
-XX:+UseSerialGC
新生代、老年代使用串行回收
新生代复制算法
老年代标记-压缩

ParNew收集器

-XX:+UseParNewGC
新生代并行
老年代串行
Serial收集器新生代的并行版本
复制算法
多线程,需要多核支持
-XX:ParallelGCThreads 限制线程数量

Parallel收集器

类似ParNew
新生代复制算法
老年代 标记-压缩
更加关注吞吐量
-XX:+UseParallelGC
使用Parallel收集器+ 老年代串行
-XX:+UseParallelOldGC
使用Parallel收集器+ 并行老年代
-XX:MaxGCPauseMills
最大停顿时间,单位毫秒
GC尽力保证回收时间不超过设定值
-XX:GCTimeRatio
0-100的取值范围
垃圾收集时间占总时间的比
默认99,即最大允许1%时间做GC
这两个参数是矛盾的。因为停顿时间和吞吐量不可能同时调优

CMS收集器

Concurrent Mark Sweep 并发标记清除
标记-清除算法
与标记-压缩相比
并发阶段会降低吞吐量
老年代收集器(新生代使用ParNew)
-XX:+UseConcMarkSweepGC
-XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次整理
整理过程是独占的,会引起停顿时间变长
-XX:+CMSFullGCsBeforeCompaction
设置进行几次Full GC后,进行一次碎片整理
-XX:ParallelCMSThreads
设定CMS的线程数量

JAVA11新增的两个收集器

ZGC:一款号称可以保证每次GC的停顿时间不超过10MS的垃圾回收器,并且和当前的默认垃圾回收起G1相比,吞吐量下降不超过15%。
Epsilon:该垃圾收集器被称为“no-op”收集器,将处理内存分配而不实施任何实际的内存回收机制。 也就是说,这是一款不做垃圾回收的垃圾回收器。这个垃圾回收器看起来并没什么用,主要可以用来进行性能测试、内存压力测试等,Epsilon GC可以作为度量其他垃圾回收器性能的对照组。大神Martijn说,Epsilon GC至少能够帮助理解GC的接口,有助于成就一个更加模块化的JVM。

G1收集器

-XX:+UseG1GC
-XX:G1HeapRegionSize=n 使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb.
-XX:InitiatingHeapOccupancyPercent=n 启动并发GC周期时的堆内存占用百分比. G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比. 值为 0 则表示"一直执行GC循环". 默认值为 45。

GC算法

新生代GC的复制算法

IBM 的专门研究表明,新生代中的对象98%是朝生夕死的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为1 块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 和其中的一块Survivor 。当回收时,将Eden和Survivor 中还存活着的对象一次性地拷贝到另外一块Survivor空间上 ,最后清理掉 Eden 和刚才用过的 Survivor 的空间。Hotspot虚拟机默认Eden和Survivor 的大小比例是8:1。也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存是会被“浪费”的。当然,98 %的对象可回收只是一般场最下的数据.我们没有办法保证每次回收都只有不多干 10 %的对象存话,当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保 ( Handle Promotion)。

老年代GC的标记-整理算法

标记出需要回收的对象,将所有需要回收的对象向一端移动,然后清理掉端边界以外的内存。

为何G1收集器可以实现在基本不牺牲吞吐且的前提下完成低停顿的内存回收

它能够极力地避免全区城的垃圾收集,之前的收集器进行收集的范围都是性个新生代或老年代。而G1将整个 Java 堆(包括新生代、老年代)划分为多个大小固定的独立区域 ( Region ) ,并且跟踪这些区城里面的垃圾堆积程度.在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的区城。区域划分及有优先级的区域回收,保证了G1收集器在有限的时间内可以获得最高的收集效率。

操作数栈与局部变量表的操作

image.png

Linux性能监控命令

uptime

系统时间
运行时间
连接数
1,5,15分钟内的系统平均负载

top

内存与cpu的使用情况

vmstat

可以统计系统的CPU,内存,swap,io等情况

pidstat

监控CPU
监控IO
监控内存
细致观察进程
需要安装
sudo apt-get install sysstat

QUEST

如何处理一个OOM异常

开启GC日志(-XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:./gclogs),判断是否问题与GC有关,如出现频繁Full GC,或新生代、老年代容量经常处于满载状态。
首先通过辅助工具,如JProfiler,visualvm等确认内存中的对象是否是有必要的,也就是分清楚是内存泄漏或者是内存溢出。
通过定位到的元素查找调用栈,定位出代码的位置。
如果元素应该被回收则修改错误代码。
如果元素应该存活,则尝试添加堆内存大小,从代码上优化生命周期或者持有状态时间。

JVM如何判断一个对象死亡

在根搜索算法中不可达的对象需经过两次标记过程:在根搜索后发现没有与GC Roots相连接的引用链,他么他会被第一次标记并进行一次筛选,条件是:是否有必要执行finalize()方法。如果需要执行,将其放入一个特殊的队列。当第二次标记时,队列中如果重新与引用链上的对象,进行了关联,则不进行回收。否则,与已经标记过一次的对象,一起回收。

finalize有什么作用

Java技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。

JVM主要参数

https://www.cnblogs.com/langtianya/p/3898760.html

-Xmx3550m:设置JVM最大堆内存为3550M。
-Xms3550m:设置JVM初始堆内存为3550M。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g:设置年轻代大小为2G。在整个堆内存大小确定的情况下,增大年轻代将会减小年老代,反之亦然。此值关系到JVM垃圾回收,对系统性能影响较大,官方推荐配置为整个堆大小的3/8。
-XX:NewSize=1024m:设置年轻代初始值为1024M。
-XX:MaxNewSize=1024m:设置年轻代最大值为1024M。
-XX:PermSize=256m:设置持久代初始值为256M。
-XX:MaxPermSize=256m:设置持久代最大值为256M。
-XX:NewRatio=4 :  设置年轻代(包括1个Eden和2个Survivor区)与年老代的比值。表示年轻代比年老代为1:4。
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的比值。表示2个Survivor区(JVM堆内存年轻代中默认有2个大小相等的Survivor区)与1个Eden区的比值为2:4,即1个Survivor区占整个年轻代大小的1/6。官方推荐幸存代占新生代的1/10。
-XX:MaxTenuringThreshold=7:表示一个对象如果在Survivor区(救助空间)移动了7次还没有被垃圾回收就进入年老代。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代,对于需要大量常驻内存的应用,这样做可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代存活时间,增加对象在年轻代被垃圾回收的概率,减少Full GC的频率,这样做可以在某种程度上提高服务稳定性。
-Xss128k:设置每个线程的栈大小。JDK5.0以后每个线程栈大小为1M,之前每个线程栈大小为256K。应当根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。需要注意的是:当这个值被设置的较大(例如>2MB)时将会在很大程度上降低系统的性能。

疑问解答

-Xmn,-XX:NewSize/-XX:MaxNewSize,-XX:NewRatio 3组参数都可以影响年轻代的大小,混合使用的情况下,优先级是什么?
如下:
高优先级:-XX:NewSize/-XX:MaxNewSize
中优先级:-Xmn(默认等效 -Xmn=-XX:NewSize=-XX:MaxNewSize=?)
低优先级:-XX:NewRatio
推荐使用-Xmn参数,原因是这个参数简洁,相当于一次设定 NewSize/MaxNewSIze,而且两者相等,适用于生产环境。-Xmn 配合 -Xms/-Xmx,即可将堆内存布局完成。

调优案例

承受海量访问的动态Web应用

服务器配置:8 CPU, 8G MEM, JDK 1.6.X

参数方案:

-server -Xmx3550m -Xms3550m -Xmn1256m -Xss128k -XX:SurvivorRatio=6 -XX:MaxPermSize=256m -XX:ParallelGCThreads=8 -XX:MaxTenuringThreshold=0 -XX:+UseConcMarkSweepGC

调优说明:

-Xmx 与 -Xms 相同以避免JVM反复重新申请内存。-Xmx 的大小约等于系统内存大小的一半,即充分利用系统资源,又给予系统安全运行的空间。
-Xmn1256m 设置年轻代大小为1256MB。此值对系统性能影响较大,Sun官方推荐配置年轻代大小为整个堆的3/8。
-Xss128k 设置较小的线程栈以支持创建更多的线程,支持海量访问,并提升系统性能。
-XX:SurvivorRatio=6 设置年轻代中Eden区与Survivor区的比值。系统默认是8,根据经验设置为6,则2个Survivor区与1个Eden区的比值为2:6,一个Survivor区占整个年轻代的1/8。
-XX:ParallelGCThreads=8 配置并行收集器的线程数,即同时8个线程一起进行垃圾回收。此值一般配置为与CPU数目相等。
-XX:MaxTenuringThreshold=0 设置垃圾最大年龄(在年轻代的存活次数)。如果设置为0的话,则年轻代对象不经过Survivor区直接进入年老代。对于年老代比较多的应用,可以提高效率;如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。根据被海量访问的动态Web应用之特点,其内存要么被缓存起来以减少直接访问DB,要么被快速回收以支持高并发海量请求,因此其内存对象在年轻代存活多次意义不大,可以直接进入年老代,根据实际应用效果,在这里设置此值为0。
-XX:+UseConcMarkSweepGC 设置年老代为并发收集。CMS(ConcMarkSweepGC)收集的目标是尽量减少应用的暂停时间,减少Full GC发生的几率,利用和应用程序线程并发的垃圾回收线程来标记清除年老代内存,适用于应用中存在比较多的长生命周期对象的情况。

内部集成构建服务器案例

高性能数据处理的工具应用
服务器配置:1 CPU, 4G MEM, JDK 1.6.X
参数方案:

-server -XX:PermSize=196m -XX:MaxPermSize=196m -Xmn320m -Xms768m -Xmx1024m

调优说明:

-XX:PermSize=196m -XX:MaxPermSize=196m 根据集成构建的特点,大规模的系统编译可能需要加载大量的Java类到内存中,所以预先分配好大量的持久代内存是高效和必要的。
-Xmn320m 遵循年轻代大小为整个堆的3/8原则。
-Xms768m -Xmx1024m 根据系统大致能够承受的堆内存大小设置即可。

大小相等减少分配开销

Xms=Xmx MinPermSize=MaxPermSize

上一篇下一篇

猜你喜欢

热点阅读