&&第一课 Java平台
Java是解释执行吗?
--不准确,Javac编译成字节码属于解释执行,但Hotspot的JVM提供了JIT编译器,能在运行时将热点代码编译成机器码,这情况下热点代码属于编译执行.
###
对于笼统的问题,要尽量表现出自己的思维深入并系统化,要避免让面试官觉得你是个"知其然不知所以然"的人.
开放性问题,考察的是多方面的,很多面试者都会在这种问题上吃亏,不知从何说起,就给了简略的回答.
###
Java平台的特性
--语言特性(面向对象,范型,Lambda)
--基础类库(集合,IO/NIO,网络,并发)
--JVM概念和机制
--类的加载机制(Bootstrap,Application和Extension ClassLoader)
--类的记载过程(加载,验证,链接,初始化)
--自定义ClassLoader
--垃圾回收原理
--常见垃圾收集器(SerialGC, ParallelGC, CMS, G1)
--JDK中的工具(编译器,运行时环境,安全工具,诊断和监控工具)
--Java运行机制
--解析执行:.class文件经过JVM内嵌的解析器解析执行
--编译执行:JIT,把经常运行的代码作为热点代码,编译成本地平台的机器码
--AOT编译:直接将所有代码编译成机器码
&&第二课 Exception和Error有什么区别?
--都继承自Throwable
--是Java对不同异常的分类,Exception是可预料的意外情况,且应该被捕获并进行处理;Error会导致程序处于非正常状态,不可以恢复.
--Exception分为checked和unchecked,checked必须显式地进行捕获,这也是编译期检查的一部分;unchecked就是运行时异常,如NullPointerException,通常需要根据具体情况判断是否需要捕获.
--Throw early, catch late原则
--第一时间抛出异常
--捕获到异常后,可以选择保留原有cause信息,直接再跑出去或构建新的异常抛出;在更高层面,因为有了清晰的业务逻辑,往往更清楚合适的处理方式.
--自定义异常两点考虑
--是否需要定义成checked exception,这种类型设计的初衷是为了从异常恢复
--在保证诊断信息足够的同时,也要考虑避免包含敏感信息,比如机器名,IP,端口等,类似的情况在日志中也有,如用户数据一般不输出到日志中.
--异常结构图
--Throwable
--Error
--LinkageError
--NoClassDefFoundError
--UnsatisfiedLinkError
--ExceptionInInitializerError
--VirtualMachineError
--OutOfMemoryError
--StackOverflowError
--Exception
--IOException(checked)
--RuntimeException(unchacked)
--NullPonterException
--ClassCastException
--SecurityException
--异常处理带来的性能影响
--try-catch会产生额外性能开销,所以最好仅捕获有必要的代码段;利用异常控制代码流程远比通常意义上的条件语句要低效
--Java每实例化一个Exception,都会对当前的栈进行快照,这是比较重的操作.
&&第三课 final、finally、 finalize有什么不同?
--final可用来修饰类,方法,变量
--finally保证重点代码一定被执行的一种机制,可以用try-finally或者try-catch-finally
--finalize是Object的方法,设计的目的是保证对象在被垃圾收集前完成特定资源的回收,JDK9已经标记为deprecated
--Java7 try-with-resources
##
try {
System.exit(1);
} finally {
System.out.println("Print from finally");
}
##
--final的语义,逻辑意图
--final不等于immutable
##
final List<String> strList = new ArrayList<>(); //只是表明strList这个引用不可以被赋值
strList.add("Hello");
strList.add("world");
List<String> unmodifiableStrList = List.of("Hello", "world");
unmodifiableStrList.add("error");
##
--如何实现immutable类
--将class声明为final
--将所有成员变量定义为private final,而且不要实现setter
--构造对象时,成员变量使用深度拷贝来初始化,而不是直接赋值
--如果确实需要实现getter,或者其他可能会返回内部状态的方法,使用copy-on-write原则,创建私有的copy
--finalize的问题
--掩盖了资源回收时的出错信息
--Throwable被生吞了
##
private void runFinalizer(JavaLangAccess jla) {
...
try {
Object finalizee = this.get();
if(finalizee != null && !(finalizee instanceof java.lang.Enum)) {
jla.invokeFinalize(finalizee);
finalizee = null;
}
} catch (Throwable x) {}
super.clear();
}
##
--替换finalize的机制
--JDK Cleaner
--幻象引用PhantomReference
第四课 强引用、软引用、弱引用、幻象引用有什么区别?
--主要体现在对象不同的可达性状态和对垃圾收集器的影响
--'Strong' Reference
--只要有强引用指向一个对象,就表明对象还活着,GC就不会回收这种对象
--SoftReference
--可以让对象豁免一些垃圾收集
--只有当JVM认为内存不足时,才会试图回收软引用指向的对象;JVM会确保在抛出OOM之前,清理软引用指向的对象.
--通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,否者,就清楚一部分缓存.
--WeakReference
--并不能使对象豁免垃圾收集
--仅仅提供一种在弱引用状态下访问对象的途径,这就可以用来构建一种没有特定约束的关系
--如果试图获取时对象还在,就使用它,否则重新实例化.
--也是很多缓存实现的选择
--幻象引用
--不能通过它访问对象
--仅仅提供了一种确保对象被finalize以后,做某些事情的机制,比如post-mortem清理机制
--也可以用来监控对象的创建和销毁
--对象的可达性状态
--引用队列ReferenceQueue
--诊断JVM引用情况
第六课 动态代理是基于什么原理?
--实现动态代理的方式有很多:JDK自身提供的动态代理,利用反射机制;ASM, cglib, Javassist,基于字节码操作.
--AccessibleObject.setAccessible(boolean flag)
--绕过API访问控制
--动态代理
--JDK Proxy
--最小化依赖关系
--平滑进行JDK升级
--代码实现简单
--cglib框架
--应用场景
--RPC
--AOP
--弥补了OOP对于跨越不同对象或类的分散,纠缠,逻辑表现不足等问题,比如在不同模块的特定阶段做一些事情,类似日志,用户鉴权,全局性异常处理,性能监控,事务处理等
--好处
--通过代理可以让调用者与实现者之间解藕.比如进行RPC调用,框架内部的寻址,序列化,反序列化等,对于调用者往往没有太大意义,通过代理,可以提供更加友善的界面.
第八课 对比Vector, ArrayList, LinkedList有什么区别?
--都是有序集合
--都提供迭代器以遍历内容
--Vecor,线程安全的动态数组,扩容时增加1倍
--ArrayList,非线程安全,扩容时增加50%
--LinkedList,双向链表,非线程安全
--排序算法
--内部排序 or 外部排序
--稳定 or 非稳定
三大类集合
--List
--有序集合,方便访问,插入,删除
--Set
--不允许重复,保证了元素唯一性
--无序
--Queue/Deque
--支持FIFO,或LIFO
--BlockingQueue,属于并发包
--TreeSet是以TreeMap实现的
--HashSet是以HashMap实现的
--适合场景
--TreeSet支持自然顺序访问,但是添加,删除,包含等操作需要log(n)
--HashSet,如果hash正常,可以提供常数时间的添加,删除,包含操作,但不保证有序
--LinkedHashSet,内部构建了一个记录插入顺序的双向链表,因此提供了按照插入顺序遍历的能力,同时也保证了常数时间的添加,删除,包含等操作
--这些集合类都不是线程安全的,对于concurrent里面的线程安全容器,Collections工具提供了一系列的synchronized方法
--原理:简单粗暴.将每个操作方法通过synchronized添加同步支持
--List list = Collections.synchronezedList(new ArrayList());
--对于小数据集的创建,Java9提供了一系列静态工厂方法
第9讲 | 对比Hashtable、HashMap、TreeMap有什么不同?
--都是Map实现,key-value存储
--Hashtable,是同步的,不支持null
--HashMap,不同步,支持null
--TreeMap,基于红黑树,顺序访问,get/put/remove都是O(log(n)),具体的顺序由Comparator决定
--HashMap 的性能表现非常依赖于哈希码的有效性,请务必掌握hashCode和equals的约定
--equals相等,hashCode一定要相等
--重写了hashCode,也要重写equals
--hashCode需要保持一致性,状态改变返回的哈希值要一致
--LinkedHashMap和TreeMap的区别?
--LinkedHashMap提供的是遍历顺序符合插入顺序,通过为条目(key-value对)维护双向链表
--TreeMap的整体顺序是由key的顺序关系决定的,通过Comparator或Comparable来决定
--HashMap源码
--容量
--负载系数
--树化
--内部结构
--Node<K,V>[] table,通过hashCode决定key-value对在数组的寻址
--链表,hashCode相同的key-value对,以链表形式存储
--resize
--创建初始数组
--在容量不满足需求时,进行扩容
--key-value对在哈希表中的位置(index)
--i=(n-1)&hash
--并不是key本身的hashCode,而是来自于HashMap中的一个hash方法
--将高位数据移位到低位进行异或运算,因为有些数据计算出的hashCode差异在高位,而HashMap里的哈希寻址是忽略容量以上的高位的,简而言之,就是避免哈希碰撞.
static final int hash(Object kye) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>>16;
}
--扩容性能开销的来源:扩容后需要将老的数组中的元素放置到新的数组中
--负载因子*容量 > 元素数量
--预先设置的容量需要满足,1.预估数量/负载因子, 2.是2的幂
--最好不超过3/4,过低会频繁扩容,过高会增加冲突
--树化
第10讲 | 如何保证集合是线程安全的? ConcurrentHashMap如何实现高效地线程安全?
--Java提供了不同层面的线程安全支持
--早期的有Hashtable
--同步包装器
--缺点,粗粒度;性能低
--并发包提供了线程安全容器
--各种并发容器
--ConcurrentHashMap, CopyOnWriteArrayList
--各种线程安全队列
--ArrayBlockingQueue, SynchronousQueue
--各种有序容器的线程安全版本
第11讲 | Java提供了哪些IO方式? NIO如何实现多路复用?
--synchronous/asynchronous
--同步:后续任务等待当前调用返回,才会进行下一步
--异步:其他任务不需要等待当前调用返回,通常依靠事件,回调等机制来实现任务间次序关系
--blocking/non-blocking
--阻塞:在进行阻塞操作时,当前线程会处于阻塞状态,无法从事其他任务,只有当条件就绪才能继续
--非阻塞:不管IO操作是否结束,直接返回,相应操作在后台继续处理
--InputStream/OutputStream
--Reader/Writer
--BufferedOutputStream
--Closable
--File
--RandomAccessFile
--InputStream
--FilterInputStream
--BufferedInputStream
--ByteArrayInputStream
--ObjectInputStream
--PipedInputStream
--OutputStream
--FilterOutputStream
--BufferedOutputStream
--ByteArrayOutputStream
--ObjectOutputStream
--PipedOutputStream
--Reader
--InputStreamReader
--FileReader
--BufferedReader
--PipedReader
--Writer
--OutputStreamWriter
--FileWriter
--BufferedWriter
--PipedWriter
第12讲 | Java有几种文件拷贝方式?哪一种最高效?
--拷贝实现机制
-IO
--输入输出流进行读写,实际上进行了多次上下文切换
--应用读取数据:先在内核态将数据从磁盘读取到内核缓存,再切换到用户态将数据从内核缓存读取到用户缓存
-NIO
--零拷贝
--数据传输不需要用户态参与,省去了上下文切换和内存拷贝的开销
第15讲 | synchronized和ReentrantLock有什么区别呢?
--synchronized是Java内建的同步机制,提供了互斥的语义和可见性
--ReentrantLock,能实现synchronized无法做到的细节控制,比如faireness
--线程安全
--多线程环境下正确性,保证多线程环境下共享的,可修改的状态的正确性
--两个方法
--封装,将对象内部状态隐藏保护起来
--不可变,final和immutable
--几个基本特征
--原子性
--相关操作不会中途被其他线程干扰,一般通过同步机制实现
--可见性
--是一个线程修改了某个共享变量,其状态能够立即被其他线程知晓
--通常被解释为将线程本地状态反映到内存上,volatile就是负责保证可见性的
--有序性
--保证线程内串行语义,避免指令重排
第16讲 | synchronized底层如何实现?什么是锁的升级、降级?
--由一对monitorenter/monitorexit指令实现的,Monitor对象是同步的基本实现单元
--三种不同的Monitor
--偏斜锁 Biased Locking
--轻量级锁
--重量级锁
--当JVM检测到不同的竞争状况时,会自动切换到适合的锁的切换,这种切换就是锁的升级,降级
--synchronized是JVM内部的Intrinsic Lock,所以偏斜锁,轻量级锁,重量级锁的代码实现并不在核心类库,而是在JVM代码中.