JVM专题

2022/03/05

JVM学什么?

(1) 源码到类文件
(2) 类文件到 JVM
(3)JVM 各种折腾 [ 内部结构、执行方式、垃圾回收、本地调用等 ]
对象在内存中的布局(对象结构)

对象大小怎么确定

是否开启压缩

内存多大,超过32G压缩无效

oops

对象头包括什么? 锁信息  gc信息(回收次数) identity hash

 markword  classPointer data padding

对象的创建过程

t多大?不压缩64位,压缩32位

对象的分配过程

对象怎么定位?

什么是JVM优化(对JVM优化的理解)

  1. 根据需求进行JVM规划和预调优

  2. 优化运行JVM运行环境(慢,卡顿)

  3. 解决JVM运行过程中出现的各种问题(OOM)

JVM 的性能优化可以分为代码层面和非代码层面。
在代码层面,大家可以结合字节码指令进行优化,比如一个循环语句,可以将循环不相关的代码提
取到循环体之外,这样在字节码层面就不需要重复执行这些代码了。
在非代码层面,一般情况可以从内存、 gc 以及 cpu占用率等方面进行优化。
VM 调优是一个漫长和复杂的过程,而在很多情况下, JVM 是不需要优化的,因为 JVM 本身
已经做了很多的内部优化操作。

内存溢出分类

栈内存溢出         java.lang.StackOverflowError

堆内存溢出        java.lang.OutOfMemoryError: heap

方法区内存溢出        java.lang.OutOfMemoryError: Metaspace

直接内存溢出

默认堆大小

  • 除非在命令行上指定了初始堆大小和最大堆大小,否则它们将根据计算机上的内存量进行计算。
  • 最大物理内存大小不超过192兆字节(MB)时默认最大堆大小是物理内存的一半,否则占用物理内存的四分之一
  • 在32位JVM上,如果有4 GB或更多的物理内存,则默认的最大堆大小最多可以为1 GB。在64位JVM上,如果有128GB或更多的物理内存,则默认的最大堆大小最大为32 GB。
  • 在JVM初始化期间分配了一个较小的值,称为初始堆大小。此数量至少为8 MB,否则为物理内存的1/64,最大为1 GB。
  • 分配给年轻代的最大空间量是堆总大小的三分之一,即年轻代和老年代默认的比例是1:2

您可以使用-Xms(初始堆大小)和-Xmx(最大堆大小)来指定初始堆大小和最大堆大小。如果你知道你的应用程序有多少堆需要工作做好,你可以设置-Xms和-Xmx相同的值。否则,JVM将使用初始堆大小开始,然后将增大Java堆,直到找到堆使用率和性能之间的平衡为止。
 

如何分析堆内存

怎么回答

1:设定了参数HeapDump,OOM的时候会自动产生堆转储文件

2:很多服务器备份(高可用),隔离这台服务器,使用其他的服务器

在线分析(2种):

jmap

jmap - histo 4655 | head -20,查找有多少对象产生

arthas   (线上分析)

下载arthas jar包运行,绑定到想检测的进程   

  • jvm观察jvm信息

  • thread定位线程问题

  • dashboard 观察系统情况(查看线程占用,内存占用)

  • jad反编译动态代理生成类的问题定位

  • 第三方的类(观察代码)

  • 版本问题(确定自己最新提交的版本是不是被使用)

  • redefine 热替换目前有些限制条件:只能改方法实现(方法已经运行完成),不能改方法名, 不能改属性m() -> mm()

  • sc - search class

  • watch - watch method

jconsole       

远程监控(JMX协议),需要在程序启动时指定参数,一般只在测试时候用,在线用严重影响主程序效率

jvisualVm  同jconsole       

离线分析(生成dump堆转储文件)

生成dump堆转储文件的过程占用系统资源,造成严重卡顿,不建议生成堆转储分析

获得堆转储文件的几种方式

1.堆内存溢出时自动获取

java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError com.mg.jvm.gc.T15_FullGC_Problem01

2.使用jmap命令

jmap -dump:format=b,file=xxx pid

3.使用arthas的heapdump命令

分析堆转储文件的几种工具

1.MAT

2.jhat                jhat -J-mx512M xxx.dump(就指定让其使用512M内存)

3.jvisualVm      java自带,打开后导入堆转储文件

如何选择垃圾回收器

关注的两个指标

吞吐量和停顿时间
停顿时间 -> 垃圾收集器 进行 垃圾回收终端应用执行响应的时间
吞吐量 -> 运行用户代码时间 /( 运行用户代码时间 + 垃圾收集时间 )

