jvm_调优and享学gc

1. jvm 监控命令参数

常用
jps 查看java进程
jstat 查看jvm gc 统计信息
jinfo 查看 修改jvm 参数
jmap 生成dump文件,查看堆信息
jstack 查看线程快照

2.oom案例 1 堆溢出

java.lang.OutOfMemoryError: Java heap space

内存溢出情况

1. 误用线程池 fixed

new FixedThreadPool(2); 核心线程数,最大线程数都为 nthread 因为是new LinkedBlockQueue() (最大值是Integer.MAX_VALUE)无尽队列,导致oom

2. 误用代缓存的线程池

使用的SynchronousQueue(), 同步队列,导致 coreThread 失效,最大线程 是Integer.MAX_VALUE 也会导致 oom

3. 动态生成的类过多

自定义的类加载器 加载动态生成的类,导致元空间不足。

导致元空间 内存不足
-XX:MaxMetaspaceSize=24m

2.1 GC overhead limit exceeded

java.lang.OutOfMemoryError: GC overhead limit exceeded

超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常, overhead limit exceeded

  1. 检查项目中是否有大量的死循环或有使用大内存的代码,优化代码。
  2. 添加参数 -XX:-UseGCOverheadLimit 禁用这个检查,其实这个参数解决不了内存问题,只是把错误的信息延后,最终出现 java.lang.OutOfMemoryError: Java heap space。
  3. dump内存,检查是否存在内存泄漏,如果没有,加大内存。

2.2 元空间溢出

2.3 线程溢出

3.调优

3.1 调整堆大小,提高服务吞吐量

增大堆空间大小。

3.2 jit优化 会 1.6 后默认开启 逃逸分析 标量替换

变量使用是否超过了作用范围

问题:堆是分配对象的唯一选择吗
大部分情况对象分配在堆中。
特殊情况,经过jit 逃逸分析之后,一个对象并没有逃逸出方法的化,那么就可以被优化成栈上分配
但是在 栈上分配 没有真正意义上把对象放在栈里面,而是把这个聚合量(对象)进行分解,分解成具体的标量。


void alloc(){
//逃逸分析
Ponit p=new Point(1,2);
sout(p.x,p.y);
}
class Point{
int x;
int y;
}
// 经过分析后,发生标量替换。,此时方法的point 已经不算对象了。
void alloc(){
int x=1;
int y=2;
sout(x,y);
}

半解释半编译

java 是半解释,半编译的语言。
在一开始执行时,解释器不用热机, 直接逐行解释,提高响应。后期对热点代码进行编译,缓存,提升整体性能。

3.2.1 栈上分配

通过逃逸分析,变量没有发生逃逸,对这个对象进行栈上分配。

3.2.2 锁消除

通过分析,一个被发现只能从一个线程被访问,那么对于这个对象可以不考虑同步,

3.2.3 标量替换

一个对象时聚合量,如果没有发生逃逸分析,进行拆解成为 标量(对象里面的字段属性)

3.3 合理配置堆内存

内存太大,full gc 时间长
内存太小 频繁触发minor gc
a
Java整个堆大小设置,Xmx 和 Xms设置为老年代存活对象的3-4倍,即FullGC之后的老年代内存占用的3-4倍。
方法区(永久代 PermSize和MaxPermSize 或 元空间 MetaspaceSize 和 MaxMetaspaceSize)设置为老年代存活对象的1.2-1.5倍。
年轻代Xmn的设置为老年代存活对象的1-1.5倍。
老年代的内存大小设置为老年代存活对象的2-3倍。

3.3 如何计算老年代存活对象大小

查看 gc日志
JVM参数中添加GC日志,GC日志中会记录每次FullGC之后各代的内存大小,观察老年代GC之后的空间大小。可观察一段时间内(比如2天)的FullGC之后的内存情况,根据多次的FullGC之后的老年代的空间大小数据来预估FullGC之后老年代的存活对象大小(可根据多次FullGC之后的内存大小取平均值)。

3.3.4 gc频率

比如从数据库获取一条数据占用128个字节,需要获取1000条数据,那么一次读取到内存的大小就是(128 B/1024 Kb/1024M)* 1000 = 0.122M ,那么我们程序可能需要并发读取,比如每秒读取100次,那么内存占用就是0.122100 = 12.2M ,如果堆内存设置1个G,那么年轻代大小大约就是333M,那么333M80% / 12.2M =21.84s ,也就是说我们的程序几乎每分钟进行两到三次youngGC。这样可以让我们对系统有一个大致的估算。

