JVM优化入门

JVM构成

JVM构成

生命周期

  • 启动
    通过引导类加载器(Bootstrap ClassLoader)由虚拟机具体实现指定创建一个初始化类完成
  • 执行
    执行Java程序,作为一个进程
  • 退出
    • 程序正常执行结束
    • 程序执行过程中发生了异常或错误
    • 由于操作系统出现错误导致JVM进程结束
    • 某线程调用 System 类的 exit方法 或 Runtime类的 exit方法、halt方法,并且Java安全管理器也允许操作
    • 本地方法接口(JNI Java native interface) 中的卸载JVM的退出API

JVM Stacks(栈)

每个线程运行时需要的内存
由多个栈帧(Frame)组成,对应每次方法调用时占用的内存
每个线程只能有一个活动栈帧,对应正在执行的方法
不涉及垃圾回收

Heap (堆)

一个JVM实例只存在一个
保存所有应用类型的真实信息
整体分为young、old Gen、Perm Gen(Metaspace)
线程共享,需要考虑线程安全问题
存在垃圾回收机制

堆的组成

young(新生代)

类的诞生、成长、消亡的区域
分为Eden(伊甸区)、Survivor0(From区 幸存0区)、Survivor1(To区 幸存1区)
Survivor0与Survivor1采用的是 标记-复制 清除算法,会进行调换
在新生代中,当内存不足时,触发Minor GC,并将存活下来的对象增长1岁,移动至幸存区
当幸存区内存不足时或默认年龄到达15岁(进行15次GC仍然存活的对象) 移动至老年代

old gen(老年代)

当老年代内存不足时,触发FullGC FullGC的触发频率低于Mimor GC
采用 标记-整理 清除算法
当对象过大时,会直接放入老年代
可能会触发 OutOfMemoryError: Java Heap space 异常

  • 堆内存分配不足
  • 创建了大量的对象无法被回收

Perm Gen / Metaspace (永久代 / 元数据)

一个常驻内存的区域,用于存放JDK自身携带的元数据及运行环境必须的类信息
关闭JVM时会释放所占的内存
可能会触发 OutOfMemoryError: PermGen space 异常
或 OutOfMemoryError: Metaspace

  • 永久代Perm内存设置不够
  • 加载了大量的第三方Jar包导致永久区被占满

JDK1.8及之后 无永久区 替换为元空间,从堆中移出至本地内存中

垃圾回收

垃圾判断算法
  • 引用计数法
    引用这个对象时,计数+1,引用失效时,计数-1,当计数为0时可以被回收
    当两个对象互相引用时,两个对象的计数都为1,无法进行回收
  • 引用链算法
    从一个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连,则说明此对象可以被回收,当一个对象不可达GC Root时,这个对象并不会立马被回收,而是处于一个死缓的阶段,若要被真正的回收需要经历两次标记
    如果对象在可达性分析中没有与GCRoot的引用链,那么此时就会被第一次标记并且进行一次筛选,筛选的条件是是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者已被虚拟机调用过,那么就认为是没必要的。
    如果该对象有必要执行finalize()方法,那么这个对象将会放在一个称为F-Queue的对队列中,虚拟机会触发一个Finalize()线程去执行,此线程是低优先级的,并且虚拟机不会承诺一直等待它运行完,这是因为如果finalize()执行缓慢或者发生了死锁,那么就会造成F-Queue队列一直等待,造成了内存回收系统的崩溃。GC对处于F-Queue中的对象进行第二次被标记,这时,该对象将被移除”即将回收”集合,等待回收

GC Roots

  1. 虚拟机栈中引用的对象
  2. 方法区类静态属性引用的对象
  3. 方法区常量池引用的对象
  4. 本地方法栈JNI引用的对象
  5. 所有被synchronized持有的对象
垃圾回收算法
  • 标记清除算法
    速度快,内存碎片化严重
  • 标记整理算法
    标记后将存活对象移向内存另一端清除
    没有内存碎片,速度慢,内存开销大
  • 标记复制算法
    将内存划分为等大小的两块,每次只使用其中一块,当一块内存满后,将存活的对象复制到另一块中
    速度快,不会有内存碎片,占用双倍的空间
