Java核心技术36讲知识点总结大纲
   
    
    
    1 Java平台的理解
   
    
   
Java的特性,解释运行和编译运行
    
    
    2 Exception 和 Error 有什么区别
   
- 
     理解Java的异常体系的设计,
 
 Throwable
 
 ,
 
 Exception
 
 ,
 
 Error
 
 的关系
- 
     理解
 
 ClassNotFoundException
 
 与
 
 NoClassDefFoundError
 
 的区别
- 
     遵循
 
 Throw early, catch late
 
 原则
    
    
    3 final、finally、finalize 有什么不同
   
从安全、性能、垃圾收集方面考虑
    
    
    4 强引用、软引用、弱引用、幻象引用有什么区别
   
对象的可达性状态:强引用 -> 软引用 -> 弱引用 -(finalize)->幻想引用
    
   
- 对象可达性状态的流转
- 引用队列(ReferenceQueue)的使用
- 诊断 JVM 引用情况
- Reachability Fence 通过底层 API 来达到强引用的效果
    
    
    5 String、StringBuffer、StringBuilder有什么区别
   
- 字符串的设计与实现考量
- 字符串缓存 intern()方法,由永久代移到堆中。
- String 的演化,Java 9 中底层把 char 数组换成了 byte 数组,占用更少的空间
- getBytes()方法指定字符编码
    
    
    6 动态代理是基于什么原理
   
- 反射原理
- 动态代理分类:JDK动态代理原理,cglib动态代理原理
- 优缺点,适用场景
    
    
    7 int和Integer有什么区别
   
- 基本类型和包装类型
- 包装类型的缓存
- 适应场景,性能影响
    
    
    8 对比Vector、ArrayList、LinkedList有何区别
   
    
   
- Collection 各种类的实现原理,使用场景
- List 的各种实现,Set 的各种实现,Queue的各种实现
- 理解Java提供的各种排序算法 Collections.sort() Arrays.sort()
    
    
    9 对比Hashtable、HashMap、TreeMap有什么不同
   
    
   
- HashMap 实现原理,源码分析
- Java 8 HashMap 中的树化
- 容量和负载因子
- 解决哈希冲突的方法
    
    
    10 如何保证集合是线程安全的? ConcurrentHashMap如何实现高效地线程安全
   
- HashTable 低效的加锁方式
- Collections 提供的同步包装器
- ConcurrentHashMap 的设计原理:
早期(Java 7)实现原理
- 分离锁 Segment 对数组进行分段锁定,基于 ReetrantLock
- HashEntry内部使用 volatile 保证可见性
- 一个 Segment 可以对应多个桶,先定位到Segment 再进行二次哈希定位具体链表
- size() 方法获取大小时会进行2次重试,不保证一定准确
Java 8 和之后版本 ConcurrentHashMap 变化:
- 总体结构类似于 HashMap,锁的粒度更细
- 
     初始化利用volatile的
 
 sizeCtl
 
 检测是否发生竞争,若竞争则等待,然后CAS初始化
- 
     使用 CAS 操作进行无锁并发操作,key哈希得到桶的位置,为空进行CAS加新节点,否则使用
 
 synchronized
 
 加锁,链表达到数量之后转换红黑树
- size() 方法利用 LongAdd 进行累计
    
    
    11 Java提供了哪些IO方式? NIO如何实现多路复用
   
- 传统的IO类库结构
- NIO 的基本组成和多路复用模型,Buffer,Channel,Selector
- Socket IO 中 传统实现方式与 NIO 的区别
- NIO 2 的基本组成
- 区分同步和异步,阻塞和非阻塞
    
    
    12 Java有几种文件拷贝方式?哪一种最高效
   
- 使用 java.io 包下的XXXStream 拷贝 和 使用 NIO 中的FileChannel的 transferTo()
- 理解用户态和内核态
- Files.copy 标准库的底层使用的还是用户态的拷贝
- NIO 中 Direct Buffer 堆外内存和垃圾收集(只有FullGC 时收集),使用场景
- 
     高效文件拷贝的原则:
