51.整理JVM

JVM调优的原则?

  1. 不要为了调优而调优,首先确定的应该是项目的架构与代码已经没有优化的空间了,再考虑进行JVM的调优工作,不能指望通过JVM调优来使得性能有一个质的飞越
  2. 从三个属性中(吞吐量,延迟,内存)中选择两个进行JVM调优,称之为调优3选2,在处理吞吐量和延迟问题时,GC能使用的内存越大,应用运行也就越流畅,这也叫GC内存最大化原则

什么情况下需要进行调优?

  1. Full GC数量频繁,GC停顿时间超长(超过1s)
  2. 应用出现OOM异常或者吞吐量下降,响应性能不高,
  3. 应用CPU占用过高,内存占用过高

吞吐量和低延迟?

吞吐量:代码时间/(代码时间+垃圾回收时间),吞吐量越高,算法越好

低延迟:STW越短,响应时间越好,算法越好,目的在于缩短或者完全消除因垃圾收集器引起的停顿

一个GC算法只可能针对于以上两个目标之一或者尝试找到一个折中方案,吞吐量优先的垃圾回收器有Parallel的年轻代和老年代版本,响应时间优先的垃圾回收器有CMS和G1

常用性能调优工具和常用参数

常用工具:jvisualvm,jconsole,MAT(提示可能内存泄露的点)

常用参数解释:

  • -Xms:JVM启动时申请的初始Heap大小
  • -Xmx:JVM运行时可申请的最大Heap值,为了避免每次GC后JVM重新分配内存,一般设置为同一个值
  • -Xmn:设置Heap中新生代的大小,通过Xms-Xmn可以得到老年代的大小
  • -Xss:设置每个线程可使用的栈大小
  • -XX:TraceClassLoading/TraceClassUnLoading可以在日志中追踪类加载和卸载的情况

CPU占用过高的排查流程?

  • top命令查看出cpu最高的进程pid(这里也可以用jps -l命令)
  • [ps -mp 进程号 -o THREAD,tid,time]定位具体的线程(这里也可以使用top -Hp pid)
  • 使用[printf "%x\n" 线程id]将需要的线程转换为16进制格式
  • 使用[jstack 进程ID|grep 16进制线程id -A60]打印出前60行信息
ps常用参数:
-A    显示所有进程(等价于-e)(utility)
-a    显示一个终端的所有进程,除了会话引线
-N    忽略选择。
-d    显示所有进程,但省略所有的会话引线(utility)
-x    显示没有控制终端的进程,同时显示各个命令的具体路径。dx不可合用。(utility)
-p    pid 进程使用cpu的时间
-u    uid or username 选择有效的用户id或者是用户名
-g    gid or groupname 显示组的所有进程。
-f    全部列出,通常和其他选项联用。如:ps -fa or ps -fx and so on.
-l    长格式(有F,wchan,C 等字段)
-j    作业格式
-o    用户自定义格式
-m    显示所有的线程
-H    显示进程的层次(和其它的命令合用,如:ps -Ha)(utility)
-a    显示同一终端下的所有程序
-A    列出所有的进程
-w    显示加宽可以显示较多的资讯
-au   显示较详细的资讯
-aux  显示所有包含其他使用者的进程
-e    显示所有进程,环境变量
-f    全格式
-h    不显示标题
-l    长格式
-w    宽输出

内存占用过高排查流程?

  • 查找进程Id: top查看内存占用过高的进程pid
  • [jmap -heap pid]:查看JVM堆内存的分配情况
  • [jmap -histo:live pid|head -n 100]:查看占用内存比较多的存活对象

什么情况会抛出OOM?OOM之前会发生什么

满足以下两个条件中的任意一个会产生OOM

  • JVM的98%时间都用于内存回收
  • 每次回收的内存小于2%

OOM之前的现象:

  • 每次垃圾回收的时间越来越长,且full gc的次数越来越多
  • 老年代的内存越来越大,每次full gc后,只有少量的内存被释放掉

线上死锁排查?

  • [jps -l]查找到可能有问题的进程id
  • 执行[jstack -F 进程id]命令
  • 如果说可以远程连接到JVM,可以使用jconsole或者jvisualvm,以图形化界面检测是否死锁

jstack命令:

-l:打印关于锁的信息

-F:当-l没有响应的时候,强制打印栈信息

-m:答应jaba和native c/c++框架所有栈信息

JVM的主要组成部分及其作用?

JVM包含两个子系统和两个组件,子系统为:类装载和执行引擎,组件为运行时数据区与本地接口

  • 类装载:根据给定的类全限定类名,装载class文件到运行时数据区的方法区
  • 执行引擎:将字节码翻译成底层系统指令,交由cpu去执行
  • 本地接口:与本地方法库交互,是其他编程语言交互的接口
  • 运行时数据区域:jvm的内存

运行时数据区:java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区

  • 程序计数器:当前线程所执行的字节码的行号指示器,利用它可以完成分支,跳转,线程恢复,循环等工作
  • 虚拟机栈:存储局部变量表,操作数栈,动态链接,方法出口等信息
  • 本地方法栈:虚拟机栈服务java方法,本地方法栈服务native方法
  • 堆:被线程共享的一块区域,几乎所有的对象实例都在这里分配
  • 方法区:用于存储已经被虚拟机加载的类信息,常量,静态常量,即时编译后的代码等数据

堆和栈的区别?