垃圾回收器
  • 串行
    单线程的垃圾回收线程,适合堆内存较少
    • serial
      工作于新生代,采用复制算法
    • serialOld
      工作于老年代,采用标记整理算法
  • 吞吐量优先
    垃圾回收总时间占程序运行时间的比例
    多个垃圾回收线程并行,需要多核CPU支持
    • ParallelGC
      工作在新生代,使用复制算法
    • ParallelOldGC
      工作在老年代,使用的是标记整理算法
  • 响应时间优先
    单次STW(Stop The World)时间最短
    多个垃圾回收线程并发,清理阶段用户线程不需要暂停
    需要多核CPU
    当并发收集失败后,触发FullGC
    • ParNewGC
      工作在新生代,采用的是复制算法
    • ConcMarkSweepGC(CMS)
      工作在老年代,采用的是标记清除算法,会产生内存碎片,当内存碎片过多时会引起并发失败,从而导致ConcMarkSweepGC退化为SerialOld,采用标记整理算法处理内存碎片,从而会引起STW
  • G1垃圾回收器
    同时注重吞吐量和低延迟
    超大堆内存,会将堆划分为多个大小相等的Region
    整体上是标记+整理算法,两个区域之间是复制算法
    当回收的速度跟不上产生的速度时,触发FullGC

Method Area(方法区 / 永久代 / 元数据)

线程共享,存储类的数据
存在常量池
会触发垃圾回收

常量池

包含要执行的类名、方法名、参数类型、字面量等信息
当类被加载时,常量信息会放入运行时常量池中,并将里面的符号地址替换为真是的地址

StringTable

String对象的 intern方法 可手动将字符串放入StringTable中

  • 1.8 尝试放入StringTabel中,如果有,就不放,没有就放,并将该字符串返回
  • 1.6 没有就复制一分放入,将常量池中的对象返回

合理使用 intern方法 可优化内存

PC Register(程序计数器)

线程私有,每个线程都有自己的计数器
用于记录下一条JVM指定的执行地址,通过寄存器实现
不会存在内存溢出

Native Method Stacks(本地方法栈)

native 关键字 表示该方法的具体实现有操作系统完成

JAVA程序分析

jps

用来输出JVM中运行的进程状态

jps [options] [hostid]

options

  • -q 不输出类名、Jar名和传入main方法的参数
  • -m 输出传入main方法的参数
  • -l 输出main类或Jar的全限名
  • -v 输出传入JVM的参数

可选,可同时使用多个

hostid

如不指定hostid就默认为当前主机或服务器

jstack

查看某个Java进程内的线程堆栈信息

jstack [option] 进程ID

option

  • -l 会打印出额外的锁信息,在发生死锁时可以用jstack -l pid来观察锁持有情况
  • -m 会输出Java堆栈信息,还会输出C/C++堆栈信息

可选

jmap

查看堆内存使用状况,一般结合jhat使用

jmap [option] 进程ID

option

  • -permstat 打印进程的类加载器和类加载器加载的持久代对象信息,包括类加载器名称、对象是否存活(不可靠)、对象地址、父类加载器、已加载的类大小等
  • -heap 查看进程堆内存使用情况,包括使用的GC算法、堆配置参数和各代中堆内存使用情况
  • -histo[:live] 查看堆内存中的对象数目、大小统计直方图,如果带上live则只统计活对象
参数描述
num序号
#instances实例个数
#bytes大小
class name全类名
jmap -histo:live 9488 | more

class name为对象类型