0.122M * 100 = 12.2M /秒 —Eden区

1024M * 1/3 * 80% = 273M

273 / 12.2M = 22.38s —> YGC 每分钟2-3次YGC

3.4 新生代 老年代比例 自适应策略 useAdaptiveSizePolicy

1.8 默认 useParrelGC 该gc回收器默认开启useAdaptivepolicy,那么会根据gc 的情况自动计算eden from to 区的大小。 默认 6:1:1
注意事项:
1、在 JDK 1.8 中,如果使用 CMS,无论 UseAdaptiveSizePolicy 如何设置,都会将 UseAdaptiveSizePolicy 设置为 false;不过不同版本的JDK存在差异;
2、UseAdaptiveSizePolicy不要和SurvivorRatio参数显示设置搭配使用,一起使用会导致参数失效;
3、由于UseAdaptiveSizePolicy会动态调整 Eden、Survivor 的大小,有些情况存在Survivor 被自动调为很小,比如十几MB甚至几MB的可能,这个时候YGC回收掉 Eden区后,还存活的对象进入Survivor 装不下,就会直接晋升到老年代,导致老年代占用空间逐渐增加,从而触发FULL GC,如果一次FULL GC的耗时很长(比如到达几百毫秒),那么在要求高响应的系统就是不可取的。

附:对于面向外部的大流量、低延迟系统,不建议启用此参数,建议关闭该参数。

3.5 cpu占用高 如何排查

  1. top 查看cpu过高的 进程id 比如 pid 1456
    top ->得到cpu高的进程id 1456
  2. top -Hp pid可以查看某个进程的线程信息 进程id
    -H 显示线程信息,-p指定pid
    top -Hp 1456
    得到 线程id
    a
  3. printf %x 将线程id十进制转十六进制
    printf %x 1465 0x5b9
  4. jstack -pid | grep -A 30 过滤出线程id锁关联的栈信息 需要16进制
    jtstack -1456 | grep -A 30 0x5b9

3.6 G1并发 执行的线程数对性能的影响

3
-XX: ConcGcThreads 设置并发标记的线程数。n=ParallelGCThreads 的1/4左右。
3
配置完线程数之后,我们的·请求的平均响应时间和GC时间都有一个明显的减少了,仅从效果上来看,我们这次的优化是有一定效果的。大家在工作中对于线上项目进行优化的时候,可以考虑到这方面的优化。

4 享学 gc 调优

4.0 性能gc 监控 jstat

jstat - gc 5000 20
5000ms 统计一次 20次

3
jstat-gc 8404 5000 20 | awk ‘{print $13,$14,$15,$16,$17}’

awk 打印 13列…

4.1 查询初始化参数

java -XX:PrintFlagsFinal -version | grep HeapSize
3

jmap -heap jmap -histo 1980 | grep 20

jmap –histo 1196 | head -20
(这样只会显示排名前 20 的数据)
3

4.2 调优参数

在高并发场景下,对象是朝生夕死,增加新生代内存,同时老年代够用就行。

-Xms1500m -Xmx1500m -Xmn1000m -XX:SurvivorRatio=8
增加新生代内存。

java -jar -Xms1500m -Xmx1500m -Xmn1000m -XX:SurvivorRatio=8 jvm-1.0-SNAPSHOT.jar

3

分析结果

500m->1500m
eden 180 -》500 没有明显增加。 每次检索gc对象的空间变大,所以gc 时间增加
YGC 消耗时间 1。扫描垃圾对象(耗时) 2 复制存活对象
方案二
很大的新生代,较小的老年代。
当前场景生命周期的短

推荐策略

  1. 新生代大小选择
  • 响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择).在此种情况下,新生代收集发生的频率也是最小的.同时,减少到达老年代的对象.
  • 吞吐量优先的应用 尽可能的设置大,可能到达 Gbit 的程度.因为对响应时间没有要求,垃圾收集可以并行进行,一般适合 8CPU 以上的应用.
  • 避免设置过小.当新生代设置过小时会导致:
    • 1.MinorGC 次数更加频繁
    • 2.可能导致 MinorGC 对象直接进入老年代,如果此时老年代满了,会触发 FullGC.
  1. 老年代大小选择