选择收集器时的考虑

  1. 优先调整堆的大小让服务器自己来选择
  2. 如果内存小于100M,使用串行收集器
  3. 如果是单核,并且没有停顿时间要求,使用串行或JVM自己选
  4. 如果允许停顿时间超过1秒,选择并行或JVM自己选
  5. 如果响应时间最重要,并且不能超过1秒,使用并发收集器

何时使用并发收集器g1

(java1.8及以上,内存6G以上,优先考虑G1)

1 50% 以上的堆被存活对象占用
2 )对象分配和晋升的速度变化非常大
3 )垃圾回收时间比较长

G1详细介绍

G1内存分配策略


将内存分成一个一个的region,且不要求各部分是连续的。
每个Region的大小在JVM启动时就确定,JVM通常生成2000个左右的heap区, 根据堆内存的总大小,区的size范围为1-32Mb,一般4M.

region类型


三种常见: Eden, Survivor, 或 old generation(老年代)区  HumongousHumongous
巨无霸区:保存比标准region区大50%及以上的对象,存储在一组连续的区中.转移会影响GC效率,标记阶段发现巨型对象不再存活时,会被直接回收。
未使用区:未被使用的region
特别说明:某个region的类型不是固定的,比如一次ygc过后,原来的Eden的分区就会变成空闲的可用分区,随后也可能被用作分配巨型对象

基本概念

cardTable(属于堆的概念,不单属于g1)

基于卡表(Card Table)的设计,通常将堆空间划分为一系列2次幂大小的卡页(Card Page)。

卡表(Card Table),用于标记卡页的状态,每个卡表项对应一个卡页。

HotSpot JVM的卡页(Card Page)大小为512字节,卡表(Card Table)被实现为一个简单的字节数组,即卡表的每个标记项为1个字节。

当对一个对象引用进行写操作时(对象引用改变),写屏障逻辑将会标记对象所在的卡页为dirty。

cardTable带来的2个问题

1.无条件写屏障带来的性能开销

每次对引用的更新,无论是否更新了老年代对新生代对象的引用,都会进行一次写屏障操作。显然,这会增加一些额外的开销。但是,与YGC时扫描整个老年代相比较,这个开销就低得多了。

不过,在高并发环境下,写屏障又带来了虚共享(false sharing)问题。

2.高并发下虚共享带来的性能开销

在高并发情况下,频繁的写屏障很容易发生虚共享(false sharing),从而带来性能开销。

假设CPU缓存行大小为64字节,由于一个卡表项占1个字节,这意味着,64个卡表项将共享同一个缓存行。

HotSpot每个卡页为512字节,那么一个缓存行将对应64个卡页一共64*512=32KB。

如果不同线程对对象引用的更新操作,恰好位于同一个32KB区域内,这将导致同时更新卡表的同一个缓存行,从而造成缓存行的写回、无效化或者同步操作,间接影响程序性能。

一个简单的解决方案,就是不采用无条件的写屏障,而是先检查卡表标记,只有当该卡表项未被标记过才将其标记为dirty。

这就是JDK 7中引入的解决方法,引入了一个新的JVM参数-XX:+UseCondCardMark,在执行写屏障之前,先简单的做一下判断。如果卡页已被标识过,则不再进行标识。

待收集集合,CSets

由G1MixedGCLiveThresholdPercent参数控制的,old代分区中的存活对象比,达到阀值时,说明该region可以被回收的对象比较多,这个old分区会被放入CSet,等待被GC。
Collection Sets,有垃圾需要被回收的region的集合。CSet中可能存放着各个分代的Region。CSet中的存活对象会在gc中被移动(复制)。GC后CSet中的region会成为可用分区。
策略:由G1MixedGCLiveThresholdPercent参数控制的,old代分区中的存活对象比,达到阀值时,这个old分区会被放入CSet,后面会被执行回收整理。

已记忆集合,RSets


RememberedSets,存储着其他分区中的对象对本分区对象的引用,每个分区有且只有一个RSet。用于提高GC效率。
YGC时,GC root主要是两类:栈空间和老年代分区到新生代分区的引用关系。所以记录老年代分区对新生代分区的引用
Mixed GC时,由于仅回收部分老年代分区,老年代分区之间的引用关系也将被使用。所以记录老年代分区之间的引用
因此,我们仅需要记录两种引用关系:老年代分区引用新生代分区,老年代分区之间的引用。
因为每次GC都会扫描所有young区对象,所以RSet只有在扫描old引用young,old引用old时会被使用。
 

