JVM 虚拟机 精华一页纸

    xiaoxiao2021-04-14  80

    1、内存管理 - 栈 or 堆


    无论是java还是C,内存分配,本质上就是 栈和堆两个类型。简单来说,代码逻辑处理在栈上,数据在堆上。

    I、JVM内存模型 堆:新生代(Eden,survivor),年老代(Gen) -- 分配对象、数组等 非堆(栈):虚拟机栈,本地方法栈 -- 栈帧 分配局部变量、操作需要的空间比如方法链接 方法区-(永久代<Perm – 好像最新的没有这个了>) -- 分配代码、全局变量、静态变量

    Object o = new Object() 首先代码在 方法区中。 执行时,Object o 会存放在 java栈 的本地变量表中。 new Object 会在 java堆中。

    II、JVM 内存分配过程 a、创建的对象都在堆的新生代(Eden)上分配空间;垃圾收集器回收时,把Eden上存活的对象和一个Survivor 的对象拷贝到另一个Survivor 一般是MinorGC b、大对象直接进入老年代 -- 所以对于大对象比较多的,年老代分配的内存要多一点,年轻代分配的少一点 --可以配置对象大小的门限 c、长期存活的对象进入老年代 --在多次MinorGC时仍然存活的,进入老年代 --可以配置门限MinorGC次数,默认15次

    原因很简单,因为 年轻代一般是复制算法,多次复制代价很大 老年代是 Full GC

    d、年龄判断,如果 Survivor 的同龄对象占所有对象的一半,大于这个年龄的就直接进入老年代 MinorGC时,检查晋升老年代的对象是否大于 老年代剩余空间,如果大于则进行 Full GC

    e、空间分配担保 在发生Minor GC 时,虚拟机检测之前 晋升到老年代的空间平均大小 是否大于老年代剩余空间,如果大于则直接进行 Full GC;如果小于,则查看HandlePromotionFailure 设置是否允许担保失败。如果允许,就进行Minor GC,并把存活的对象移到老年代,如果不允许,则进行Full GC III、内存溢出 a、OutOfMemoryError 首先,堆内存不够分配、肯定会出现 内存溢出的问题 永久代,加载的类太多,也会有 栈内存申请不到也有 本机native直接内存溢出

    内存溢出会出现在各个内存区域

    b、StackOverflowError 递归调用(没有关闭条件) 线程太多

    c、内存溢出定位过程 使用内存映像分析工具(Eclipse Memory Analyzer),对dump出的文件进行分析 确认内存中的对象是否必要的。 即分清楚出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow) 如果是内存泄漏通过工具查看 泄漏对象到 GC root的引用链 如果不是泄漏,则 检查 虚拟机的堆参数(-Xmx -Xms),从代码上检查是否存在某些对象生命周期过长,持有状态时间过长的情况。 2、内存(垃圾)回收 


    在描述 java 垃圾回收之前,想象一下 C ++ 内存如何内存管理 和 垃圾回收。 通常new 一片内存区域,存储一些数据,假设就是 new int[] 频繁的操作删除后,留下了很多内存碎片 然后一般都是 memcpy 把数据转移到内存的一端,一般是都移动到开始端。 事实上,所有的内存回收后的管理,基本都是 拷贝移动已有数据。比如 Redis的 ziplist 就是这么设计的。

    垃圾回收两个问题: I、如何判断 对象不再被使用? a、首先想到的是记录每一个使用者 - 引用计数器,事实上早期的java垃圾回收就是如此。 引用计数有个很大的困扰,几个对象间的互相循环引用,怎么办?引用计数一直存在。

    b、标记引用链 + 从根开始 - 根搜索法 通过引用链可以识别对象引用关系;从根开始,就能识别脱离主链的 循环引用的问题。这样利用有向图,从根开始寻找整个引用链,把不再链上的对象都进行标记。

    什么样的对象适合做根对象 GCroot 静态变量 - 程序加载首先进内存的对象,全局根 栈帧的变量 - 程序当前执行到的对象,临时根 (因为执行完毕,栈帧的数据就会回收,执行过程中,作为当前流程开始的对象同样也是根)

    II、如何操作回收不用的对象? 前面已经描述过C++ 内存回收方法,java也非常类似

    标记清除法 - 前面发现的对象,标记完后,进行删除, 类似 delete,这样会产生很多碎片 复制算法 - 把存活的对象,统一拷贝到 另一块完整内存 标记整理法 - 把存活对象移动到一端,剩下的内存统一清理,类似 memcpy,后delete

    适用场景 复制算法,适用存活对象较少的场景,比如 新生代;标记整理算法和清除算法,适用于存活对象较多的场景。

    III、垃圾回收器 除了标记清除法外,其他两种需要移动对象,都会造成程序的卡顿(移动过程中,对象不能被改变),这个问题数据库备份过程中也有同样的问题。

    a、复制算法收集器 -- 基本都用在新生代 Serial收集器 - 单线程条件下运行 (一般client和默认的) ParNew收集器 - 多线程条件下运行 (一般server模式适用) Parallel Scanvenge收集器

    ParNew VS Parallel Scanvenge ParNew 关注卡顿时延; Parallel Scanvenge 关注系统吞吐量

    b、标记整理算法收集器 -- 基本用在老年代 Serial Old收集器 - 单线程 Parallel Old收集器 - 多线程 关注吞吐量

    c、标记清除算法收集器 -- 用在老年代 CMS(Concurrent Mark Sweep)收集器 关注时延(因为耗时最多的标记和清除不需要影响用户业务) G1收集器(Garbage First)收集器 时延 or 吞吐量 可以这么理解,时延的目标是单次回收要尽快,减少单次时延,而整体卡顿累计时长可能更多,导致吞吐量下降;吞吐量则关注整体卡顿情况,累计时长要端,吞吐量要高,单次卡顿时延可能会较长。 在这两种策略下,关注时延的 可能是多次频繁小范围的GC、关注吞吐量的可能是 一次就彻底的大范围的GC

    组合: 单线程版本 - Serial + Serial Old 用在 Client模式下 (一般很少使用) 吞吐量优先组合 - Parallel Scanvenge + Parallel Old(Serial Old 老版本) 用在 Server模式下 时延优先组合 - ParNew + CMS(Serial Old 备用)用在 Server 模式下

    3、JVM 优化


    I、JVM crash JVM 宕机的问题分析,首先 JVM 是一个C++进程,同样可以采用 C++ coredump 的分析思路来分析 JVM (网上描述的,好像用jmap生成的dump不能用GDB调试)

    a、定位的文件素材 crash 日志 生成 -XX:ErrorFile=/path/xxx.log; 执行命令-XX:OnError="string" -XX:+ShowMessageBoxOnError -- 打开实时GDB调试 程序自带日志 coredump文件 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/xx.log linx: kill -3 | windows : Ctrl + Break 用JDK 自带命令jmap ,或者工具 JConsole和VisualVM 如果不能生成 则检查linux的 ulimit 配置 如果都找不到 到 /var/log/message中 找 cat messages|grep java java线程相关信息

    b、分析文件 crash 日志 日志头(概要信息): -- 得到在哪个大的部分出现的问题。粗略信息 SIGSEGV - 执行 JNI 时出现的问题,一般是 编译加载类、执行JVM 外部代码出现的问题 EXCEPTION_ACCESS_VIOLATION - 执行 JVM 自身的代码 EXCEPTION_STACK_OVERFLOW - 堆栈出错

    执行代码类型 C J VM 等

    线程信息: -- 得到crash时线程的工作情况 线程类型 - Java Thread | VMThread | CompilerThread | GCTaskThread | WatcherThread | ConcurrentMarkSweepThread 线程状态 - _thread_in_native | _thread_uninitialized | _thread_new | _thread_in_vm | _thread_in_Java | _thread_blocked

    安全点 safepoint 和锁 Mutex 安全点是标记线程运行到一个区域,JVM将其挂起,以便执行GC 等JVM操作。没有运行到安全点的线程,GC是不能回收其内存的;如果线程一直不到安全点可能会出现假死状态

    内存heap情况 各个内存区域使用情况

    其他信息 JVM参数,系统环境

    分析重点:概要信息里,判断Crash时正在执行什么信息;当前线程状态;还有内存使用情况

    经典问题:内存溢出,一般永久代因为分配较少,出现问题的情况比较多;堆栈溢出,主要是 jni 本地栈溢出的可能较多

    threaddump / heapdump 文件 当前线程运行状态、线程堆栈信息 类、对象使用情况

    分析重点:基本信息里面,生成堆栈时的 异常线程和异常原因; wait/lock 等信息

    经典问题:内存溢出

    分析顺序 crash日志 > thread dump > heap dump

    II、JVM OOM 问题 分析的文件和 crash 一样的。分析过程也是类似; 另外,可以通过JDK工具和命令实时监控分析。

    不过,OOM 不一定会出现crash的情况。一般都是分析 heap dump文件。 a、分析内存 堆、非堆的使用情况; 看看是否是内存分配参数设置不合理。 b、分析 出现OOM的线程,正在操作的情况。找到导致泄露的对象的 GC Root 链 c、分析 类实例数最多、最大的 类的使用情况。(大对象、多对象) -- 找到上面这两种情况下的 类和对象 是否需要/需要这么多,分清是 泄露还是对象生命周期不合理 d、分析 GC 的情况 -- 看看Full GC的情况

    III、性能优化 程序的性能优化,无非就是 CPU、内存、IO 三种资源的占用情况分析。 性能优化的关键在于,分段排查,逐步逼近的方式,确定问题代码所在 先测量 再逐步逼近 找到问题代码 分析原因,并给解决方法

    第一步:Linux 命令查看 top -H -p<pid> 找到 java进程和线程 中最耗资源的线程 第二步:在各种日志中找到对应的线程进行分析

    a、卡顿时间较长 or 处理很慢 - 一般是CPU在高负荷运转,说明线程在高负荷执行;GC 时间较长等 查看GC 时长,GC 频次,各种GC占比 使用的是什么垃圾处理器 (比如 GCViewer 工具),GC的具体情况 -XX:+PrintGCTimeStamps -Xloggc:/tmp/gc.log -XX:+PrintGCDetails 查看各个线程处理情况,lock wait/notify 等情况;长时间运行的线程 查看JNI线程使用情况

    连续生成两次的 线程堆栈(core) 文件,对比,查看 对象变化,线程执行变化;如果执行方法没有变化的线程,一般就是有问题的线程

    IV、JDK自带工具 a、命令行工具 内存信息 jmap工具 生成dump jmap dump:format=b,file=xxx pid 内存统计 jmap -heap 内存跟踪 jstat 线程堆栈跟踪 jstack 配置信息 jinfo 分析工具 jhat

    利用命令行就是 jmap+jstack,然后详细信息通过jhat

    b、可视化工具 JConsole JVisualVM - 其中 BTTrace 可以嵌入到每个方法追踪每个方法的执行(通过类似 asm/CGLib 字节码加载替换) 对比两个 dump 的差异,找出对象的

    V、性能分析工具 MAT IBM Heap - 可以分析出OOM中的 内存占用最大的地方,可以溯源GC root,找到对象树

    VI、配置建议 a、内存分配,各个代的分配,32位JVM下 堆分配 1G,年轻代 一半,年老代一半。持久代64M -Xmx512m -Xms512m -- 最大最小保持一致,避免频繁扩容和收缩 -Xmn256m -- 年轻代一般在 一半左右,官方推荐 3/8 -XX:PermSize=64m -- 持久代一般固定在 64M左右 -XX:MaxPermSize=128m

    b、垃圾收集器选择 -XX:+UseParNewGC --设置年轻代使用 ParNew收集器 并行 -XX:+UseConcMarkSweepGC --设置年老代为CMS收集器 并发

    c、其他设置项:内存压缩,对象晋级等等。 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=5 -- Full GC 5次后,进行内存碎片压缩 -XX:+HeapDumpOnOutOfMemoryError -- 内存溢出时生成dump文件

    d、启用运行期编译 Jit即时编译器 C1 – Client (简单优化 C2 – Server(激进优化 运行在Server模式下会更高效) 根据监控,针对热点代码进行优化(比如方法内联) Jit的缺点是 编译有耗时,另外,对于一些类装载卸载比较多的场景也不适合。 Server模式启动时要慢一点,运行时效率很高 也可以指定,运行模式 解释模式-Xint,编译模式-XComp

    比较理想的情况是,编译成Class,运行时可以动态Server模式;兼顾了效率和可移植性。

    转载请注明原文地址: https://ju.6miu.com/read-670477.html

    最新回复(0)