响应时间优先的应用:老年代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数.如果堆设置小了,可以会造成内存碎 片,高回收频率以及应用暂停而使用传统的标记清除方式;
如果堆大了,则需要较长的收集时间.最优化的方案,一般需要参考以下数据获得:并发垃圾收集信息、持久代并发收集次数、传统 GC 信息、花在新生代和老年代回收上的时间比例。

吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的新生代和一个较小的老年代.原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而老年代尽存放长期存活对象。

gc 调优策略

1. 降低 Minor GC 频率 增大新生代空间

由于新生代空间较小,Eden 区很快被填满,就会导致频繁 Minor GC,因此我们可以通过增大新生代空间来降低 Minor GC 的频率。 单次 Minor GC 时间是由两部分组成:T1(扫描新生代)和 T2(复制存活对象)。

情况 1:假设一个对象在 Eden 区的存活时间为 500ms,Minor GC 的时间间隔是 300ms,因为这个对象存活时间>间隔时间,那么正常情况下,MinorGC 的时间为 :T1+T2。

情况 2:当我们增大新生代空间,Minor GC 的时间间隔可能会扩大到 600ms,此时一个存活 500ms 的对象就会在 Eden 区中被回收掉,此时就不存在复制存活对象了,所以再发生 Minor GC 的时间为:即 T12(空间大了)+T20

可见,扩容后,Minor GC 时增加了 T1,但省去了 T2 的时间。

在 JVM 中,复制对象的成本要远高于扫描成本。如果在堆内存中存在较多的长期存活的对象,此时增加年轻代空间,反而会增加 Minor GC 的时间。如果堆中的短期对象很多,那么扩容新生代,单次 Minor GC 时间不会显著增加。因此,单次 Minor GC 时间更多取决于 GC 后存活对象的数量,而非 Eden区的大小。

这个就解释了之前的内存调整方案中,方案一为什么性能还差些,但是到了方案二话,性能就有明显的上升。

4.3 JVM 性能调优之预估调优与问题排查

动态年龄判断

s区 相同年龄所有对象的大小超过 s区的一半,直接晋级到old 区。
2
秒杀 大促场景频繁 full gc
full gc 时间长, 新生代 老年代 方法区

useAdaptiveSizePolicy

启用自适应策略
会自适应新生代 eden 和 s0的比例
3

4.4 mat 分析使用

使用Eclipse Memory Analyzer Tool(MAT)分析线上故障(一)

incoming outgoing reference

- with incoming references 对象被谁引用了

C 的引入 c1对象 c2对象 c的class 对象
也就是c 对象被谁引用了
在这里插入图片描述

- with outgoing references 对象 引用了谁

3

-3
3

shallow retained heap

  • 浅堆对象自生占用的内存
  • 深堆 统计的结果 自身对象被gc,释放的内存大小。
    3
    改变
    3
    3

支配树试图 上到下找

堆中最大的对象
只能显示 支配的对象。 所有执行b的对象都经过a 那么称为 a 支配b
3
3
3

线程视图

3
3

path to gc roots

3
c被b 引用 ,b被a 引用
3

根据大对象 定位代码

JAVA内存泄露使用MAT(Memory Analyzer Tool)快速定位代码

JVM 内存分析工具 MAT 的深度讲解与实践——进阶篇

thread local 引用图

3
thread 里面有个threadlocal map ,map 的key 是 弱引用 threadlocal

4.5 内存泄漏

内存泄漏指:不使用的对象,但是存在引用,使得jvm误以为还在使用。无法回收掉。

内存溢出 : 申请内存,没有足够的内存可以使用了。

1. 静态集合类

3

2. 单例对象

内存中 单例对象一直存在

3. 内部类持有外部类

外部类A 内部类B
内部类B 被C对象 引用,但是外部类A对象 不再使用,由于C引用B 导致A也回收不了。

4. 各种连接、IO 没有释放资源

没有关闭流

5. 变量不合理的作用域

一个变量的作用范围大于其使用范围,很有可能会造成内存泄漏。
3

6. hash 值的改变

存入hashmap 中,放入之后,又修改字段,那么hash值改变,存在内存泄漏。
3

7. 缓存泄漏

8. 监听器 和回调

在 api 中注册回调,却没有显示的取消,那么就会聚集导致内存泄漏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值