ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

JVM(java虚拟机)性能分析

2022-04-04 16:02:12  阅读:230  来源: 互联网

标签:java 对象 虚拟机 -- GC 内存 JVM memory 线程


一、jvm性能调优的目标---降低垃圾回收的频率和时间

  JAVA 程序运行时,jvm 自动进行内存的回收和释放,将死亡的对象从内存里面移除,以释放更多的内存空间供新生的对象使用。这个过程就是 JVM 的垃圾回收,又称之为 GC。新时代垃圾回收,称之为 MinorGC,老年代垃圾回收称之为 MajorGC。GC 的时候所有线程都会暂停等待 GC 完成,频繁 GC 会极大的影响应用性能。Jvm 调优的一个主要目标就是降低 GC 频率和时间

二、Jvm 的内存空间

  1.Jvm 的内存空间包括上以下个部分

    堆区: 堆区分为 年轻代(分为 eden 和 存活区,存活区是 S0  S1) ,老年代,用来存放不同类型的对象。 例如 朝生暮死的小对象,长数组的大对象,长生不死

      堆空间最大是物理内存的1/4,最小是物理内存的1/64,没有设置固定式,就是最大最小活动。

    年轻代:年轻代有三个区域,一个 n eden 区和 2 2 个 r survivor 区。 新的对象都是在 n eden 创建的,然后在 r survivor 区域内多次 gc ,杀不死的对象或者过大的对象直接进去老年代

    老年代 :老年代主要存放超龄对象( ( 超过年龄阈值) ) 和大对象( ( 超过尺寸阈值) ) 。老年代空间不足的时候,直接触发 fullgc

    方法区: 存储被虚拟机加载的类信息、常量、字符串常量、类静态变量、编译后的代码数据等

    栈区xss: java 线程在运行时需要申请线程栈,虚拟机栈中存放每个方法在执行的同时创建的栈帧,栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。当一个方法被调用,栈帧入栈;当这个方法返回,栈帧出栈

      应用可提交内存 = 最大堆内存+xss*线程数

    程序计数器 : 程序计数器记录着当前线程所执行的字节码的行号指示器 ,即当前运行位置。它和 U CPU 的寄存器并称为 进程切换 的上下文

  2.Jmap -heap {PID} 可以查看 JVM 内存空间分配

  

 

 

   

 

 

 三、GC过程以及原理

  新的内存对象是在eden中存活,如果eden满了做一次YGC,清空eden。残留的垃圾对象会扔到S0,eden又满了,做第二次YGC,清空eden和S0,把残留的垃圾对象会扔到S1,周而复始

  有部分数据始终无法GC,会被打上存活标签【每次GC年龄加1】,超出年龄上线 (默认 16  岁 【-XX:MaxTenuringThreshold  年轻代GC最大年龄】) ,垃圾数据进去老年代。

  老年代空间满了,进行一次FullGC,返回是所有堆内存空间,FGC的时间会很长,超过200ms就问题很大了。FGC的时候CPU是暂停的,长时间暂停会导致业务数据丢失

  如果年轻代的对象尺寸超出老年代的剩余大小,也会FullGC

    

  年轻代 GC: 新创建的对象都会被分配到 Eden 区( ( 没有超出尺寸阈值的对象 ), 这些对象经过第一次 Minor GC 后如果 还活着 , 就 被复制到两块 Survivor 区 轮转( 交替复制) ,在 Survivor 区中每熬过一次 GC ,年龄就会增加 1 岁,当年龄增加到一定程度时( (默认 16  岁) ,就会被 扔 到 老年 代中。 因为这里 存放的基本都是朝生暮死的小对象 ,所以 采用复制算法 进行 GC 。即 将内存分为两块,每次只用一块 。 当这一块内存用完,将没死 的对象复制到另外一块 ,优点是不会产生内存碎片 。

   老年代 GC : 超出尺寸阈值和年龄阈值的对象都会进入老年代。随着 Minor GC 的持续进行,老年代对象也会持续增长,最终老年代的空间也不够 了 ,就会执行 MajorGC 。 老年代 使用标记清除算法 。 首先从对象的集合中进行遍历,如果发现对象 依然引用, 就 打上存活的标记 ,接着再去 对象集合进行二次遍历, 把那些 没有被打标记的对象清除掉。 因为需要 2 次扫描对象,所以老年代的 GC 时间会很长

  查看进程的GC情况:【jstat -gcutil {pid} 1000】

  

 

 

   

 

 

 

 四、参数配置

 

 

   

 

 

 五、内存溢出

    Kill process or sacrifice child 【强杀进程 OOMkiller】--内核层面

    Out of swap space  【swap 满了  --改空间或者关掉swap 】--内核层面

    memory cgroup out of memory --内核层面【3.x内核层面的问题,开启keem的方法,申请的内存不会被释放  查看内核版本 uname -r】

      ls -al /sys/kernel/slab/| grep inode_cache |more 查看slab缓存-

      cat /sys/fs/cgroup/memory/kubepods/memory.kmem.slabinfo  查看内存泄露,不报错就没有问题

      dmesg | grep -i memory  查看内存日志

       如果有以上三个问题--解决方案

          1:内核参数文件 /boot/grub2/grub.cfg 中添加 cgroup.memory=nokmem 让系统禁用 cgroup 的 kmem        

          2:内核参数文件 /boog/grub2/grub.cfg 中添加 cgroup_disable=memory关掉整个 cgroup memory

          3:升级内核版本到 4.x

    StackOverflow【栈内存溢出】 --应用层面

      无限递归循环,超出栈帧深度,栈空间耗尽

      执行了大量方法,导致线程栈空间耗尽

      方法内声明了海量的局部变量

      解决方案:

        最常用的是修改启动参数 -Xss 增加栈内存空间,但是在某些异常场景下,这种做法毫无用处

        通过程序抛出的异常堆栈,修复引发无限递归调用的异常代码

    Java heap space【堆内存溢出】--应用层面

      请求创建了一个超大对象(数组),超出了 heap 区域内存空间

      内存持续泄漏,大量对象没用被 GC,JVM 无法对其自动回收

      解决方案:

        通常只要将 -Xmx 参数调高即可

        如果是超大对象,需要检查合理性

        如果是内存泄漏,需要找到持有的对象,修改代码设计

    GC overhead limit exceeded--应用层面

      GC花费98%的时间只回收了2%的内存,持续5次

      -XX:-UseGCOverheadLimit

      解决方案:

        添加 JVM 参数-XX:-UseGCOverheadLimit

        检查代码的死循环,优化它

        检查是否存在内存泄露,优化它

    Unable to create new native thread --应用层面

      Java 无法直接访问到操作系统底层(例如上图所说的 allocateDirect 本地内存),为此 Java 使用 native 方法来扩展 Java 线程的功能。当 JVM 向底层操作系统请求创建一个新的 native 线程时,如果没有足够的资源分配就会返回这个错误

        原因:

          线程数超过操作系统最大线程数(thread_max) 

          操作系统的内存耗尽,拒绝本次 native 内存分配

    Direct buffer memory Java 应用通过 Direct ByteBuffer 直接访问堆外内存。堆外内存不足,返回此错误

       NIO 模式下需要使用 ByteBuffer 来读写数据,它使用 Native 函数库直接分配堆外内存。应用程序通过 Direct ByteBuffer 直接访问堆外内存,实现高速 IO。但是 ByteBuffer.allocateDirect 分配的是本地内存,不归 GC 管。正式因为不需要内存拷贝所以速度相对较快。不过由此带来一个问题,因为分配的都是本地内存,所以堆内存很少使用,JVM 就不执行 GC,DirectByteBuffer 对象就无法被回收。此时虽然堆内存充足,但本地内存却不够了,就会出现 java.lang.OutOfMemoryError: Directbuffer memory

      解决方案:

          调整 启动参数  - - XX:MaxDirectMemorySize 设置 Direct ByteBuffer  的上限值

          检查 JVM  参数是否有  - - XX:+DisableExplicitGC 选项,如果有就去掉,该参数会使  System.gc() 失效

          物理内存不足,升级配置

 

六、常见的线程问题

    CPU  使用率不高但是响应时间很长

      对进行线程 dump,检查是不是有线程卡在了 IO、数据库这些地方

      检查是否有“ Waiting on condition”,如果有则说明线程在等待某种条件来唤醒。比如 sleep 时间,网络 IO,磁盘 IO

      检查是否有线程死锁( 线程死锁指的是两个线程互相持有对方的锁,无法释放)

    CPU 的 的 s us  使用率高,负载很高,响应很长

      多次堆栈,检查是否有某个线程一直在执行同一个方法

七、堆栈日志分析

  常见线程堆栈状态

    waiting on condition:线程等待某个条件的发生;线程进入了 sleep;线程在等待网络的读写

    Waiting for Monitor Entry and in Object.wait():等待获取线程 monitor

    "C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f6e4813e800

    nid=0x3803 waiting on condition [0x0000000000000000]

    java.lang.Thread.State: RUNNABLE
    这一段线程堆栈的意思是,线程进入 waiting on condition 状态,等待” C2
    CompilerThread”编译代码,当前状态是 runnable

 

搜索

复制

            

标签:java,对象,虚拟机,--,GC,内存,JVM,memory,线程
来源: https://www.cnblogs.com/Mr-Simple001/p/16099474.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有