- 使用缓存,减少IO次数
- transferTo 等机制,减少上下文切换和额外IO
- 尽量减少不必要的转换,编解码,对象序列化之类
 
    
    
    13 谈谈接口和抽象类有什么区别
   
- 接口和抽象类的设计原则
- 面向对象基本要素:抽象,封装,继承,多态
- 
     面向对象的S.O.L.I.D原则:
- 单一职责
- 开关原则
- 里氏替换
- 迪米特法则
- 接口分离
- 依赖反转
 
    
    
    14 谈谈你知道的设计模式
   
设计模式分类:
- 
创建型模式—对对象创建过程的各种问题和解决方案的总结: 工厂模式、单例模式、构建器模式、原型模式 
- 
结构型模式—针对软件设计结构的总结,关注于类、对象继承、组合方式的实践: 桥接模式、适配器模式、装饰者模式、代理模式、组合模式、外观模式、享元模式等 
- 
行为型模式—从类或对象之间交互、职责划分等角度总结的模式: 策略模式、解释器模式、命令模式、观察者模式、迭代器模式、模板方法模式、访问者模式 
通过实例学习和理解设计模式
Spring 中常见的设计模式:
- BeanFactory 和 Application 应用工厂模式
- Bean 的创建过程中,提供了单例和原型模式
- AOP 中的代理模式,装饰器模式,适配器模式
- 各种事件监听器,观察者模式
- 类似JdbcTemplate 的模板模式
    
    
    15 synchronized 和 ReentrantLock 有什么区别
   
- 理解线程安全,保证线程安全有哪些方案
- 理解synchronized 和 ReentrantLock的机制和基本使用
- 线程安全的特性:原子性,可见性,有序性
- ReentrantLock的特点:公平锁,可重入锁,手动释放,超时等待,响应中断等
- Condition 的应用:ArrayBlockingQueue 源码分析 lock 和 condition的作用
    
    
    16 synchronized底层如何实现?什么是锁的升级、降级
   
- synchronized 代码块由 monitorenter/monitorexit 指令实现,Monitor对象是同步的基本单元
- Java 6之后 Monitor 的实现三种不同的锁:偏斜锁(BiasedLocking)、轻量级锁和重量级锁。升级降级就是三种不同锁的转换
- 没有竞争时使用偏向锁,JVM利用CAS在对象头的MarkWord设置当前线程ID
- 当有线程竞争时,需要撤销revoke偏向锁,利用CAS获取锁,成功则用轻量级锁,失败升级为重量级锁
    
   
- 不同的锁类型:
    
   
- 读写锁,StampedLock
    
    
    17 一个线程两次调用start()方法会出现什么情况
   
- 
理解线程的生命周期状态:java.lang.Thread.State: - NEW
- RUNNABLE
- BLOCKED :阻塞,线程被挂起,无法进入临界区
- WAITING :进入临界区后,发现某条件未达到而等待
- TIMED_WAIT :进入临界区后,基于超时的等待
- TERMINATED
 
- 
线程是什么,Java底层的实现方式: 操作系统调度的最小单元,Java线程映射到OS内核线程 
- 
线程状态的切换 
    
   
    
    
    18 什么情况下Java程序会产生死锁?如何定位、修复
   
- 
写一个死锁程序 
- 
诊断死锁的工具: - jstack
- 
       API:重量级操作
 
 ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
 
 Q:分布式环境可以用API实现吗 
    
    
    19 Java并发包提供了哪些并发工具类
   
- 
java.util.concurrent 包下各类基本分类,使用 把握主要的组成部分: 
- 
     各种同步结构
- CountDownLatch 无法重用,countDown/await ,基于事件
- CyclicBarrier 可以重用,所有线程await() 之后自动重置,基于线程
- Semaphore
- Phase 与 CountDownLatch 类似,允许线程动态注册
 
