月度记录-2024-6月

139 阅读9分钟

1. 在IDEA中,调试模式和直接运行模式,执行相同的代码,调试模式执行得慢

我是在执行一个for循环的时候发现的,本来是想统计for循环的执行耗时,结果意外发现只要打了断点(即使在最后一行的sout语句上打断点),执行时长就会增加数倍。比较好奇,以前从来没想过,问了下gpt。以下是可能的原因:

  • 断点和调试信息:

  • 断点处理:在调试模式下,IDEA会插入断点。这意味着每当程序执行到断点处时,程序会暂停,以便开发者查看程序状态。这种暂停和检查会显著增加程序的执行时间。

  • 调试信息的插入:调试模式会插入额外的信息和代码来追踪变量和调用栈。这些插入的代码会增加执行的开销。

  • 逐行执行和代码检查:

  • 逐行执行:调试模式允许逐行执行代码,以便开发者能够查看每一行代码的执行情况。这种逐行检查会导致程序运行速度显著降低,因为程序不能连续执行,而是每执行一行就需要停下来等待用户的操作。

  • 代码检查:调试器会检查和显示当前作用域内的所有变量的值。每当变量发生变化时,调试器都需要更新这些值,这增加了额外的处理时间。

  • 禁用优化:

  • 禁用JIT优化:在调试模式下,为了确保程序行为与源代码严格一致,JVM可能会禁用某些即时编译器(JIT)优化。这些优化通常在正常运行模式下大大提高代码的执行效率,但在调试模式下被禁用或减少使用,以便调试信息准确无误。

  • 内联和循环展开禁用:某些优化技术(如方法内联、循环展开等)在调试模式下可能被禁用。这些优化通常能显著提高程序性能,但在调试模式下,它们可能导致调试信息不准确。

  • 额外的系统资源开销:

  • 调试器线程:调试器本身需要运行多个线程来监视和控制被调试程序。这些额外的线程和资源消耗会导致程序执行速度变慢。

  • 内存和处理器开销:调试器需要额外的内存和处理器资源来保存调试信息、断点、变量状态等。这些额外的资源消耗会影响程序的执行性能。

我觉得最大的点,可能在禁用优化。

2. 什么是:循环展开、循环重排、方法内联

1. 循环展开(Loop Unrolling)

循环展开是一种优化技术,其主要目标是减少循环控制的开销(如循环计数器的增加和终止条件的检查),从而提高循环执行的效率。例子:

原始代码:
for (int i = 0; i < 8; i++) {
    array[i] = i * 2;
}
展开后的代码:
array[0] = 0 * 2;
array[1] = 1 * 2;
array[2] = 2 * 2;
array[3] = 3 * 2;
array[4] = 4 * 2;
array[5] = 5 * 2;
array[6] = 6 * 2;
array[7] = 7 * 2;
或者部分展开
for (int i = 0; i < 8; i += 2) {
    array[i] = i * 2;
    array[i + 1] = (i + 1) * 2;
}

2. 循环重排(Loop Reordering)

循环重排是优化内存访问模式的技术,通过调整循环的顺序,改善数据局部性和缓存性能,提高缓存命中率,从而提高执行效率。例子:

假设数组是按行存储的
原始代码:按照列写数据
for (int j = 0; j < M; j++) {
    for (int i = 0; i < N; i++) {
        array[i][j] = i + j;
    }
}
重排后的代码:按照行写数据,可以更好利用缓存
for (int i = 0; i < N; i++) {
    for (int j = 0; j < M; j++) {
        array[i][j] = i + j;
    }
}

3. 方法内联(Method Inlining)

方法内联是一种优化技术,它将函数调用替换为函数体的实际代码,从而消除函数调用的开销。例子:

原始代码:
int square(int x) {
    return x * x;
}
int result = square(5);
内联后的代码:
int result = 5 * 5;

其他JIT优化技术

除了上述优化技术,JVM的JIT编译器还使用其他多种优化技术,包括:

  • 逃逸分析:确定对象是否在方法之外被引用,如果没有,可以在栈上分配对象而不是堆上,从而减少垃圾回收开销。
  • 常量传播和折叠:将常量值直接传播并折叠,从而减少计算量。
  • 死代码消除:移除不影响程序结果的无用代码,减少执行负担。
  • 分支预测:优化分支条件,提高分支预测的命中率,减少分支错预测的开销。

3. org.apache.commons.collections4.MapUtils

这类里面有个getString挺好用的,还支持写defaultValue,以前都是自己写方法,现在可以直接用了。

4. idea,debug,Evaluate

debug的时候,可点击小计算器图标(ctrl+u)弹出Evaluate窗口,在这里我们可以写代码然后执行代码,灰常方便,以前想看点东西,还得复制出来,再到notepad++中查询,现在可是不用啦

5. idea快速判断

判断条件在开发过程中使用频率非常高,如何快速的写出判断条件呢?

boolean.if 可以生成if(boolean)

boolean.else 可以生成if(!boolean)

string.null 可以生成if(string == null)