堆的物理地址分配对象是不连续的,因此性能会慢一些,栈使用的是数据结构中的栈,先进后出,物理地址分配是连续的所以性能快

堆所分配的内存是在运行期间确认的,大小不固定,一般远远大于栈,栈分配的内存在编译时就要确定

存放对象的实例和数组,栈存放的是局部变量,操作数栈,返回结果

对于整个程序是可见的,栈对于线程可见,生命周期与线程相同

Java是否会存在内存泄露问题?

有,当长生命周期的对象持有短生命周期对象的引用就会发生,经过短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致无法回收

垃圾收集器?

  • Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;
  • ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
  • Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
  • Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;
  • Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
  • CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。
  • G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代和老年代),而前六种收集器回收的范围仅限于新生代或老年代。

标记清除:从GC roots中找到存活对象,然后清除其他没有被标记的对象

标记整理:在标记清除的基础上,将存活对象压缩到内存的一端,避免了内存碎片问题

复制算法:把内存划分为两个区域,每次只使用其中的一个区域,垃圾收集时,把存活对象复制到另一个区域,清空当前块的内存

分代收集:根据对象的存活周期划分内存,一般包括年轻代,老年代和永久代

CMS垃圾收集器?

牺牲了吞吐量来获得最短回收停顿时间的垃圾回收器,对于要求服务响应速度的应用,这种垃圾回收器非常合适,使用-XX:+UseConcMarkSweepGC来使用

CMS的步骤分为:

  • 初始标记:会标记GC Roots直接关联的对象,这个过程会发生stw,因为只标记一层所以速度很快,该过程是单线程
  • 并发标记:该过程不会停止用户线程,做的是从GC Roots向下追溯标记可达对象的工作
  • 重新标记:该过程中保证的是可达对象一定被标记了,需要stw
  • 并发清理:不会stw,并发的回收不可达的对象,这个过程用户线程会不断的产生垃圾,变为浮动垃圾,留给下一次GC处理

优点:

  • 将stw的时间降到最低,给电商网站的用户带来最好的体验

缺点:

  • 内存碎片问题
  • 浮动垃圾问题
  • 有时候会占用大量的cpu时间,当cms运行过程中预留的空间不够用,会使用serial old来进行老年代垃圾回收导致停顿时间变长

G1垃圾收集器?

相较于CMS,G1最突出的改进有以下两点:

  • 基于标记整理算法实现,不会产生内存碎片
  • 可以精确控制停顿时间,在不牺牲吞吐量的前提之下,实现低停顿垃圾回收

流程如下:

前三个流程基本和CMS一致

筛选回收:排序各个Region的回收价值和成本,根据用户期望的GC停顿时间来指定回收计划,最后按照极坏回收一些价值高的region中的垃圾对象,需要stw

简述分代垃圾回收器是怎么工作的?

分代有两个分区:老年代(2/3)和新生代(1/3)

新生代使用的复制算法,Eden,To Survivor,From Survivor.默认占比为8:1:1,执行流程如下:

  • Eden+From->To,并且使对象的年龄+1,大于15就被丢进老年代,大对象也会直接进入老年代
  • 清空Eden与From
  • From和To交换身份

内存分配策略:

  • 对象优先在Eden区分配,当Eden没有足够空间,发起一次Minor GC
  • 大对象直接进入老年代,参数-XX:PretenureSizeThreshold
  • 长期存活的对象进入老年代
  • 空间担保机制:在Minor GC发生之前,JVM要预估老年代是否能容纳Minor GC后新生代晋升到老年代的存活对象,以确定是否需要提前触发GC回收老年代空间
  • 动态年龄判断:新生代对象年龄没有达到阈值,但是新生代存活对象的年龄达到某个值且这些对象大小的总和大于survivor空间的一半或者说survivor空间不足以容纳这些对象,对象直接进入老年代

类装载的过程?

加载:根据查找路径找到相应class文件然后导入,在方法区生成类信息,在堆中添加class对象

验证:检查加载的class文件的正确性

准备:给类中的静态变量,或者是final修饰的变量分配内存空间

解析:将符号引用转为直接引用

初始化:对静态变量和静态代码块执行初始化

对象创建过程?

  • java在new一个对象的时候,会先查看所属类有没有被加载到内存中,如果没有会先进行类的加载过程
  • 申请对象内存:指针碰撞或者空闲列表
  • 成员变量赋默认值
  • 设置对象头(哈希码,GC分代年龄,偏向锁id,指向类的元数据的指针)
  • 调用构造方法init

方法区信息的演变

方法区是一个JVM的规范,永久代与元空间都是实现方式,JDK1.8之前,方法区的实现是永久代,之后开始使用元空间来替代,以前永久代的静态变量和常量池移动到堆内存中,其他的内容移动到了元空间,元空间直接在本地内存分配

元空间不会发送垃圾回收,永久代会发生垃圾回收

为什么要使用元空间代替永久代呢?

永久代所使用的物理内存与堆内存是连续的,由于类和方法信息难以确定大小,所以指定永久代的大小困难,容易出现永久代溢出,太大则容易导致老年代移溢出,并且永久代会为GC带来不必要的复杂度,并且回收效率偏低,且字符串存在于永久代中,容易出现性能问题和内存溢出

方法区变成元空间后,物理内存不再与堆内存连续,而是存在于本地内存中,理论上机器内存多大,元空间就有多大

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值