G1的GC类型

1)Ygc:仅处理年轻代region
2)MixedGc:包含所有年轻代以及部分老年代Region。
3)FullGc:全堆扫描,每个Region

MixGc过程:

1. 初始标记( Initial Marking ) 标记以下 GC Roots 能够关联的对象,并且修改 TAMS 的值,需要暂
停用户线程
2. 并发标记( Concurrent Marking ) 从 GC Roots 进行可达性分析,找出存活的对象,与用户线程并发执行
3. 最终标记( Final Marking ) 修正在并发标记阶段因为用户程序的并发执行导致变动的数据,需暂停用户线程
4. 筛选回收( Live Data Counting and Evacuation ) 对各个 Region 的回收价值和成本进行排序,根据用户所期望的GC 停顿时间制定回收计划

5. 对存活对象进行转移(复制算法),转移到其他可用分区,所以当前的分区就变成了新的可用分区。复制转移主要是为了解决分区内的碎片问题。

G1的FullGc


G1在对象复制/转移失败或者没法分配足够内存(比如巨型对象没有足够的连续分区分配)时,会触发FullGC。
开始版本FullGC使用的是stop the world的单线程的Serial Old模式。
JDK10以后,Full GC已经是并行运行,在很多场景下,其表现还略优于 Parallel GC 的并行 Full GC 实现。
但是仍然要避免fgc。

G1调优的一些考虑

1.不要设置年轻代的大小,默认5%到60%,是G1动态调整暂停stw时间的依据

2 设置 XX:MaxGCPauseMillis=<N>
其值不应该使用平均响应时间,应该考虑使用目标时间的90%或者更小作为响应时间指标. 即90%的用户(客户端/?)请求响应时间不会超过预设的目标值;

3 转移失败
survivors 或 promoted objects 进行GC时如果JVM的heap区不足就会发生提升失败(promotion failure). 堆内存不能继续
扩充,因为已经达到最大值了. 当使用 -XX:+PrintGCDetails 时将会在GC日志中显示 to-space overflow (to-空间溢出)。该操作很昂贵,原因如下:
1)GC仍继续所以空间必须被释放. 
2)拷贝失败的对象必须被放到正确的位置(tenured in place). 
3)CSet指向区域中的任何 RSets 更新都必须重新生成(regenerated). 

避免转移失败的方法:
1)增加保留内存大小, 其默认值是 10;G1保留内存大小,非必须不会使用保留内存;即增大-XX:G1ReservePercent=n
2)更早启动标记周期(marking cycle).即InitiatingHeapOccupancyPercent设置的小一点??
3)增加标记线程(marking threads)的数量. 合理设置-XX:ConcGCThreads=n

4 新生代优化-避免短生命对象进入老年代
预估每次Minor GC后存活下来对象的大小,合理的设置Survivor区,同时考虑高峰期间时,动态年龄判断条件的影响,不要让这种短生命周期对象侥幸逃脱进入老年代

G1相关的参数


-XX:MaxGCPauseMillis=200 - 设置最大GC停顿时间指标,JVM会尽力实现,但不保证. 默认值为200毫秒.
-XX:InitiatingHeapOccupancyPercent=45 - 如果老年代占据了堆内存的45%的时候,此时会触发一次mixGc。值为0则表示“一直执行GC循环)'. 默认值为45。
-XX:G1MixedGCLiveThresholdPercent:默认值是85%,确定要回收的Region的时候,必须是存活对象低于85%的Region才可以回收。
-XX:G1ReservePercent=n 设置堆内存保留为假天花板的总量,以降低提升失败的可能性. 默认值是 10.
-XX:ConcGCThreads=n 并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不同而不同.
-XX:+UseG1GC - 让 JVM使用G1垃圾收集器, jdk9被设为默认垃圾收集器;所以如果你的版本比较新则不再需要使用该参数
-XX:MetaspaceSize=256M 元空间,默认20M,确实有点小。
-XX:MaxMetaspaceSize=512M 最大元空间