string.nn 可以生成if(string != null)

string其实没啥用,因为普遍都使用StringUtils

6. 关于日志

以前记录日志是为了开发阶段能找到问题,对于线上出现问题如何通过日志快速排查,没想过。或许以后记录日志要更多地考虑线上,考虑一旦有问题,该怎么记录日志才能更快地解决问题。

7. 多用虚拟化

以后要把项目地组件打成docker镜像,方便随时启动,随时测试。其实不只是组件,项目也可以打出来,随时想看都可以看。再不济用虚拟机,,把组件和项目扔到虚拟机中去跑,不用了挂起,用的时候迅速切换。

8. IDEA中没有SVN选项的解决方法(没有git箭头,没有svn箭头)

  1. 在顶部菜单栏中选择“VCS”选项。
  2. 在下拉菜单中选择“Enable Version Control Integration”。
  3. 在弹出的窗口中,选择“Subversion”作为版本控制系统。

9. 记录一次bug

升级项目,打包发版,在自己电脑上正常运行,于是拷贝给测试,在测试电脑上一直提示密码错误。

第一时间,肯定是以为密码错了,于是在数据库替换密码,发现不对。

第二,认为是数据库连接错了,于是检查配置,不对。

第三,认为打包有问题,重新打包,还是一样,只有测试电脑上不行。

第四,换nacos配置和数据库,把我的给到测试,还是不行。

最终,精彩的来了。我把测试的redis端口,从127.0.0.1改为他的局域网ip,重启gateway服务,竟然可以登录进去了!检查redis配置,发现写的确实是127.0.0.1,有那么一瞬间大脑都要萎缩了。

明显不符合逻辑,于是进行了更细致的检查,检查代码发现gateway中用到了redis,登录之前去查询了一下redis。摸不着头脑,为何写局域网IP可以呢?这岂不是查不出来吗?

对!就是查不出来就可以正常登录!所以测试那里一定是存在这个参数!而且redis重启后也存在,所以此参数是持久化了。经过检查,确实存在此参数,删除即正常,至此问题解决。

那为何会有此参数呢?经过排查发现,是我第一次打包误操作,开错了加密方式。真是搬起石头砸自己的脚。排查问题的能力还是有待提高,这次虽然比以往快一点,但是还没有到最优化,依然可以有很多优化的地方,比如替换环境,替换的不够彻底,要不早成功 了。何必打那么几次包呢?

10. spring中读取nacos配置文件给工具类的静态变量赋值

@Component
piblic class test {
    public static String A;
    
    @Value("${nacos中配置的key}}")
    public String a;
    
    @PostConstruct
    public void init() {
        A = a;
    }
}

11. @PostConstruct

它用于在依赖注入完成后,需要执行初始化操作的方法上。具体来说@PostConstruct注解的方法会在依赖注入完成后自动调用,用于执行任何初始化工作。

上面的例子就是先实例化test,注入a的值,然后调用init方法。

具体执行顺序

  1. 实例化:Spring容器实例化bean。
  2. 依赖注入:Spring容器将所有依赖注入到bean中。
  3. @PostConstruct:调用标记了@PostConstruct的方法。

注意事项:

  1. 每个bean只能有一个标记为@PostConstruct的方法。
  2. @PostConstruct方法不能有任何参数,并且返回类型应该是void。
  3. 如果类中没有使用Spring注解或者没有被Spring容器管理,则@PostConstruct方法不会被调用。

12. @DependsOn注解

在Spring框架中,@DependsOn注解用于定义Bean之间的依赖关系。它指定一个或多个Bean,这些Bean必须在当前Bean被初始化之前初始化。这个注解主要用于解决Bean初始化的顺序问题,以确保某些Bean在其他Bean之前被创建和初始化。

@DependsOn可以在类级别或方法级别使用。它接受一个或多个Bean名称作为参数,这些Bean名称必须在当前Bean之前初始化。

  1. 慎用@DependsOn: 使用@DependsOn可能导致代码变得复杂,建议仅在必要时使用。
  2. Bean名称: 确保在@DependsOn中指定的Bean名称是正确的,并且这些Bean确实存在。
  3. 依赖环路: 避免创建依赖环路,否则会导致Spring容器初始化失败。

13. @Order

@Order 注解在 Spring 中是控制 bean 初始化顺序的一种便捷方式。通过合理使用 @Order 注解,可以确保在 Spring 容器初始化时,各个 bean 的顺序满足业务逻辑或者依赖关系的要求。

@Order 注解可以用在类级别(标记在类名上)或者方法级别(标记在初始化方法上)。

数值越小,优先级越高,默认的顺序是 Ordered.LOWEST_PRECEDENCE。

14. CommandLineRunner

CommandLineRunner是Spring Boot提供的一个接口,用于在Spring应用程序启动完成后执行一些特定的代码。实现CommandLineRunner接口的bean会在所有的Spring Beans都初始化完成之后自动执行run方法。它通常用于在应用程序启动时执行一些初始化操作、检查或运行脚本。

注意:是spring启动完成之后执行!