欢迎光临散文网 会员登陆 & 注册

Java实战和面试宝典

2023-06-07 16:02 作者:编程小宇e  | 我要投稿

一、基础篇

 1.包装类Boolean是值传递还是引用传递?


代码:


public class TEST {

    public static void main(String[] args) {

        Boolean flag = new Boolean(true);

        setFalse(flag);

        System.out.println(flag);

    }


    private static void setFalse(Boolean flag) {

        flag = new Boolean(false);

    }


}

flag仍旧输出true;

在Java中,所有的参数都是按值传递的,而不是按引用传递的。这意味着你在传递一个对象作为参数时,实际上是将对象的副本传递给了函数。因此,在这个例子中,当你调用setFalse()方法时,它会接收到一个Boolean类型的副本,并将其设置为false。但是原始的flag仍旧保持着原来的值,因为它没有被直接修改。

如果你想在方法中更改原始变量的值,你需要使用一个可变对象类型,例如一个数组或者一个自定义类。你可以将Boolean对象包装在一个数组中,然后将该数组作为参数传递给setFalse()方法。这样就可以修改数组第一个元素,从而更改原始的Boolean值。

结论:当你把整个对象作为引用赋值给参数对象时,实际上只是传递进来的一个拷贝,你修改这个拷贝并不影响这个被传递的引用对象本身,但它如果有内部属性的话,是可以被重新设置的。




 2. HashMap链表转红黑树为什么是大于8?

 HashMap链表转红黑树的操作,以提高查找的速度,红黑树的时间复杂度O(logn),而链表是O(n/2),因此只在O(logn)<O(n/2)时才会进行转换,也就是以8作为分界点。HashMap的链表是双向的,寻找小于链表长度一半的节点从头节点检索,否则从尾节点检索。

3.内存告警,但CPU不高,dump中的也只有30M,我们2核4G的机器上配置了3G的内存

启动指令:

nohup nice java -jar -DfileEncoding=utf-8 -Dserver.port=$RUN_PORT -Xms3000m -Xmx3000m -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+PrintGCDetail -jar ${JAR_FULLNAME}>dev/null 2>&1 &


具体表现:

普罗米修斯显示堆内存占用82%,但是老年代还没达到GC的阈值,所以不能触发FULL GC,

但是我们机器的物理内存确占用大于96%,再下去可能宕机。

进程在申请内存时并不是直接分配物理内存的,而是分配一块虚拟空间,到真正堆这块虚拟空间写入数据时才会通过缺页异常(Page Fault)处理机制分配物理内存,也就是我们看到的进程Res指标。

当我们服务启动时,物理内存时一条上升的线,在不断生成对象的过程中堆内存达到-Xmx,然后物理内存就不会释放了,就会看到一条直线,而且FULL GC不会释放物理内存,因为频繁对内存扩容和缩容很耗费性能。

结论:这个内存使用变化是正常的,只是-Xmx设置的太大了,然后java对象占用的内存达到之后这个值以后,物理内存就下不来了,重新指定-Xmx的值就行了。



 4.MySQL索引最佳实践有哪些?

①最左匹配原则

指的是查询从索引的最左前列开始并且不跳过索引中的列。

联合索引在查询的时候会先去比对第一个字段,第一个字段比对完成之后再去比对第二个字段,以此类推,不根据索引顺序查询无法精确定位到索引树的某个节点,导致无法根据索引树查找。

所以使用了联合索引,查找条件必须要按照联合索引字段出现的顺序排列,否则会导致索引失效。

②不在索引列上做任何操作(计算、函数、自动或者手动类型转换),会导致索引失效而转向全表扫描

SQL中对name字段取了前3位,索引树中没有执行函数之后的值,所以根本走不到索引。

需要考虑使用函数的结果是否可以在索引树中定位,而且还可以保持索引树依然有序。

③存储引擎不能使用索引中范围条件右边的列

SQL走了部分字段,key_len为78是name字段和age字段之和,position字段并没有走索引。