- 各种线程安全容器
- 各种并发队列的实现和场景
- Executor框架
- 
典型使用场景 车站出租车每次装5个人才能出发,再放入5个人进入 
- 
并发容器 
    
   
    
    
    20 并发包中的ConcurrentLinkedQueue和LinkedBlockingQueue有什么区别
   
    
   
    
     ArrayBlockingQueue
    
    : 有界队列,一把锁,注意 add, offer , put 区别
   
    
     SynchronousQueue
    
    : 容量为0,每个操作都要等待
   
    
     PriorityBlockingQueue
    
    : 无界优先队列
   
    
     LinkedBlockingQueue
    
    : 没有设置容量时为无界队列,头尾2把锁
   
    
     ConcurrentLinkedQueue
    
    : 基于CAS无锁的并发队列,性能更好,可以提供更高吞吐量
   
    
    
    21 Java并发类库提供的线程池有哪几种? 分别有什么特点
   
- 
 Executors
 
 工具类,
 
 ThreadPoolExecutor
 
 线程池核心类
- 
Executors 提供的线程池: - newCachedThreadPool():
 public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }- 1
- 2
- 3
- 4
- 5
 可根据实际情况调正线程池大小,需要注意的是线程数没有限制,空闲的线程缓存60秒 - newFixedThreadPool(int nThreads)
 public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }- 1
- 2
- 3
- 4
- 5
 创建固定大小的线程池,工作队列使用 
 
 LinkedBlockingQueue
 
 ,使用时注意任务的堆积影响- newSingleThreadExecutor()
 public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }- 1
- 2
- 3
- 4
- 5
- 6
 大小为1的线程池,同样要注意等待任务的堆积影响 - newSingleThreadScheduledExecutor(): 线程池大小为1,可以做定时任务
- newScheduledThreadPool(int corePoolSize):可以指定线程数的定时任务线程池
- newWorkStealingPool(int parallelism):Java 8新增,构建ForkJoinPool ,并行处理任务
 
    一般建议使用
    
     ThreadPoolExecutor
    
    创建语义明确的线程池
   
    
    
    22 AtomicInteger底层实现原理是什么?如何在自己的产品代码中应用CAS操作?
   
- AtomicXXX 的实现原理:CAS原理
- 
     CAS 有什么问题:
- ABA问题,解决方案:版本控制:AtomicStampedReference
- 自旋次数(CAS的设计前提是竞争时短暂的)
 
- 如果要使用CAS操作,可以使用 AtomicLongFieldUpdater 包装自己的数据
- 
     理解
 
 AQS(AbstractQueuedSynchronizer)
 
 的原理,简单拆分:- 表征状态的 volatile变量:state
- 一个FIFO 的等待线程队列,处理线程竞争和等待
- 各种基于CAS的基础操作方法,和等待实现的 tryAcquire 方法和 tryRelease 方法
 
- 
     以
 
 ReentrantLock
 
 为例理解 AQS 作为基础实现的作用
    public final void acquire(int arg) {
            if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                selfInterrupt();
        }
 
 
 
 - 1
- 2
- 3
- 4
- 5
 理解 tryAcquire 和 acquireQueued 方法的实现
    
    
    23 请介绍类加载过程,什么是双亲委派模型
   
- Java 的类加载过程主要三个步骤:加载、链接(验证、准备、解析)、初始化
- 理解JVM各阶段做了什么工作
- 类加载器与双亲委派模型
- Java 9 中类加载器发生了哪些变化
- 有方法可以降低类加载开销吗 : AOT、AppCDS
    
    
    24 有哪些方法可以在运行时动态生成一个Java类
   
- 加深理解类加载机制,defineClass 等
- 动态代理的原理,字节码操纵技术还有哪些,ASM, Javassist , cglib
    
    
    25 谈谈JVM内存区域的划分,哪些区域可能发生OutOfMemoryError
   
