又是一季 “金三银四” ,你面试了吗?
Q1:String、StringBuffer、StringBuilder 有什么区别?
Tips:首先要整理出 String 的基本特性,Immutable、不可变等特性,其次是后两者与前者的区别,最后是 StringBuffer 与 StringBuilder 之间的区别,从安全和并发的角度去谈
Answer:String 是 Java 中基础类型之一,它提供了我们关于字符串构建和处理的大部分逻辑,本质上在内部就是一个 final 修饰的字符数组,一旦初始化就不在允许修改,所以我们平常的手动字符串拼接其实都是生成了一个新的字符串对象,这个叫字符串的不可变性。StringBuilder 相比于 String,内部的字符数组不再修饰为 final,这也就方便了各种 append 操作,通过动态扩容内部字符数组避免每一次拼接都产生新的 String 对象(动态扩容每次会扩容一倍,至少保证一定时间内不需要重新分配内存),StringBuffer 相对于 StringBuilder,内部所有方法都加上了 synchronized 保证并发安全性,当然牺牲的就是性能。
可能追问:String 为什么设计成不可变的?
1、jvm 中有常量池的概念,比如 String a = "abc",那么 abc 编译的时候会进入常量池,a 引用指向常量池,当我在别的地方定义 String b = "abc",你会发现 a 和 b 指向同一块内存,如果字符串可变,那么 a 变化后,b 就会发现自己的值变化了,这是不合理的
2、多线程操作字符串,每次都是创建新的字符串,不存在并发安全问题
Q2:强引用、软引用、弱引用、虚引用有什么区别?具体使用场景是什么?
Tips:这道面试题,属于既偏门又非常高频的一道题目。说它偏门,是因为在大多数应用开发中,很少直接操作各种不同引用,虽然我们使用的类库、框架可能利用了其机制。它被频繁问到,是因为这是一个综合性的题目,既考察了我们对基础概念的理解,也考察了对底层对象生命周期、垃圾收集机制等的掌握。
Answer:不同的引用类型,主要体现的是对象不同的可达性(reachable)状态和对垃圾收集的影响。这些引用共同的父类是 ReferenceQueue
强引用:我们日常程序中使用的最多的引用,这种引用永远不会被 GC 回收,比如存在一个强引用 a 指向一个数组,那么只要 a 不被赋值 null,也就是不丢失这个强引用,此数组永远不会被回收内存
软引用:当系统发生 GC 时,如果内存仍然不够新对象的分配,会收集掉这部分的引用对象
弱引用:当系统发生 GC 时,不论整理后的内存是否充足,依然会回收掉这部分引用对象
虚引用:虚引用指向的对象,甚至无法再次获取到该引用对象,往往和一个队列共同使用,在引用的对象被回收的时候,GC 会添加该对象到队列中,相当于是告知系统此对象是何时回收掉的
Q3:谈谈 Java 反射机制,动态代理是基于什么原理?
Tips:反射是一个比较底层的机制,涉及到 jvm 内存区,动态代理是需要使用到反射的,两种不同的代理方式之间的区别与联系
Answer:jvm 堆内存中有一块“方法区”,用于存放类的定义元数据,以及类的一些静态字段的值,反射本质上就是根据类的全路径名从方法区读出类的基本定义,动态代理分两种,jdk 自带动态代理实现和 cglib 动态代理:
jdk 动态代理只能为接口做代理,原因是运行时会生成一个类,这个类继承 Proxy,并在构造时传入一个 InvocationHandler 实例保存在父类 Proxy protected字段中用于完成代理逻辑;jdk 这个代理的确定很明显,不能为类做代理只能为接口做代理,
cglib 其实也类似,有一个 MethodInterceptor 用于自定义代理逻辑,只不过 cglib 生成的代理类直接继承被代理类,并基于 fastclass 而不是反射提升调用性能
fastclass 其实就是对类中方法索引化,每一个方法对应一个索引,调用的时候不需要反射,直接根据索引知道调用哪个方法