尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JVM相关知识

JVM相关知识 文章目录1. JVM内存结构1概述2) JVM栈3) 堆4) 方法区5) 本地方法栈6) 程序计数器7) 堆内存区域划分8) 元空间2. JVM的相关参数1 常用参数2 设置垃圾回收器类型3 设置预先分配内存4 提前启用CMS来收集垃圾3. JVM的垃圾回收机制1垃圾回收区域2判断对象是否存活的算法3垃圾回收算法4GC触发时机5 垃圾收集器类型4. JVM中的类加载机制1 加载机制类型双亲委派机制2 类加载器3 加载clas文件原理5. 内存溢出1定义2 分类6. 内存泄露1定义2触发内存泄漏的场景3处理方案4 怎样避免7. 项目启动参数设置8. 内存问题排查9. 调优1 调优目标2 调优工具3 调优方法1. JVM内存结构1概述1. JVM的内存结构分为5部分分别是 1JVM栈 2 堆 3方法区 4本地方法栈 5程序计数器 2. 线程共享的区域有堆、方法区。 3. 线程独立的区域有JVM栈本地方法栈程序计数器。2) JVM栈1. 每个线程独有的主要是执行java程序存储方法调用的栈帧。 2. 每当启动一个新线程的时候java虚拟机都会为它分配一个java栈生命周期与线程相同。该区域主要维护栈帧。 每调用一个方法JVM都会创建一个栈帧来保护当前方法的状态并将其压入栈中当方法执行完之后就将该栈帧弹出。 3. 每个线程包含一个栈区栈中只保存 基本数据类型的对象 和 自定义对象的引用(不是对象) 每个栈中的数据都是私有的不能互相访问。 4. 栈的结构分为3部分基本类型变量区执行环境上下文操作指令区。 5. 栈帧的结构 1局部变量表存放方法参数和局部变量。 2操作数栈执行字节码指令的工作区。 3动态链接方法指向方法区的方法引用。 4返回地址方法执行后的返回位置。3) 堆1. 所有线程共享的。 2. 主要用于存放对象实例 绝大多数创建的对象都会存到这里。 3. 是垃圾回收器最主要针对的区域。 4. java堆可以处于物理上不连续的内存空间中只要逻辑上是连续的即可就像我们的磁盘空间一样。 在实现时既可以实现成固定大小的也可以是可扩展的当前主流的虚拟机都是按照可扩展来实现的(通过-Xms和-Xmx控制)。 5. 如果在堆中没有足够的内存来完成实例的分配并且堆也无法再扩展时就会造成内存溢出抛出 OutOfMemoryError异常。4) 方法区1. 该区域是所有线程共享的。 2. 主要存放类的元数据(类名方法字段)常量池静态变量构造函数等。 3. 垃圾回收器针对这块区域的回收主要是针对常量池和类的卸载。 4. JDK8之前方法区的实现是 永久代JDK8及之后方法区的实现是 元空间 。 5. 运行时常量池用于存储编译期生成的各种字面量和符号引用以及在运行期解析后得到的直接引用。 包含 1编译期可知的字面量 a. 文本字符串 b. 被声明为final的常量值 c. 基本数据类型的值。 2符号引用 a. 类和接口的全限定名 b. 字段和名称的描述符 c. 方法的名称和描述符5) 本地方法栈1. 每个线程独有的。 2. 主要用于JVM 调用本地的native方法 由JVM自行管理。6) 程序计数器1. 每个线程独有。 2. 用于保存当前线程执行的内存地址。由于JVM程序是多线程执行的(线程轮流切换)所以为了保证线程切换回来以后还能恢复到原来的状态就需要一个独立的计数器记录之前中断的地方。 3. 这个区域是唯一 一个不抛出 OutOfMemoryError 的运行时数据区。 4. 分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。7) 堆内存区域划分1. 堆内存的结构 1新生代(Eden2个Surviver) 2老年代 3永久代(java8中虚拟机移除了永久代使用本地内存来存储类的元数据信息称为元空间)。 2. Eden 1该区域是最主要的刚创建的对象的内存分配区域。 绝大多数的对象都会被创建到这里除了部分大对象通过内存担保机制创建到old区域。因为默认大对象都是能存活较长时间的。 2该区域内的对象大部分都是 短时间内会死亡的。 3垃圾回收器针对该部分主要采用复制算法 进行回收。 3. Survivor有2 个 1也是属于新生代的区域主要是将在Eden 中未被清理的对象存放到该区域中。 2第一次GC以后Eden 中仍然存活的对象会移动到Surviver1中。 3再次GC后仍然存活会从servivor1 移动到 setvivor0。 4该区域分为2块区域采用的是复制算法每次只使用1块。Eden 与Survivor 区域的大小比例为 81 是根据大量的业务运行总结出来的规律。 4. 老年代(old) 1该区域属于老年代在survivor中存活了多次都没有被清理的对象就会被复制到该区域。 2该区域主要采用的是标记清除算法。 5. 永久代 jdk8 及以后移除了永久代改为使用元空间来存储类的元数据信息。 移除永久代的原因为 避免 OOM内存溢出永久代大小受限在动态生成大量类如 Spring、Hibernate、JSP、CGLib 代理的应用中极易溢出。 减少 Full GC 频率永久代内的类元数据回收条件非常苛刻常导致 Full GC引入元空间后通过更灵活的阈值触发回收减轻 GC 压力。 充分利用本地内存本地内存由操作系统管理可以动态增长只要系统内存足够类元数据不会轻易导致 OOM。8) 元空间1存储位置不在堆中而是在本地内存。 2存储内容类的元数据包括 1. 类的结构信息如方法、字段、接口、注解等。 2. 方法的字节码。 3. 运行时常量池Constant Pool。 4. 类加载器的元数据。 3大小限制动态扩展可以通过-XX:MaxMetaspaceSize 设置。 4垃圾回收由元空间自动管理当类加载器被回收时其关联的类元数据会被释放。 5内存碎片管理元空间使用 元空间虚拟机Metaspace Virtual Machine 来优化内存分配。 6作用解决了永久代的内存限制问题提升了 JVM 对动态类加载的支持能力。2. JVM的相关参数1 常用参数-Xms500m 堆的最小空间大小(堆的初始大小) -Xmx2g 堆的最大空间大小 -Xmn300m 新生代的空间大小一般设置为整个堆的1/4--1/3 -Xss 设置每个线程的堆栈大小默认每条线程是1M -XXNewSize 设置新生代的最小空间大小 -XXMaxNewSize 设置新生代的最大空间大小 -XXPermSize 设置永久代的最小空间大小 -XXMaxPermSize 设置永久代的最大空间大小 -XXMetaspaceSize 设置元空间初始大小 -XXMaxMetaspaceSize 设置元空间最大空间大小 -XXSurvivorRatio8 Eden区与Survivor区的大小比值设置为8。表示Eden区与一个survivor区的比值是81因为sorviver区有2个所以每个surviver区占整个年轻代的1/10 。 -XXUseG1GC 使用G1(Garbage First) 垃圾回收器 -XX:DisableExplicitGC 禁止在启动期间显式调用System.gc() -XX:HeapDumpOnOutOfMemoryError OOM时导出堆到文件 -XX:HeapDumpPathd:/a.dump 设置导出OOM的路径 -XX:PrintGCDetails 打印GC详细信息 -XX:PrintGCTimeStamps 打印CG发生的时间戳 -XX:PrintHeapAtGC 每一次GC前和GC后都打印堆信息 -XX:TraceClassLoading 监控类的加载 -XX:MaxTenuringThreshold14 提升年老代的最大临界值(tenuring threshold)。默认值为 15[每次GC增加1岁到15岁如果还要存活放入Old区] -XX:ParallelGCThreads8 设置垃圾收集器在并行阶段使用的线程数[一般设置为本机CPU线程数相等即本机同时可以处理的个数设置过大也没有用] -XX:ConcGCThreads8 并发垃圾收集器使用的线程数量 -XX:PrintClassHistogram 按下CtrlBreak后打印类的信息 -XX:MaxGCPauseMillis200 目标最大停顿时间2 设置垃圾回收器类型-XX:UseSerialGC -XX:UseParallelGC -XX:UseParallelOldGC -XX:UseConcMarkSweepGC XX:UseG1GC3 设置预先分配内存-XX:AlwaysPreTouch JAVA进程启动的时候,虽然我们可以为JVM指定合适的内存大小,但是这些内存操作系统并没有真正的分配给JVM,而是等JVM访问这些内存的时候,才真正分配,这样会造成以下问题。 1、GC的时候,新生代的对象要晋升到老年代的时候,需要内存,这个时候操作系统才真正分配内存,这样就会加大young gc的停顿时间; 2、可能存在内存碎片的问题。 可以在JVM启动的时候进行配置这样JVM就会先访问所有分配给它的内存,让操作系统把内存真正的分配给JVM.后续JVM就可以顺畅的访问内存了。4 提前启用CMS来收集垃圾1. 垃圾收集线程会跟应用的线程一起并行的工作,万一垃圾收集线程在工作的时候,老年代内存不足怎么办?因此最好还是提前启动CMS来收集垃圾(CMS GC)。 2. 设置CMSInitiatingOccupancyFraction75 那么当老年代堆空间的使用率达到75%的时候就开始执行垃圾回收,CMSInitiatingOccupancyFraction默认值是92%,这个就太大了。 3. CMSInitiatingOccupancyFraction参数必须跟下面两个参数一起使用才能生效的。 -XX:UseConcMarkSweepGC -XX:UseCMSInitiatingOccupancyOnly3. JVM的垃圾回收机制1垃圾回收区域1.JVM的内存结构包括5大区域堆JVM栈方法区本地方法栈程序计数器。 2. JVM栈本地方法栈程序计数器是线程独有的。 线程结束的时候或者是方法结束的时候内存会跟着回收。 3. 垃圾回收机制主要针对的区域是 堆 和 方法区。 4. JVM中存在着大量生命短暂的对象也有一部分生命周期比较长的对象针对不同的对象需要采用不同的垃圾回收机制。2判断对象是否存活的算法1. 引用计数算法 原理给对象增加一个引用计数器每当有一个地方引用时计数器就1引用失效时计数器就-1当计数器值为0的时候就是已经死去。 优点实现简单判断效率高。 缺点无法解决对象的循环引用问题。 2. 可达性分析算法 原理通过一系列称为“GC Roots”的对象作为起始点从这些节点开始向下搜索搜索走过的路径称为“引用链”当一个对象到 GC Roots 没有任何的引用链相连时(从 GC Roots 到这个对象不可达)时证明此对象不可用。 在java语言中可作为GC Roots的对象包括 1虚拟机栈中引用的对象(栈帧中的本地变量表)。 2) 方法区中类静态属性引用的对象。 3方法区中常量引用的对象。 4本地方法栈中JNI(Native方法) 引用的对象。 注意点 1针对被判定为不可达的对象如果重写了finalize() 方法会给予它一次复活的机会。 2第一次标记如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链那它将会被第一次标记 3第二次标记第一次标记后接着会进行一次筛选如果对象重写了finalize()方法并且之前没有执行过那么对象会执行finalize()方法。在finalize()方法中没有重新与引用链建立关联关系的将被进行第二次标记。 4标记一次且不执行finalize()方法的对象和第二次标记成功的对象都真的会被回收如果对象在finalize()方法中重新与引用链建立了关联关系那么将会逃离本次回收继续存活。 5因为finalize() 的执行时机和顺序都不确定并且会显著影响垃圾回收性能所以java9以后推荐使用 Cleaner 和 PhantomReference(虚引用)。3垃圾回收算法1. 分代收集算法 根据对象存活周期的不同将JVM内存划分为几块。一般是新生代、老年代和 永久代。 针对不同代的对象使用不同的回收算法。 新生代每次都有大量对象死亡只有少量存活所以使用复制算法。 老年代对象存活率高没有额外空间对他进行分配担保。一般使用标记--清除算法或者 标记--整理算法。 永久代用于存放静态文件比如方法区方法区中主要回收的内容有废弃的常量无用的类的等。 2. 复制算法 原理将内存分为大小相等的2块一次只使用其中的一块当这块区域需要进行垃圾回收时会将此区域中还存活的对象复制到另一块上面然后把使用过的内存区域一次清理掉。 使用 1 因为新生代中98%的对象都是朝生夕死的所以并不需要按照11的比例来区分内存空间而是将新生代内存分为一块较大的Eden伊甸园 和 2块较小的Survivor(幸存者) 空间每次使用Eden和其中的一块Survivor(2块Servivor一个叫From区一个叫to区)。 2 当Eden区满的时候会触发第一次Minor gc将Eden区还存活的对象复制到Survivor From区。 3 当Eden区再触发Minor gc时会扫描Eden区和Survivor From区对2个区域进行垃圾回收经过这次回收后还存活的对象会一次性都复制到Survivor To 区域并将Eden区和Servivor From区域清空。 4当后续Eden区域又发生Minor gc时会对Eden区和To区进行垃圾回收将剩余存活的对象一次性复制到From区将Eden和To区清空。 5 部分对象会在From区域和To区域中来回复制如此交换15次(由JVM参数MaxTenuringThreshold决定这个参数默认是15)之后如果还存活就会存入老年代。 6如果Survivor To 不足以存放Eden和Survivor From区域的存活对象就将存活对象直接存放到老年代。如果老年代也满了就会触发一次Full gc也就是新生代老年代都进行回收。 7HotSpot默认Eden与Survivor的大小比例是8 : 1也就是说Eden:Survivor From : Survivor To 8:1:1。所以每次新生代可用内存空间为整个新生代容量的90%,而剩下的10%用来存放回收后存活的对象。 3. 标记--清除算法 原理先标记出所有需要被回收的对象在标记完成后统一回收所有被标记的对象。 弊端 1 效率问题 标记和清除的效率都不高。 2 空间问题标记清除后会产生大量不连续的内存碎片空间碎片太多会导致以后在程序运行中需要分配较大对象时无法找到足够大的连续内存而不得不触发另一次垃圾回收。 4. 标记--整理算法 原理先标记出所有需要被回收的对象在标记完成后不直接清理可回收对象而是将存活对象全部向一端移动接着清理掉边界以外的内存。4GC触发时机1. Scavenge GCMinor GC/ Young GC 1 一般情况下当新对象生成并且在Eden申请空间失败时就会触发Scavenge GC对Eden区域进行GC清除非存活对象并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。 2这种方式的GC是对年轻代的Eden区进行不会影响到年老代。 3因为大部分对象都是从Eden区开始的同时Eden区不会分配的很大所以Eden区的GC会频繁进行。 4一般使用速度快效率高的算法 (复制算法)使Eden区能尽快释放出来。 2. Full GC 1) 对整个堆进行整理。 2比Scavenge GC要慢因此应该尽可能减少Full GC的次数。 3触发Full GC的情况有 a. 年老代被写满。 b. 永久代被写满。 c. System.gc 被显示调用。 d. 上一次gc后堆的各域分配策略动态变化。 e. CMS GC时出现promotion failed和concurrent mode failure。5 垃圾收集器类型1. Serial垃圾收集器 1单线程复制算法新生代垃圾收集器。执行GC的时候只有一个线程。 2进行垃圾收集的时候需要暂停其他所有的工作线程直到垃圾收集结束。 3简单高效没有线程交互的开销是java虚拟机运行在Client模式下默认的新生代垃圾收集器。 2. Parallel 垃圾收集器 1是Serial多线程的版本新生代垃圾收集器也是使用复制算法 除了使用多线程外其他与Serial一致。 2进行垃圾收集的时候需要暂停其他所有的工作线程直到垃圾收集结束。 3默认开启和CPU数目相同的线程数可以通过 -XX:ParallelGCThreadsn 这个JVM选项来控制线程数量。 4是很多 java虚拟机运行在 Server 模式下新生代的默认垃圾收集器。 3. Parallel Scavenge垃圾收集器 1) 多线程复制算法新生代垃圾收集器。 2使用自适应调节策略保证高吞吐量、高效率。 3在年轻代垃圾收集和年老代垃圾回收时都使用多线程收集。 4. Serial old 垃圾收集器 1Serial 垃圾收集器的年老代版本单线程标记----整理算法年老代垃圾收集器。 5. Parallel old 垃圾收集器 1Parallel Scavenge垃圾收集器的年老代版本多线程标记----整理算法年老代垃圾收集器。 6.CMS收集器 (JDK14已经移除) 1多线程标记---清除算法年老代垃圾收集器。 2 通过多线程并发进行垃圾回收尽量减少垃圾收集造成的停顿, 最主要目标是获取最短的垃圾回收停顿时间也被称为短暂停顿并发收集器适用于不能忍受长时间停顿要求快速响应的应用。 3) 工作过程分4个阶段 a. 初始标记只是标记一下 GC Roots 能直接关联的对象速度很快仍然需要暂停所有的工作线程。 b. 并发标记进行 GC Roots 跟踪的过程和用户线程一起工作不需要暂停工作线程。 c. 重新标记修正在并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录仍然需要暂停所有的工作线程。 d. 并发清除清除 GC Roots 不可达对象和用户线程一起工作不需要暂停工作线程。由于耗时最长的并发标记和并发清除过程中垃圾收集线程可以和用户线程一起并发工作 所以总体上来看CMS 收集器的内存回收和用户线程是一起并发地执行。 7. G1垃圾收集器--标记整理 1是在Java 7后才可以使用的特性它的长远目标是代替CMS收集器。 2G1收集器是一个并行的、并发的、增量式压缩短暂停顿的垃圾收集器。 3基于标记--整理算法不产生内存碎片。 4不区分新生代和年老代空间。它把堆内存划分为大小固定的几个独立区域。并且跟踪这些区域的垃圾收集进度同时在后台维护一个优先级列表每次根据所允许的收集时间 优先回收垃圾最多的区域。区域划分和优先级区域回收机制确保 G1 收集器可以在有限时间获得最高的垃圾收集效率。 8. ZGC 1jdk11 引入jdk15正式生产就绪的一款低延迟垃圾收集器核心目标是将GC停顿时间控制在10ms以内且停顿时间不随堆内存大小增长支持从8M到16TB的堆内存。 2) 使用2大核心技术染色指针 和 读屏障。 染色指针将GC状态信息直接存储在64位指针中而非传统GC的对象头里。 优势 a. GC状态与引用地址绑定无需修改对象头避免并发竞态.。 b. 无需遍历堆即可获取对象状态。 c. 支持指针的自愈能力。 读屏障JVM在从堆中读取对象引用时插入的一段轻量级代码用于检查染色指针的状态若指针颜色显示对象已被移动读屏障会自动修正引用为新地址再返回给应用程序自愈 若对象尚未处理读屏障会协助完成标记或重定位。 优势 a. 支持并发对象转移GC移动对象时应用线程无需暂停。 b. 无需维护记忆集RSet相比G1大幅降低内存开销。 c. 自愈机制确保只有第一次访问旧地址会慢一次后续直接命中新地址。图示对比维度CMSG1ZGC堆结构分代年轻/老年代连续Region逻辑上分代动态Region大小可调全局停顿初始标记、重新标记较短初始标记、最终标记可控仅初始标记极短内存碎片有标记-清除无整体标记-整理无并发整理吞吐量较高高高略低于G15%–15%最大停顿不稳定可能Full GC可控默认200ms1ms适用堆大小几百MB ~ 4GB4GB ~ 64GB几百MB ~ 16TB核心技术增量更新SATBRSet染色指针读屏障JDK版本JDK 14已移除JDK 7JDK 1115稳定注 1. Region G1 和 ZGC 这类现代垃圾收集器中将堆内存划分为多个大小相等或不等的内存块每个内存块就是一个 Region。可以把 Region 理解为 内存分块管理单元。4. JVM中的类加载机制1 加载机制类型双亲委派机制1. 使用的是 双亲委派机制。 2. 工作过程 1 如果一个类加载器收到了类加载的请求那么它首先不会尝试自己去加载这个类而是把这个请求委派给父类加载器去完成。 2父类加载器也会先去委派给自己的父类加载器每一个层次的类加载器都是如此。 3所有的类加载请求最后都会传送到顶层的启动类加载器中只有父类加载器反馈自己无法完成这个加载请求时子类加载器才会尝试自己去加载。 3. 优点 使得java类随着它的类加载器一起具备了一种带有优先级的层次关系。保证了基础类的统一性和java程序的稳定运行。2 类加载器1. 启动类加载器 Bootsrap ClassLoader 是最顶层的类加载器是由C编写而成, 已经内嵌到JVM中了。 在JVM启动时会初始化该ClassLoader它主要用来读取Java的核心类库JRE/lib/rt.jar中所有的class文件。 2. 扩展类加载器 Extension ClassLoader 负责加载\lib\extends目中的jar包。 3. 应用程序类加载器 Application/System ClassLoader 是类加载器ClassLoader.getSystemClassLoader方法的返回值因此称为系统类加载器。 负责加载用户路径上指定的类库。一般情况下是默认的类加载器 4. 自定义类加载器 Custom ClassLoader 负责加载用户自定义的jar包。3 加载clas文件原理1. JVM中类的加载是由类加载器和它的子类来实现的。java中的类加载器是一个重要的java运行时系统组件它负责在运行时查找和装入类文件中的类。 2. 由于java的跨平台性经过编译的java源程序并不是可执行的程序而是一个或者多个类文件。当java程序需要使用某个类时JVM会确保这个类已经被加载、连接(验证、准备和解析) 和初始化。 3. 类的加载 1类加载器将 .calss 文件加载到内存主要是.class文件获取也可以是从zip(jar、war)包中读取、运行时计算生成(动态代理)、由其他文件生成(比如将 JSP 文件转换成对应的 Class 类)等。 2JVM解析 .class 文件提取类的基本信息(如类名、访问标志、父类、接口、字段、方法等)将提取出的类元数据存储到方法区中这些信息包括类的结构信息(字段、方法、接口、注解等)、方法的字节码、运行时常量池、类加载器的引用等。 3JVM在堆内存中创建一个java.lang.Class类的实例这个class实例代表刚才加载的类作为该类的反射入口它持有指向方法区中元数据的引用。 4. 加载完成后Class对象还不完整所以此时的类还不可用。当类被加载后就进入了连接阶段。 5. 连接 阶段包括 1验证确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求并且不会危害虚拟机自身的安全。 2准备为静态变量分配内存并设置默认的初始值。 3解析虚拟机将常量池中的 符号引用 替换为 直接引用的过程。 6. 最后JVM对类进行初始化初始化操作包括 1执行类的构造器方法。 2如果类存在直接的父类并且父类还没有被初始化那么就先初始化父类。 3如果类中存在初始化语句那么就依次执行这些初始化语句。 注意下面的情况不会执行类初始化 a. 通过子类引用父类的静态字段只会触发父类的初始化而不会触发子类的初始化。 b. 定义对象数组不会触发该类的初始化。 c. 常量在编译期间会存入调用类的常量池中本质上并没有直接引用定义常量的类不会触发定义常量所在的类。 d. 通过类名获取 Class 对象不会触发类的初始化。 e. 通过 Class.forName 加载指定类时如果指定参数 initialize 为 false 时也不会触发类初始化 其实这个参数是告诉虚拟机是否要对类进行初始化。 f. 通过 ClassLoader 默认的 loadClass 方法也不会触发初始化动作。 7. 类的加载是由是类加载器完成的类加载器有4种类型。分别是启动类加载器扩展类加载器应用程序类加载器、自定义类加载器。5. 内存溢出1定义1. 程序运行过程中无法申请到足够的内存而导致的一种错误。 2. 内存溢出通常发生于老年代或者永久代垃圾回收后仍然没有内存空间容纳新的java对象的情况。2 分类1. 堆内存溢出是实际中最常见的内存溢出情况。 2. 方法区内存溢出。 3. 线程栈内存溢出。6. 内存泄露1定义程序中动态分配一些内存给对象但是对象始终处于被占用的状态一直占用内存而且无法被GC回收导致最终内存不够用而对象本身处于可到达而无用的状态。2触发内存泄漏的场景1. 长生命周期的对象持有短生命周期对象的引用 短生命周期的对象正常应该被很快的回收但是因为被常生命周期的对象引用导致自身一直无法被回收。 比如在全局静态map中缓存局部变量且没有清空操作随着时间的推移这个map会越来越大最后造成内存泄漏。 2. 修改HashSet所存储的对象中那些参与计算哈希值的字段 对象参与计算哈希值的字段被修改后会导致无法从HashSet中删除该对象造成内存泄漏。 3. 机器的连接数和关闭时间设置 长时间开启非常耗费资源的连接也会造成内存泄漏。3处理方案1. 通过内存镜像分析工具(Visuaivm)对dump出来的堆转储快照进行分析确认内存中的对象是否是必要的分清楚到底是出现了内存泄漏还是内存溢出。 2. 如果是内存泄漏可以进一步通过工具查看泄漏对象到GC Roots 的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Roots 相关联并导致垃圾收集器无法自动回收它们的。掌握了泄漏对象的类型信息以及GC Roots 引用链的信息就可以比较准确地定位出泄漏代码的位置。 3. 如果不存在泄漏就是说内存中的对象确实都还必须存活着那就应该检查虚拟机的堆参数**-Xmx 与-Xms与机器物理内存对比看是否还可以调大从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况尝试减少程序运行期的内存消耗。4 怎样避免1. 尽早释放无用对象的引用。 2. 如果字符串拼接比较多使用StringBuilder替代String因为String是不可变的每一个String对象都会占用内存的一部分空间。 3. 尽量减少静态变量因为静态变量是存放在方法区的永久区的基本不参与垃圾回收。 4. 避免在循环中创建对象。 5. 开启大型文件或者是一次性从数据库拿了太多的数据都会造成内存溢出所以要大概计算下数据量的最大值是多少设置所需最小及最大的内存空间值。7. 项目启动参数设置注意 java8和java11的参数设置有的不一样需要注意-Xms4g -Xmx4g -XX:UseG1GC -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/logs/TSINGYUN-PSM/dump/psm.hprof -XX:PrintGC -Xlog:gc*:file/data/logs/tsingyun-psm/gc/gc.log -Duser.timezoneGMT8 -Xdebug -Xrunjdwp:transportdt_socket,servery,suspendn,address10.20.208.244:170018. 内存问题排查1. 查询磁盘使用情况 df -h 2. 查询内存使用情况 free -h 3. 查询CPU使用情况 top 4. 查询进程占用cpu排序 top 然后按1 5. 查询cpu占用最高的线程号 top -Hp 进程号 如 top -Hp 2005104 6. 查询占用高的线程对应的16进制值 【这是因为操作系统的线程id与java中的线程id不同需要转换进制后关联】 7. printf %x\n 进程号 如printf %x\n 2374049 结果 2439a1 8. 使用 jstack 输出进程对应的堆栈信息 jstack 2005104| grep 2439a1 -F -m -l -h 9. 持续生成进程记录到指定文件 nohup top -d5 -Hbp2287885 /data/logs/TSINGYUN-PSM/dump/threadInfo.log 10. 手动生成dump文件 jmap -dump:formatb,file/data/logs/TSINGYUN-PSM/dump/psm_241024.hprof 22878859. 调优1 调优目标1. 减少GC频率与停顿时间 避免长时间stop-the-world影响响应速度 2. 合理分配内存 避免内存溢出或者内存浪费。 3. 优化线程与锁机制 降低死锁线程竞争等问题。 4. 提升吞吐量 在高并发场景下最大化CPU利用率。2 调优工具1. jps JVM Process Status Tool 显示指定系统内所有的HotSpot虚拟机进程。 2. jstat JVM statistics Monitoring 用于监视虚拟机运行时状态可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。 命令 jstat -gc pid 1000 实时监控GC统计信息 3. jmap JVM Memory Map 用于生成heap dump文件。 命令 jmap -heap pid堆内存分析 4. jhat JVM Heap Analysis Tool 与jmap搭配使用用来分析jmap生成的dumpjhat内置了一个微型的HTTP/HTML服务器生成dump的分析结果后可以在浏览器中查看。 5. jstack 用于生成java虚拟机当前时刻的线程快照。 命令 jstack pid线程快照分析 6. VisualVM 内存、线程、GC可视化监控3 调优方法1. 监控基线使用工具收集CPU、内存、GC日志 2. 分析瓶颈 1频繁Full GC→ 检查内存泄漏或老年代过小。 2Young GC时间长→ 调整Eden/Survivor比例。 3高CPU占用→ 分析线程竞争或死锁。 3. 参数调整逐步修改JVM参数避免一次性过多变更。 4. 验证效果对比调优前后的GC日志和性能指标。
返回列表