- JVM 内存划分,参考《深入理解Java虚拟机》
- 程序计数器
- Java虚拟机栈
- 堆
- 方法区
- 运行时常量池
- 本地方法栈
    
   
    注意JDK版本,JDK 7前还有把
    
     方法区
    
    称为
    
     永久代
    
    ,之后由
    
     元数据区
    
    取代,而字符串缓存和静态变量并没有转移到元数据区,是直接分配在了堆上
   
- 常见 OOM 分析
    
    
    26 如何监控和诊断JVM堆内和堆外内存使用
   
- 图形化工具:JConsole , VisualVM
- 命令行工具:jstat , jmap
- GC 日志
- Java Mission Control(JMC)
细化对各部分内存区域的理解,堆内结构的理解,参数调整
参数调整:
- 最大堆体积 -Xmx value
- 初始化最小堆体积 -Xms value
- 老年代和新生代的比例 -XX:NewRatio=value
- 新生代大小比例 -XX:SurviorRatio=value
堆外内存
    
    
    27 Java常见的垃圾收集器有哪些
   
- 主流 Oracle JDK 的GC:
- Serial GC :采用标记-整理(Mark-Compact)算法
- ParNew GC : Serial GC的多线程实现
- CMS (Concurrent Mark Sweep) GC : 基于标记-清除(Mark-Sweep)算法,存在内存碎片问题,设计目标是减少停顿
- Parrallel GC : 吞吐量优先的GC, JDK8 等为server 模式默认GC,新生代老年代并行
- G1 GC : 兼顾吞吐量和停顿时间,JDK 9后默认GC
- 如何判断一个对象是否可以回收?引用计数法和可达性分析
- 
     常见垃圾收集算法:
- 复制算法
- 标记-清除
- 标记-整理
 
- 垃圾收集的基本过程理解
    
    
    28 谈谈你的GC调优思路
   
- 
明确GC 调优的目的:性能角度(内存占用footprint、延时latency、吞吐量throughput),其他方面 
- 
一般调优思路: - 明确需求和问题,确定目标
- 掌握JVM,GC的状态,是否有GC调优的必要
- 应用的特征和 GC的类型是否符合
- 分析确定需要调正的参数或软硬件
- 验证目标是否达到
 
- 
G1 GC 的内部结构和主要机制 内部划分为同等大小的 
 
 region
 
 (数值是在 1M 到 32M 字节之间的一个 2 的幂值数),年代成为逻辑概念,增加了
 
 Humongous
 
 ,G1会将超过 region 50%大小的对象归类为
 
 Humongous
 
 对象,逻辑上属于老年代。- 新生代,G1 采用并行的复制算法,会发生 STW暂停
- 老年代,大部分情况为并发标记,整理发生在新生代GC时捎带进行,而且是增量进行
 查询有关资料学习 
    
    
    29 Java内存模型中的happen-before是什么
   
- 
Java (JMM)内存模型的理解 
- 
先行发生原则(Happen-before) 是Java内存模型中保证多线程操作可见性的机制 
- 
理解 JMM 试图解决什么问题: - 
简化多线程编程,保证程序可移植性 
- 
隔离了不同处理器内存排序的区别 
- 
解决多线程中可见性等问题 
 
- 
- 
JMM内部实现依赖于 
 
 内存屏障
 
 ,通过禁止某些重排序的方式,提供内存可见性保证。
- 
线程安全的原子性,可见性,有序性的理解,同时理解(sychronized,volatile的原理) 
    
    
    30 Java程序运行在Docker等容器环境有哪些新问题
   
(由于本人在docker下使用java比较少,以下只摘录原专栏的主要点)
- 
     早期 JDK 在Docker 环境下容易出现的基础问题:
- 未配置JVm堆,原数据区,直接内存等参数,可能超过容器限制出现OOM被容器kill
- 会错误判断CPU资源,因Docker 限制了 CPU的核数
 
- 容器对计算资源的管理方式是全新的,Docker通过CCroup实现
- 
     解决方案:
- 根据容器设置好JVM的各种参数,包括CPU资源
- 升级到新的JDK 9 ,10
 
    
    
    31 你了解Java应用开发中的注入攻击吗
   
