java 八股
基础
Exception和Error有什么区别?
他们都继承Throwable类。程序可以处理Exception,但最好不要处理Error。Exception有checked Exception和Unchecked Exception。checked要求一定被程序捕获,不然过不了编译。Unchecked可以按需要捕获
finally 中的代码一定会执行吗?
如果没有外部因素等的话,是的。外部因素指的是停电、程序关闭等
泛型的作用
把类型错误暴漏在编译期,转成字节码后都是Object或指定的父类
序列化和反序列化
序列化指的就是把一个类对象或是一个数据结构转化为字节流形式。一般应用的时候不会java自带的序列化方法,用kryo等协议
serialVersionUID 有什么作用?
这个ID是一个静态变量,帮助反序列化的时候检查反序列化之后的类和原类是否是同一个。这个ID本身不会被序列化,只是被识别和储存
如果有些字段不想进行序列化怎么办?
用transient修饰,反序列化后变为默认值
反射
允许在程序运行的过程中动态找到一个实例的属性或方法
反射的场景了解么
比如Spring、mybatis的动态代理,注解的实现。
谈谈反射机制的优缺点
更加灵活,但也增加了风险和运行成本
ClassLoader
JVM需要一些手段来将静态的字节码中的内容加载到内存中使用,这个手段就是ClassLoader
ClassLoader是一个抽象类,下面提到的类都实现了ClassLoader
他主要有三个实现:BootStrapClassLoader,ExtClassLoader和AppClassLoader
- BootStrapClassLoader:他是由c++写出来的(所以一个类如果是被他加载的话,getClassLoader会得到null返回值)。负责加载jdk的一些核心类,比如lang包,util包中的类
- ExtClassLoader:他是java的类了,负责加载jdk中的一些扩展类,比如lib下的ext包中的类,这个在后面就不用了,在jdk17中试发现变成了PlatformClassLoader
- AppClassLoader:他也是java的类,负责加载用户自己写的类,比如我们src下的类
- 除此之外,我们还可以通过实现ClassLoader类来定义一些类加载器,这种方法在一些框架中比较常用,比如Tomcat中就自己定义了几个类加载器负责加载不同部分的包
ClassLoader有一个所谓的双亲委派模型,说白了就是在加载一个类的时候,先看看有没有顶层的类被加载了,如果被加载了不再加载。这样的好处是可以避免重复加载类,因为一个类如果被两个不同的ClassLoader加载到jvm中,jvm会将他识别为两个不同的类而不是相同的类。同时也可以避免核心API被篡改,比如有攻击者如果也写了一个SPring类并插入到项目文件中,我们用的时候用的依然是BootStrapClassLoader加载的正确的Spring类而不是攻击者的。
获取 Class 对象的四种方式
class.forName。直接getClass。直接.class。通过ClassLoader加载
注解
注解是继承了Annotation接口的一个特殊接口,他们的属性其实是无参数方法,可以有默认值。他可以修饰一个类,方法或属性。某个目标被修饰后,他的字节码中就携带了这些元数据,在编译或运行时这个属性可以被检测到并使用。比如@Override就会在编译时检查他是否合法的继承了父类的方法;Spring中@Component等注解,Spring在加载容器的时候如果发现某个类被注解,就会把他加载到容器中成为一个Bean。
代理
代理有静态代理和动态代理两种。静态代理是开发者手动新建代理类,这种代理已经不常用了。现在更常用的是动态代理。动态代理的效果就是程序运行时用一个代理对象来代替真实对象执行某些方法。动态代理有jdk动态代理和CGLIB动态代理。应用场景有Spring的aop、mybatis代理执行某些mysql语句等等。jdk代理需要指定ClassLoader、接口和invocationHandler。他实际上是新建了一个实现了指定接口的新类。这个新类在执行方法的时候会执行invoke方法而不是原方法。CGLIB动态代理是实现了一个被代理类的子类,生成这个子类的字节码文件,这个子类覆盖了原方法,所以他不能代理final方法。
BigDecimal
主要是浮点数会有丢失精度的问题,所以用BigDecimal这个类来进行浮点数运算。
SPI
方法的调用方定义接口规则,由实现方去实现(API是实现方实现好方法,然后调用方去使用)。比如java在实现日志的时候,就是对外提供了一个spi,然后由Log4j等框架实现对应的方法,完成日志输出
讲讲lambda表达式
java的一个语法糖,用于简化匿名内部类的书写。本质上他在编译后由jvm动态生成一个函数式接口的实现类。代码更简洁的同时,大多数时间性能比匿名内部类好(不再生成一个新的字节码文件了)
并发编程
如何创建线程?
都要新建Thread对象。要么新建一个对象继承Thread类,要么重写Runnable接口,要么重写Callable接口。重写接口后把接口的实现类的对象作为参数传给Thread类新建对象。(这一步可以用匿名内部类或者lambda表达式简化)。或者新建线程池
说说线程的生命周期和状态?
java的线程周期:
- new Thread对象被新建但还没调用start
- Runnable java线程不再区分running和Ready,统一称为Runnable
- wait 被调用wait方法或join方法后 只能被主动唤醒
- time_wait 超时等待,除了被notify唤醒之外还能在超时后被自动唤醒
- blocked 被阻塞,一定要获的锁才能被唤醒
- Terminated 执行完run方法之后
Thread#sleep() 方法和 Object#wait() 方法对比
sleep没有释放锁,wait释放了锁;sleep在超时后自动被唤醒,wait只能被主动唤醒;
wait在Object中的原因是他要释放锁,而锁锁住的实际上是对象。sleep不牵扯到锁的释放
可以直接调用 Thread 类的 run 方法吗?
可以,但是run方法会在主线程中被执行,而不是新建一个新线程去执行、注意,一个Thread对象可以调用多次run,但只能调用一次start
什么是线程死锁?
死锁的四个条件:互斥,占有且等待,不可抢占,循环等待
如何检测死锁?
Jconsole有可视化页面
虚拟线程和平台线程有什么关系?
虚拟线程是jdk实现的线程,跟操作系统关系不大。多个虚拟线程可以对应同一个平台线程,平台线程和内核线程在java中是一对一的,内核线程和CPU物理线程是一对多的。
虚拟线程有什么优点和缺点?
简化异步编程,减少CPU开销。缺点是可能和一些第三方库不兼容
ThreadLocal
每个Threa对象会维护一个自己的ThreadLocalMap,这个Map是线程私有的,以ThreadLocal作为键。在使用的时候直接ThreadLocal.set就可以把一个值设置为这个变量私有的。由于ThreadLocal是弱引用,gc的时候可能ThreadLocal被回收而value不被回收,导致内存泄漏,所以使用完之后需要手动Remove。
join方法
比如在线程A中调用线程B的join方法,那么线程A会被阻塞进入waiting状态,直到线程B的run方法执行完毕,线程A继续执行
notifyAll方法
会唤醒所有被当前在当前对象上进入wait和time_wait的线程