怎么样写出带Bug的代码

1、创建class时不没有重写hashCode()equals()方法,轻则创建的对象比较时无法区分,重则将大量对象存储至map时导致内存泄漏。

  解决方法:根据业务需要重写equals()方法和hashCode()方法。

2、内部类引用外部类

2.1、非静态的内部类默认会持有外部类,尽管代码上不再使用外部类,所以如果有地方引用了这个非静态内部类,会导致外部类也被引用,垃圾回收时无法回收这个外部类。

举例

public class Outer{
    private byte[] bytes = new byte[1024]; //外部类持有数据
    private  String name  = "Bug";
     class Inner{
        private String name;
        public Inner() {
            this.name = Outer.this.name;
        }
    }
    public static void main(String[] args) throws IOException, InterruptedException {
        int count = 0;
        ArrayList<Inner> inners = new ArrayList<>();
        while (true){
            if(count++ % 100 == 0){
                Thread.sleep(10);
            }
            inners.add(new Outer().new Inner());
        }
    }
}

解决方法:使用静态内部类

public class FixOuter {
    private byte[] bytes = new byte[1024 * 1024]; //外部类持有数据
    private static String name  = "Bug";
    static class Inner{
        private String name;
        public Inner() {
            this.name = FixOuter.name;
        }
    }
    public static void main(String[] args) throws IOException, InterruptedException {
        int count = 0;
        ArrayList<Inner> inners = new ArrayList<>();
        while (true){
            if(count++ % 100 == 0){
                Thread.sleep(10);
            }
            inners.add(new Inner());
        }
    }
}

 

2.2、匿名内部类对象如果在非静态方法中被创建,会持有调用者对象,垃圾回收时无法回收调用者。

举例

public class Outer {
    private byte[] bytes = new byte[1024];
    public List<String> newList() {
        List<String> list = new ArrayList<String>() {{
            add("1");
            add("2");
        }};
        return list;
    }
    public static void main(String[] args) throws IOException {
        int count = 0;
        ArrayList<Object> objects = new ArrayList<>();
        while (true){
            System.out.println(++count);
            objects.add(new Outer().newList());
        }
    }
}

 

解决方法:使用静态方法

public class FixOuter {
    private byte[] bytes = new byte[1024];
    public static List<String> newList() {
        List<String> list = new ArrayList<String>() {{
            add("1");
            add("2");
        }};
        return list;
    }
    public static void main(String[] args) throws IOException {
        int count = 0;
        ArrayList<Object> objects = new ArrayList<>();
        while (true){
            System.out.println(++count);
            objects.add(newList());
        }
    }
}

3、ThreadLocal使用不当

3.1ThreadLocal变量会与线程绑定,如果线程长时间存在(例如线程池中的线程),而ThreadLocal变量没有被及时移除,可能会导致内存泄漏。

解决方法在使用完ThreadLocal变量后,调用remove()方法移除变量。

3.2ThreadLocal本身是线程安全的,但需要注意在使用ThreadLocal变量时,不要在多个线程中共享同一个ThreadLocal实例。

解决方法每个线程使用自己的ThreadLocal实例,或者使用InheritableThreadLocal来实现线程间的数据传递

4、IO或者网络资源没有正确关闭

解决方法:在finally块中关闭不再使用的资源;从 Java 7 开始,使用try-with-resources语法可以用于自动关闭资源

5、Arrays.asList()方法使用不当

 

List<String> list = Arrays.asList("a", "b", "14a", "36B");
String remove = list.remove(0);

java.lang.UnsupportedOperationException
at java.base/java.util.AbstractList.remove(AbstractList.java:167)

 

 解决方法:使用错误源于对Arrays.asList()说明不够了解,想快速简单可编辑List可以在使用Arrays.asList()创建后再使用ArrayList进行一下再包装

    

List<String> list = Arrays.asList("a", "b", "14a", "36B");
        ArrayList<String> l = new ArrayList<>(list);
        String remove = l.remove(0);