注入攻击的基本特征是程序允许攻击者将不可信的动态内容注入到程序中,并将其执行。
常见注入攻击(Java相关):
- SQL注入
- 操作系统命令注入
- XML注入攻击
    
    
    32 如何写出安全的Java代码
   
从Java语言的角度理解常见的攻击,如哈希碰撞等消耗资源的操作
从设计,开发,测试,部署等不同阶段考虑有哪些安全的策略
    
    
    33 后台服务出现明显“变慢”,谈谈你的诊断思路
   
- 服务器变慢的定义,突然变慢还是长时间,是否重现,“慢”的定义
- 
     理清问题的症状,定位原因:
- 错误日志
- 系统资源等情况的检查
- 监控Java服务自身,GC日志是否异常
 
- 
     业界最广泛的性能分析方法论:
- 自上而下。从应用顶层逐步深入到具体模块
- 自下而上。从类似CPU这种底层往上分析
 
- 
     首先排除功能性错误,然后使用典型的自上而下分析:
- 系统层面关注点:CPU,内存,IO(top,free,iostat等命令)
- JVM层面,利用工具,JMC,JConsole,日志等
 
    
    
    34 有人说“Lambda能让Java程序慢30倍”,你怎么看
   
- 理解基准测试定义的范围和目标
- 微基准测试
- JMH的使用
    
    
    35 JVM优化Java代码时都做了什么
   
- 
     JVM的优化分为:
- 运行时优化(runtime)
- 即时编译器优化(JIT)–将热点代码以方法为单位转换成机器码
 
- 从整体了解Java代码编译、执行的过程
这部分内容建议参考 《深入理解Java虚拟机》及相关资料学习
    
    
    36 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景
   
- 
     MySQL事务隔离级别:
- 读未提交
- 读已提交
- 可重复读
- 串行化
 
- 
     理解
 
 悲观锁
 
 和
 
 乐观锁
 
 的出发点及使用
    
    
    37 谈谈Spring Bean的生命周期和作用域
   
- 
Spring Bean生命周期 - 
初始化参考下图: 
- 
销毁:依次调用 DisposableBean 的 destroy 方法和 Bean 自身定制的destroy 方法 
 
- 
- 
Spring Bean 的作用域: - Singleton
- Prototype
- Request
- Session
- GlobalSession
 
- 
Spring 的基础机制:控制反转(依赖注入),AOP及具体相关概念 
    
    
    38 对比Java标准NIO类库,你知道Netty是如何实现更高性能的吗
   
(Netty 还不太熟悉,以后补上总结)
Netty 在NIO 基础上进行了很多改进:
- Reactor模式实现的线程模型,利用EventLoop 创建新的机制
- Zero-Copy 机制,各种降低内存分配和回收的开销等
- 通信协议,序列化方面的优化
    
    
    39 谈谈常用的分布式ID的设计方案?Snowflake是否受冬令时切换影响
   
- 
分布式ID的定义,基本要求: - 全局唯一,分布式系统中的唯一
- 有序性,通常要求是有序递增
 
- 
业界通常做法: - 
基于数据库自增的实现。扩展性和可靠性存在局限 
- 
基于 Twitter 的 
 
 Snowflake
 
 的实现等,如下64位,通常使用Java中的long存储- 头部是1位的正负标识
- 
         41位的时间戳,通常使用
 
 System.currentTimeMills()
 
- 10位WorkerID :5位数据中心+5位机器ID
- 最后12位是单位毫秒内可生成的序列号数目的理论极限
 
- 
Redis,Zookeeper,MangoDB等中间的解决方案 
- 
其他开源实现 
 
- 
- 
Snowflake 中大都依赖于 
 
 System.currentTimeMills()
 
 与夏/冬时令没有关系,不受影响
- 
分布式ID除了唯一和有序还会有其他要求,如有意义(业务相关),高可用性,紧凑等 
- 
主流方案的优缺点分析 - 数据库自增方案的局限
- 时钟偏斜问题
- 等等
 
 