范围后面的字段都不会再走索引。如果是大于等于则能使用到后面的两个字段索引。

④尽量使用覆盖索引(索引列包含查询列),减少select * 语句

查询尽量指明具体的字段,且尽可能被联合索引覆盖,覆盖索引可以避免回表查询

⑤在使用<>,not in, not exists的时候无法使用到索引

当使用>,<,<>这些时,MySQL内部优化器会根据检索比例、表大小等多个因素整体评估是否使用索引

⑥is null,is not null一般情况下也无法使用索引

因为null值比较特殊,在索引树中的字段不好比对。一般建议将字段设置为not null,即便字段没有值,也要设置一个默认值,方便走索引,提高效率。

⑦like以通配符开头,mysql索引失效会变成全表扫描操作

%在前面相当于查询是跳过了前面的部分字符,截取部分去查询,这剩下的部分已经不再有序了,所以不会用到索引。

而%在后面相当于最左前缀,从最开始的字符串匹配,在索引树中的数据还是有序的,所以可以使用索引。

⑧字符串不加单引号索引失效

查询的时候尽量使用相同类型,否则mysql在检测到类型不匹配时,底层可能会强转类型或者使用函数进行转换从而被放弃走索引。

二、框架篇

1.Spring如何解决循环依赖问题?

Spring能解决的循环依赖场景是setter注入的单例Bean,Spring通过三级缓存方案进行解决。

初始化的Bean先放入三级缓存(singletonFactory),填充依赖时从三级缓存中取出并放入二级缓存(early半成品),在此过程中可升级bean(SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference,如AOP中的AbstractAutoProxyCreator),最终完成填充后放入一级缓存(成型的singleton)。

还有一些失效场景:启用@Async+@EnableAsync,A和B相互依赖,但是A中含有@Async修饰的方法,按照缓存解决循环依赖的思路,A初始,填充B,B初始,填充时从三级缓存拿到A,并放到二级缓存(中途在getEarlyBeanReference中可以执行增强操作),最终B完成放入一级缓存,再回到A的填充阶段,一级缓存中拿到了B并填充,最终A填充完毕,执行初始化动作,因为被@Async修饰,因此需要被AOP动态代理,需要用到Bean对象增强处理器(AsyncAnnotationBeanPostProcessor,后置处理器。它只在一处postProcessorAfterInitization()实现了对代理对象的创建,未实现getEarlyBeanReference),最终A被增强了,但是B填充的A是普通对象,因此就失效了。

参考:今天一定要搞清楚Spring怎么解决循环依赖 - 掘金


2.Spring中@Lazy注解加在类上不起作用?

@Lazy是复制阶段的beanPostProcessor做的。

A依赖B期待B延迟初始化,即使B上面加了延迟初始化,它也会进行初始化。因为@Autowire没有增加@Lazy注解去标识这个属性不需要注入,所以会强行注入。

@Lazy注入的是代理对象,只有真正调用它方法时才会创建对象。

很多解决循环依赖的方法,归根到底都是推迟依赖bean的创建。


三、中间件篇

1.Redis预扣库存在并发场景下怎么将库存同步到数据库

可以考虑两种方式:

一是通过定时任务同步,redis预减在并发环境下是可靠的,然后每秒钟同步一下,保证最终一致就行。

二是redis扣减后,发布MQ消息,然后再更新数据库,sql应为set stock = stock - 1,通过行级锁保证数据的准确性。

2.RabbitMQ中防止消息被重复消费,方案:使用redis存储已消费的数据,每次消费时判断redis中是否有该条数据,如果有则不消费,没有则消费,那么当redis宕机了怎么办判断消息是否重复消费呢?

一是在业务侧处理时,业务状态做流转变更,缓存判断后再通过业务状态进行判断。

二是保证消息可查,消息除缓存外做持久化,消息被消费后重新进行标记,这样可以对重复消息或者未消费消息进行最终一致性补偿。



Java实战和面试宝典的评论 (共 条)

分享到微博请遵守国家法律