6、CompletableFuture线程池配置不当,默认线程池使用的是ForkJoinPool,ForkJoinPool的cpu默认核心数是当前cup核心数(jdk17)在IO密集型任务中,这会导致大量任务排队等待,可能会导致内存溢出(任务提交速度 大于 任务处理速度)

public static void main(String[] args) {
    // 统计已提交的任务数量(线程安全)运行前建议设置 JVM 参数:-Xmx64m (或更小)以快速触发 OOM
    AtomicLong submittedCount = new AtomicLong(0);
try {
// 无限循环提交任务
while (true) {
// 每个任务携带一个 1KB 的数组,增加内存占用,加速 OOM
byte[] data = new byte[1024]; // 1 KB

// 提交异步任务(默认使用 ForkJoinPool.commonPool())
CompletableFuture.runAsync(() -> {
// 通过引用 data 确保数组被 lambda 捕获,使其在任务执行前不会被 GC
int len = data.length; // 仅用于确保捕获

// 模拟 IO 密集型操作(例如网络请求、文件读写等)
try {
TimeUnit.SECONDS.sleep(2); // 2 秒,处理速度极慢 IO 操作特点:线程大部分时间在等待,不占用 CPU
                } catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});

// 更新提交计数并每 10,000 次打印一次进度
long count = submittedCount.incrementAndGet();
if (count % 10000 == 0) {
System.out.println("已提交任务数: " + count);
}
}
} catch (Throwable t) {
// 捕获任何错误(包括 OutOfMemoryError),打印信息后退出
System.err.println("捕获到异常/错误: " + t);
t.printStackTrace();
System.err.println("崩溃前总计提交任务数: " + submittedCount.get());

// 短暂等待,让部分已完成的任务有机会输出日志(如果有)
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException ie) {
// ignore
}
System.exit(1);
}
}

  解决方法:根据业务场景需求使用测试数据多轮压测确定线程池配置

public static void main(String[] args) {
        // 运行前建议设置 JVM 参数:-Xmx256m (无需过小,观察内存平稳即可)
        // 获取 CPU 核心数,用于估算线程池大小
        int processors = Runtime.getRuntime().availableProcessors();

        // IO 密集型任务可适当调大线程数(例如 2~4 倍核心数)
        int corePoolSize = processors * 2;      // 核心线程数
        int maximumPoolSize = processors * 4;   // 最大线程数
        long keepAliveTime = 60L;                // 空闲线程存活时间
        TimeUnit unit = TimeUnit.SECONDS;

        // 使用有界队列,容量固定,避免无限积压
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(1000);

        // 拒绝策略:调用者运行(让提交任务的线程自己执行任务)
        // 这样当队列满时,主线程会参与任务执行,从而减缓提交速度,形成背压
        RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();

        // 创建自定义线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                unit,
                workQueue,
                handler
        );

        // 允许核心线程超时回收,避免空闲线程长期占用资源
        executor.allowCoreThreadTimeOut(true);

        // 计数器:记录已提交任务数(线程安全)
        AtomicLong submittedCount = new AtomicLong(0);

        // 注册 JVM 关闭钩子,确保程序退出时优雅关闭线程池
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("\n正在关闭线程池...");
            executor.shutdown();
            try {
                if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
                    executor.shutdownNow();
                }
            } catch (InterruptedException e) {
                executor.shutdownNow();
            }
        }));

        try {
            // 无限循环提交任务,模拟持续的高负载
            while (true) {
                // 每个任务携带一个 1KB 的数组,模拟内存占用(实际业务中可能是 IO 缓冲区)
                byte[] data = new byte[1024];

                // 提交异步任务,使用自定义线程池
                CompletableFuture.runAsync(() -> {
                    // 通过引用 data 确保数组在任务执行前不被 GC
                    int len = data.length; // 仅用于捕获 data

                    // 模拟 IO 密集型操作(例如数据库查询、远程调用等)
                    try {
                        TimeUnit.SECONDS.sleep(2); // 2 秒,远慢于提交速度
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }, executor);

                // 更新计数器,并定期打印线程池状态
                long count = submittedCount.incrementAndGet();
                if (count % 1000 == 0) {
                    System.out.printf("已提交任务数: %d, 队列大小: %d, 活跃线程数: %d, 已完成任务数: %d%n",
                            count,
                            executor.getQueue().size(),
                            executor.getActiveCount(),
                            executor.getCompletedTaskCount());
                }
            }
        } catch (Throwable t) {
            // 捕获任何异常或错误(包括 OOM,但本示例中应不会发生)
            System.err.println("捕获到异常/错误: " + t);
            t.printStackTrace();
            executor.shutdownNow();
        }
    }

  7、CompletableFuture异常丢失,这是CompletableFuture的异常处理机制的特性

