• 新同事写了几段小代码,把系统给搞崩了,被老板爆怼一顿!


    Java程序是基于GC的,在启动初始,就申请了足量的内存池,再加上JIT等编译器的实时优化,速度并不比直接用C++语言写的慢。Java语言同时由于反射和可观测等特点,再加上JFR这种神器,在发生问题的时候比二进制文件更容易找到它的根源。

    最近在看RCA(Root Cause Analysis)的东西,不小心发现了yCrash这么个东西。它的几段问题小代码写的非常典型,我们可以稍微看一下,来看看Java应用程序常见的几个崩溃场景。

    1.堆空间溢出

    OOM 一般是内存泄漏引起的,表现在 GC 日志里,一般情况下就是 GC 的时间变长了,而且每次回收的效果都非常一般。GC 后,堆内存的实际占用呈上升趋势。

    下面的代码是死循环,持续向HashMap里塞数据,由于myMap属于GCRoots,始终得不到释放,所以它最终的结果就是OOM。

    1. import java.util.HashMap;
    2. public class OOMDemo {
    3. static HashMap myMap = new HashMap<>();
    4. public static void start() throws Exception {
    5. while (true) {
    6. myMap.put("key" + counter, "Large stringgggggggggggggggggggggggggggg"
    7. + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
    8. + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
    9. + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
    10. + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
    11. + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
    12. + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
    13. + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
    14. + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
    15. + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
    16. + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
    17. + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg"
    18. + counter);
    19. ++counter;
    20. }
    21. }
    22. }

    2.内存泄漏

    内存泄漏和内存溢出是一个道理,不同的是它的语意。

    内存溢出可能是由于请求量过高,或者真实的业务需求需要所造成的后果,而内存溢出属于未知的、超出期望的OOM情况。

    我们可以使用上面同样的代码达到这个目的。

    在现实情况中,内存泄漏通常都非常的隐蔽,需要借助Mat等工具才能找到根本原因。jmap、pmap等是常用的工具。

    比如,如果你忘记了重写对象的hashCode和equals方法,就会产生内存泄漏。

    1. //leak example : created by xjjdog 2022
    2. import java.util.HashMap;
    3. import java.util.Map;
    4. public class HashMapLeakDemo {
    5. public static class Key {
    6. String title;
    7. public Key(String title) {
    8. this.title = title;
    9. }
    10. }
    11. public static void main(String[] args) {
    12. Map map = new HashMap<>();
    13. map.put(new Key("1"), 1);
    14. map.put(new Key("2"), 2);
    15. map.put(new Key("3"), 2);
    16. Integer integer = map.get(new Key("2"));
    17. System.out.println(integer);
    18. }
    19. }

    3.CPU飙升

    直接一个死循环,就可以把CPU干死。

    1. public class CPUSpikeDemo {
    2. public static void start() {
    3. new CPUSpikerThread().start();
    4. new CPUSpikerThread().start();
    5. new CPUSpikerThread().start();
    6. new CPUSpikerThread().start();
    7. new CPUSpikerThread().start();
    8. new CPUSpikerThread().start();
    9. System.out.println("6 threads launched!");
    10. }
    11. }
    12. public class CPUSpikerThread extends Thread {
    13. @Override
    14. public void run() {
    15. while (true) {
    16. // Just looping infinitely
    17. }
    18. }
    19. }

    获取问题代码通常可以使用下面的方法。

    (1)使用 top 命令,查找到使用 CPU 最多的某个进程,记录它的 pid。使用 Shift + P 快捷键可以按 CPU 的使用率进行排序。(2)再次使用 top 命令,加 -H 参数,查看某个进程中使用 CPU 最多的某个线程,记录线程的 ID。(3)使用 printf 函数,将十进制的 tid 转化成十六进制。(4)使用 jstack 命令,查看 Java 进程的线程栈。(5)使用 less 命令查看生成的文件,并查找刚才转化的十六进制 tid,找到发生问题的线程上下文。

    4.线程泄漏

    线程资源是昂贵的。如果你不停的创建线程,系统资源很快就会被耗尽。下面的代码一直不停的创建线程,如果同时请求压力比较大的话,多数能搞死宿主机。

    1. public class ThreadLeakDemo {
    2. public static void start() {
    3. while (true) {
    4. new ForeverThread().start();
    5. }
    6. }
    7. }
    8. public class ForeverThread extends Thread {
    9. @Override
    10. public void run() {
    11. // Put the thread to sleep forever, so they don't die.
    12. while (true) {
    13. try {
    14. // Sleeping for 10 minutes repeatedly
    15. Thread.sleep(10 * 60 * 1000);
    16. } catch (Exception e) {}
    17. }
    18. }
    19. }

    这是暴力啊,这和每个请求创建一个线程,或者创建一个线程池的后果是一样的。xjjdog这里还有两篇关联的线程泄漏文章。

    • 强烈反对使用Spring封装的多线程类!
    • 夺命故障!炸出了投资人!

    java.lang.OutOfMemoryError: unable to create new native thread

    5.死锁

    死锁代码一般不会发生,但一旦发生还是非常严重的,相关的业务可能就跑不动了。

    1. public class DeadLockDemo {
    2. public static void start() {
    3. new ThreadA().start();
    4. new ThreadB().start();
    5. }
    6. }
    7. public class ThreadA extends Thread {
    8. @Override
    9. public void run() {
    10. CoolObject.method1();
    11. }
    12. }
    13. public class ThreadB extends Thread {
    14. @Override
    15. public void run() {
    16. HotObject.method2();
    17. }
    18. }
    19. public class CoolObject {
    20. public static synchronized void method1() {
    21. try {
    22. // Sleep for 10 seconds
    23. Thread.sleep(10 * 1000);
    24. } catch (Exception e) {}
    25. HotObject.method2();
    26. }
    27. }
    28. public class HotObject {
    29. public static synchronized void method2() {
    30. try {
    31. // Sleep for 10 seconds
    32. Thread.sleep(10 * 1000);
    33. } catch (Exception e) {}
    34. CoolObject.method1();
    35. }
    36. }

    死锁属于比较严重的一种情况,jstack 会以明显的信息进行提示。当然,关于线程的 dump,也有一些线上分析工具可以使用。比如fastthread,但也需要你先了解这些情况发生的意义。

    6.栈溢出

    栈溢出不会造成 JVM 进程死亡,危害“相对较小”。下面是一个简单的模拟栈溢出的代码,只需要递归调用就可以了。

    1. public class StackOverflowDemo {
    2. public void start() {
    3. start();
    4. }
    5. }

    通过 -Xss 参数可以设置虚拟机栈的大小。比如下面的命令就是设置栈大小为 128K。

    -Xss128K

    如果你的应用经常发生这种情况,可以试着调大这个值。但一般都是因为程序错误引起的,最好检查一下自己的代码。

    7.Blocked线程

    BLOCKED是一个比较严重的线程状态,当后端的服务处理时间非常长,请求的线程就会进入等待状态。这时候通过jstack来获取堆栈,就会发现线程处于阻塞状态。它阻塞在对锁的获取上(wating to lock)

    1. public class BlockedAppDemo {
    2. public static void start() {
    3. for (int counter = 0; counter < 10; ++counter) {
    4. // Launch 10 threads.
    5. new AppThread().start();
    6. }
    7. }
    8. }
    9. public class AppThread extends Thread {
    10. @Override
    11. public void run() {
    12. AppObject.getSomething();
    13. }
    14. }
    15. public class AppObject {
    16. public static synchronized void getSomething() {
    17. while (true) {
    18. try {
    19. Thread.sleep(10 * 60 * 1000);
    20. } catch (Exception e) {}
    21. }
    22. }
    23. }

    一旦频繁发生这种情况,就证明你的程序相应太慢了。如果CPU资源还有剩余,可以尝试着增加请求的线程数,比如tomcat的最大线程数。

  • 相关阅读:
    SWAT模型应用
    2022-08-01 mysql/stoonedb慢SQL-Q18分析
    【Linux】进程控制
    MyBatis-Plus
    【C#】【FFmpeg】获取电脑可用音视频设备并输出到下拉列表框
    【开源】基于SpringBoot的高校学院网站的设计和实现
    面试官问:如何判断一个元素是否在可视区域?
    Rust个人学习之结构体
    恶意代码可视化检测技术研究综述
    结构型模式-代理模式
  • 原文地址:https://blog.csdn.net/weixin_72753070/article/details/126014786