下面参数不建议修改
-XX:G1NewSizePercent=5    设置年轻代占整个堆的最小百分比,默认值是堆的5%。需要开启-XX:UnlockExperimentalVMOptions
-XX:G1MaxNewSizePercent=60    设置年轻代占整个堆的最大百分比,默认值是堆的60%。
-XX:NewRatio=n 新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2.
-XX:SurvivorRatio=n eden/survivor 空间大小的比例(Ratio). 默认值为 8.
-XX:MaxTenuringThreshold=n 年轻代提升到年老代的最大临界值. 默认值为 15.
-XX:G1HeapRegionSize=n region大小  默认值将根据 heap size 算出最优解;1M-32M
-XX:G1MixedGCCountTarget mixed回收执行次数,默认回收次数8。
-XX:G1HeapWastePercent,默认值是5%,就是说空出来的区域大于整个堆的5%,即使未达到回收次数,也会立即停止混合回收了。
如:默认回收次数是8次,但是可能到了4次,发现空闲Region大于整个堆的5%,就不会再进行后续回收了。
 

JVM优化常见问题及工具使用

1.cpu占用过高的解决方法

1. 使用nohup java 命令运行后台运行java程序

2. 使用top命令查看各进程对本地内存和cpu的占用情况

3.  找到占用cpu高的进程的pid

4.  使用ps H -eo pid ,tid,%cpu |grep pid 找到这个进程下哪个线程占用cpu的比例较高

         -eo(输出哪些感兴趣的内容),这里找到的线程id是十进制的,转换成十六进制(7f99) 

或者使用top -Hp 命令也能查看进程下id的占用情况(图二)​​​​​​

 

5. 使用jstack  进程id 查看进程下线程的详细情况

6.  根据十六进制的线程id找到对应的线程(看nid)

 

         找到问题了,原来是这个类的第八行是一个while true的死循环

2.程序迟迟得不到响应结果,也不报错

(可能发生了线程死锁)

1. 使用nohup java运行java程序(同上)

2. 使用top命令找到有问题的进程id(同上)

3.使用jstack查看进程里线程的详细信息,在信息的最下方有死锁的信息

found one java-level deadlock    waiting to lock XXXX

堆内存诊断(OOM)

 jamp -heap 进程id  (抓取堆内存快照,堆内存各个区域的使用情况)

 jmap - histo 4655 | head -20(查找有多少对象产生,只看前20行数据,有排序)

通过查看这个类一共产生了多少个对象,占用内存多少来判断应该是哪个类出现了问题


3、垃圾收集发生的时间是什么时候?

    Full GC = Major GC + Minor GC +Metaspace Gc;
    (1)Eden区或者S区的空间不够用的时候 ---->MinorGC
    (2)老年代的空间不够用的时候  ----->Major GC  出发MajorGC往往会伴随着Full GC    
    (3)方法区不够用了也会出发GC
    (4)System.gc()方法调用的时候,但是这只是向虚拟机发出一个 指令。但是什么时候发生回收,还不能确定。只能让虚拟机自己决定。


4、如果Full GC频繁怎么办?或者如何减少Full GC的次数?


    适当的将Yong区增大。 设置YOng和Old的占比。尽可能让对象在Yong区进行回收    


5、如果GC的次数频繁,会怎么办?

    首先拿到gc的日志。然后通过工具进行分析日志。如果是堆内存空间不够用,则要适当的增加堆内存。也许是选择的垃圾回收器不太合适。 如果用的G1垃圾回收器,查看设置的停顿时间是否太严格了。或者堆内存使用比例是不是了。

6、如果cpu飙升怎么办?

    使用top命令查看时哪个进程占用cpu比较大
    (1)因为并发量太大了。导致一些占用cpu的运算一直处于运算中
            解决方案:搭建集群。增加MQ延缓代码的处理
    (2)查看线程是否存在死循环


7、如果发生了OOM怎么办?

    通过dump文件查看oom。分析dump文件  工具MAT
 
内存泄漏和内存溢出有区别嘛?
    内存泄漏是指:哪些对象没法进行回收,持续的占用内存空间。
    内存溢出:OMM是指内存没法装下对象
 
方法去中的回收主要是什么内容?
    没有用的类的信息、常量、静态变量
    
再问:类的信息什么时候被回收?
        (1)堆中不再有该对象
        (2)加载该类的classCloader已经被回收。因为classLoader是可以作为GC Root的
        (3)java.lang.Class对象也不再有任何地方引用了


8、不可达的对象一定会被回收嘛?

    finalize()方法可以自救。
 

9、生产环境下如何设定日志参数

-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause

10、DCL与volitale问题

为什么必须要加volitile?参考对象创建过程的指令重排

DCL单例模式 - 阿玛尼迪迪 - 博客园

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔◎

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值