参数描述
Bbyte
Cchar
Ddouble
Ffloat
Iint
Jlong
Zboolean
[数组,如[I表示int[]
[L+类名其他对象
  • -dump 把进程内存使用情况dump到文件
jmap -dump:format=b,live,file=/tmp/123.dat 进程ID

b: 二进制格式
live:只保留存活对象,会触发垃圾回收 可选
file:生成的文件的名称
生成的文件可以使用MAT、VisualVM等工具查看,也可使用 jhat 查看

jhat

读取dump文件并可以进行分析

jhat -port 9998 /tmp/123.dat

如果Dump文件太大,可能需要加上-J-Xmx512m

jhat -J-Xmx512m -port 9998 /tmp/123.dat

jstat

打印出当前JVM运行的各种状态信息,例如新生代内存使用情况,老年代内存使用情况,以及垃圾回收的时间

jstat -gc 进程ID 时间间隔/ms 打印次数

-- 监控进程ID为5000的进程,每1s打印一次,共打印100次
jstat -gc 5000 1000 100
  • -gc 显示总容量/字节
参数描述
S0Csurvivor0总容量
S1Csurvivor1总容量
S0Usurvivor0目前已使用容量
S1Usurvivor1目前已使用容量
ECEden总容量
EUEden目前已使用容量
OCold Gen总容量
OUold Gen目前已使用容量
YGC目前新生代垃圾回收总次数
YGCT目前新生代垃圾回收总消耗时间
FGC目前 Full gc次数总次数
FGCT目前 Full gc次数总耗时,单位是秒
GCT垃圾回收总耗时
  • -gcutil 显示占用百分比
参数描述
S0survivor0已使用的占当前容量百分比
S1survivor1已使用的占当前容量百分比
EEden已使用的占当前容量百分比
Oold Gen已使用的占当前容量百分比
M元空间(MetaspaceSize)已使用的占当前容量百分比
CCS压缩使用比例
TGC年轻代垃圾回收次数
YGCT年轻代垃圾回收消耗时间
FGC老年代垃圾回收次数
FGCT老年代垃圾回收消耗时间
GCT垃圾回收消耗总时间

jconsole、jvisualvm、jMeter

图形界面工具

JVM调优

目标

  1. GC的时间足够的小
  2. GC的次数足够的少
  3. 发生Full GC的周期足够的长

GC时间小必须要一个更小的堆,要保证GC次数足够少,必须保证一个更大的堆,只能取其平衡
更大的年轻代必然导致更小的老年代,大的年轻代会延长普通GC的周期,但会增加每次GC的时间,小的年老代会导致更频繁的Full GC
更小的年轻代必然导致更大年老代,小的年轻代会导致普通GC很频繁,但每次的GC时间会更短;大的年老代会减少Full GC的频率

入手

  • 系统运行日志
    系统运行日志就是在程序代码中打印出的日志,描述了代码级别的系统运行轨迹(执行的方法、入参、返回值等),一般系统出现问题,系统运行日志是首先要查看的日志
  • 堆栈错误信息
    当系统出现异常后,可以根据堆栈信息初步定位问题所在
    OutOfMemoryError: Java heap space 堆内存溢出
    StackOverflowError: 栈溢出
    OutOfMemoryError: PermGen space 方法区溢出
    OutOfMemoryError: metaspace 元空间溢出
  • GC日志
    程序启动时用 -XX:+PrintGCDetails 和 -Xloggc:/data/jvm/gc.log 可以在程序运行时把gc的详细过程记录下来,或者直接配置“-verbose:gc”参数把gc日志打印到控制台,通过记录的gc日志可以分析每块内存区域gc的频率、时间等,从而发现问题,进行有针对性的优化
  • 线程快照
    根据线程快照可以看到线程在某一时刻的状态,当系统中可能存在请求超时、死循环、死锁等情况是,可以根据线程快照来进一步确定问题
  • 堆转储快照
    当程序发生内存溢出时,把当时的内存快照以文件形式进行转储

垃圾回收

新生代大小(Oracle说 在四分之一到二分之一)
幸存区大小,让对象在晋升为老年代之前被回收
控制阈值让长时间存活的幸存区对象尽快晋升

参数描述
-Xmn设置新生代初始大小
-XX:MaxTenuringThreshold=threshold控制阈值让长时间存活的幸存区对象尽快晋升

垃圾回收器

选择合适的垃圾回收器

  • 串行
参数描述
-XX:+UseSerialGC使用串行垃圾回收器
  • 吞吐量优先
参数描述
-XX:+UseParallelGC打开(1.8默认开启)
-XX:+UseParallelOldGC指定老年代使用Parallel Old回收器 ,成对存在的,开启一个另一个也会开启
-XX:+UseAdaptiveSizePolicy自适应调整新生代内部的大小
-XX:GCTimeRatio=ratio垃圾回收时间占比(当达不到时,会去调整堆的大小)
-XX:MaxGCPauseMillis=200ms默认值200ms 每次执行GC最大的暂停时间
-XX:ParallelGCThreads=n控制垃圾回收的线程数(默认全部)
  • 响应时间优先
参数描述
-XX:+UseConcMarkSweepGC打开CMS垃圾回收器
-XX:+UseParNewGC当打开CMS时,Parallel会自动打开
-XX:ParallelCMSThreads=n并行的垃圾回收线程数量(初始标记和二次标记阶段,默认值为8)
-XX:ConcGCThreads=threads并发的垃圾回收线程数量(建议为并行的垃圾回收线程数量的四分之一,默认值为2)
-XX:CMSInitiatingOccupanyFraction=ratio控制执行CMS的时机,当内存占比到达多少的时候执行CMS垃圾回收
-XX:+UseCMSCompactAtFullCollection指定在CMS回收完老年代后,对内存空间进行压缩处理,以避免碎片化问题
-XX:+CMSScavengeBeforeRemark重新标记会对整个堆内存扫描,此参数会在重新标记前先对新生代进行垃圾回收(提高效率,免得新生代指向老年代,导致无意义扫描)
  • G1垃圾回收器
参数描述
-XX:+UseG1GC打开G1垃圾回收器(JDK9后默认)
-XX:G1HeapRegionSize=size设置区的大小
-XX:MaxGCPauseMillis=time设置暂停目标 默认200ms
-XX:InitiatingHeapOccupancyPercent=percent老年代占用堆内存的阈值 默认45%(JDK9之前 JDK9可动态调整)
-XX:MaxGCPauseMillis=ms每次执行GC最大的暂停时间
-XX:+UseStringDeduplication打开字符串去重(默认开启)
-XX:+ClassUnloadingWithConcurrentMark当类加载器的所有类不再使用时,卸载它加载的所有类(默认开启)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值