public static void main(String[] args){
        System.out.println("异常被彻底吞掉!");

        CompletableFuture.supplyAsync(() -> {
                    System.out.println("→ 任务开始执行...");
                    if (true) throw new RuntimeException("数据库连接超时!"); // 模拟故障
                    return "成功数据";
                })
                .thenApply(result -> {
                    //  陷阱1:前序已异常,此代码块根本不会执行!
                    //  陷阱2:此处的try-catch毫无意义(根本进不来)
                    try { return result.toUpperCase(); }
                    catch (Exception e) { return "兜底"; }
                })
                .thenAccept(System.out::println); //  陷阱3:无异常处理,无get/join

        try {
//         等待异步线程(但异常已丢失!)
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("主线程结束,异未出现");public static void main(String[] args){
        System.out.println("异常被彻底吞掉!");

        CompletableFuture.supplyAsync(() -> {
                    System.out.println("→ 任务开始执行...");
                    if (true) throw new RuntimeException("数据库连接超时!"); // 模拟故障
                    return "成功数据";
                })
                .thenApply(result -> {
                    //  陷阱1:前序已异常,此代码块根本不会执行!
                    //  陷阱2:此处的try-catch毫无意义(根本进不来)
                    try { return result.toUpperCase(); }
                    catch (Exception e) { return "兜底"; }
                })
                .thenAccept(System.out::println); //  陷阱3:无异常处理,无get/join

        try {
//         等待异步线程(但异常已丢失!)
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("主线程结束,异未出现");

    解决方法:在CompletableFuture调用链中使用whenComplete方法,末尾使用handle方法和exceptionally方,调用get或者 join方法 

public static void main(String[] args) {
System.out.println("异常精准捕获+降级+监控");

CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> {
if (Math.random() > 0.5)
// throw new IllegalStateException("API限流");
throw new RuntimeException("数据库连接超时!");
return "原始数据";
}, Executors.newSingleThreadExecutor()) // 显式指定线程工厂(JDK17推荐)
.whenComplete((result, throwable) -> {
if (Objects.nonNull(throwable)) {
System.err.println(throwable);
} else {
System.out.println(result);
}

})
// 第一层:链内精准处理(推荐handle)
.handle((result, ex) -> {
if (ex != null) {
System.err.println("捕获异常: " + ex.getMessage());
// 记录监控指标(示例)
// Metrics.counter("cf_error").increment();
return "【降级数据】"; // 返回安全默认值
}
return result.toUpperCase();
})

// 第二层:链尾兜底(防止handle遗漏)
.exceptionally(ex -> {
System.err.println("链尾兜底异常: " + ex.getMessage());
return "【终极降级】";
});

// 第三层:强制获取结果(生产环境必须!)
try {
String result = future.get(3, TimeUnit.SECONDS); // 显式超时控制
System.out.println("最终结果: " + result);
} catch (TimeoutException e) {
System.err.println("任务超时!");
future.cancel(true); // 取消任务
} catch (ExecutionException e) {
// JDK 17中:getCause()可获取原始异常(非CompletionException包装)
System.err.println("执行异常: " + e.getCause().getMessage());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("线程中断");
}
}

  

 

posted @ 2024-12-07 21:43  干瘪咸鱼  阅读(49)  评论(0)    收藏  举报