首页 > 编程语言 >java面试总结

java面试总结

时间:2023-02-14 18:14:02浏览次数:52  
标签:总结 缓存 java mbd beanName 面试 bean 调用 线程

java基础

为什么java中只有值传递?

java中基本类型是通过copy传递值的,引用类型是通过copy引用传递的,所以java中只有值传递。

java序列化

java不建议使用自带序列化 Serializable。缺点是: 只适用于java,无法跨语言;效率低,生成的字节数组较大;不安全。建议使用kryo、protobuf等,java建议使用kryo

反射

通过反射获取类信息,会跳过jvm安全检查机制。灵活性好。

代理模式

  • 静态代理: 为每一个类生成一个代理类,通过jvm编译成class文件。拓展性不好,每次原始类新增方法时,都需要同步为代理类拓展方法。每个类都要生成一个代理类,增加工作量。
  • 动态代理:在jvm运行期间,自动生成字节码文件创建代理对象。拓展性好、简单。
    • java动态代理:必须要基于接口进行代理。InvocationHandler、Proxy。性能更好。
    • cglib代理:通过为原始类生成子类的方式实现代理。MethodInterceptor、Enhancer

bigdecimal

java原生浮点数float、double计算可能存在精度的丢失。浮点数小数部分转二进制是通过*2取整数位来实现的。会出现无限循环。直接运算可能出现精度丢失。

使用bigdecimal不能使用equals方法比较适否相等,equals会比较精度,1.0和1.00是不想等的。需要通过.compareTo比较。不能通过new BigDecimal(float/double)来创建。通过.valueOf()创建。

Unsafe类

  • 内存操作:allocateMemory分配内存,setMemory设置内存值,copyMemory复制内存,freeMemory释放内存
  • 内存屏障:loadFence、storeFence、fullFence、解决指令重排序问题。
  • cas: cas是cpu原语cmpxchg命令,比较并交换 compareAndSwap,参数包含(object, offset, expected, update)。
  • class操作:类加载,静态变量操作。
  • 锁: park挂起线程,unpark恢复线程。
  • 对象操作:获取对象内字段偏移量,对象内字段内容修改,对象实例化。
  • 数组操作:定位数组偏移量。
  • 系统信息:指针大小,内存大小。

SPI机制

java实现的服务发现机制。META-INF/services/目录下,接口全路径名文件,内容为实现类全路径名。

通过ServiceLoader来加载实现类,存在并发问题。

语法糖

在jvm层面解语法糖,将语法糖变成普通的实现。for-each、基本类型自动装箱拆箱、try-with-resources、匿名内部类、lambda、泛型(java是假泛型,存在泛型擦除)、switch字符串,字符串计算hash,还是基于整数的。

集合

  • List:有序,可重复

    • ArrayList: 底层结构为数组。
      • 通过new ArrayList()创建集合对象时,数组是空的,并没有实际分配内存,只有添加第一个元素的时候,才会分配数组大小,默认数组大小为10,超过此大小时,将会触发1.5倍扩容。最大容量为Integer.MAX。只有新容量大于Integer.MAX-8且最小需要的容量大于Integer.MAX-8才会给这么大。
    • Vector:旧版本集合,不在使用,线程安全,性能差,数组实现。
    • LinkedList: 底层结构为链表。

    List统一使用ArrayList。

  • Set:无序(遍历顺序与插入顺序不一致,按照hash值排序),不可重复

    • HashSet: 底层结构为hashmap。
    • LinkedHashSet:LinkedHashMap。
    • TreeSet:TreeMap
  • Queue:队列,先进先出,有序可重复

    • ArrayQueue:基于数组的双端队列
    • PriorityQueue:
    • BlockingQueue: 阻塞队列
  • Map:键值对

    • HashMap:1.8:数组+链表+红黑树,1.7:数组+链表。

      • 为了方便通过hash值计算位置,所以数组长度必须为2的幂次方。同时为了方便扩容(当发生扩容时,要么就是在原位置,要么就是在原位置+原数组长度的位置)。
      • 当链表长度大于8时,会触发转红黑树,转红黑树之前会查看数组是否大于64,不够的话会触发扩容。当树的大小小于6时,树会退化为链表。
      • 当前大小大于阈值(capacity*负载因子默认0.75)时,会触发扩容。
    • LinkedHashMap:hashMap+双向链表,将key通过链表关联,保证顺序

    • Hashtable:数组+链表,线程安全,性能差,方法是synchronized

    • TreeMap:有序map,支持自定义排序,线程不安全,红黑树。

    • ConcurrentHashMap:

      • 1.7

        • 存储结构:segment数组+hashentry数组+链表。

        • 初始化:segment数组默认大小为16且设置后不允许发生变化(不会扩容,允许创建时指定大小,最大1<<16)。初始化segment[0]节点,大小为2,

      • 1.8

        • 存储结构:数组+链表+红黑树。

          初始化: 在put第一个元素的时候触发初始化。cas锁初始化entry数组,cas向桶中放入第一个节点,synchronized锁定数组中的某一个头节点,向链表中存放数据。synchronized性能:锁升级过程== 偏向锁、锁撤销、轻量级锁、重量级锁。

IO

  • 输入流inputstream/输出流outputstream:

    • file、buffer等都属于包装模式、inputstreamreader,outputstreamwriter适配器。
  • io模型:同步阻塞,同步非阻塞,多路复用,信号驱动,异步io,主要是针对网络io模型

    • bio:同步阻塞io,客户端read阻塞,等待内核写数据

    • nio:同步非阻塞,客户端read不阻塞,没有数据就返回-1,不会一致等待内核写数据,但是如果read读到数据的话,仍然是阻塞的,其他客户端必须要等待当前客户端read结束才能执行。通过轮询的方式来调用read方法,判断有没有数据,如果大量链接都没有数据的话,还是会轮询,浪费cpu。

    • 多路复用: 多路复用其实用多种实现方式,select、poll、epoll。nio的缺陷是通过循环调用read判断是否有数据,每次read都是一次系统调用,存在用户态内核态的切换,而且如果cpu利用率不高(如果大量链接都都没有数据的话,那么这其实是一次无效请求)。

    • select\poll: select和poll将这个轮询的操作放在了内核中处理,只进行一次系统调用,由内核来告诉程序哪儿些连接有数据可以读。缺点是每次都要把所有的fd传递给内核,然后内核遍历所有的fd,返回可读的fd,仍然存在遍历。select可以传递的fd数量有限,poll取消了这个限制,并做了一些优化,但是整体区别不大。

    • epoll:epoll在内核中开辟了两块内存区域,一块是一个红黑树,用来存放注册的fd文件,一块是一个链表,用来存放就绪的连接。通过epoll_create来创建这两块内存空间。在有连接到达时通过epoll_ctl命令来将fd注册到红黑树上,当某个fd有事件发生时,通过中断回调,会讲这个fd加入到链表中,这样当调用epoll_wait时,就会将所有就绪的fd列表返回给服务端。

    中断:cpu分为硬中断和软中断,硬中断就是cpu时钟中断,cpu快速响应硬中断,主要处理硬件相关的操作。软中断是为了防止cpu中断程序处理时间过长,cpu在硬中断后,将操作交给内核线程来处理。

多线程

  • 进程和线程: 进程是操作系统分配资源的最小单位,线程是进程内分配资源的最小单位。
  • 并发和并行: 并发是指单个cpu内多个线程交替执行,并行是指多个cpu下线程一起执行。
  • 同步和异步:同步是指线程在某一操作下阻塞等待操作的结果返回,才能执行后面的操作;异步是指执行某一操作后不需要等待操作的结果就可以继续执行后续操作。
  • 为什么使用多线程:为了压榨cpu资源,避免cpu空闲。同时也为了提高系统的吞吐量。使用多线程可能带来以下问题:死锁、线程安全问题(数据不一致)、内存泄漏。
  • 线程生命周期:
    • new:new Thread(),但是并没有调用start方法
    • ready:调用了start方法,等待获取cpu资源;或者是线程运行时间片结束重新回到ready状态,或者是等待线程被notify唤醒,进入ready状态等待获取cpu资源。ready状态就是表示获取到cpu时间片前的状态。
    • running:获取到了cpu资源,正在运行。一般会把ready和running统一称为running状态。
    • waiting:调用了wait方法,进入等待状态。
    • time_waiting:调用了sleep(timeout),wait(timeout)方法时进入超时等待状态。超时后自动唤醒。wait会释放锁,sleep不会释放锁。
    • blocking:等待获取锁资源。
    • Terminated:线程任务执行结束,退出。
  • 上下文切换:多线程在同一个cpu下并发执行时,由于cpu是按照时间片来执行线程的,每次时间片结束,切换线程时,都需要保存当前线程的状态信息、运行条件等(上下文),以便下次再次执行此线程时恢复现场。同时cpu需要获取下一个要执行的线程的上下文。
  • 死锁:线程a占用a资源,线程b占用b资源,线程a想要获取b资源,线程b想要获取a资源。就发生了死锁。避免死锁的方式有:
    • 按照顺序获取资源,每个线程都要先获取a资源,才能获取b资源。
    • 一次性获取所有资源
    • 添加超时时间,当等待资源超时释放占有的资源。
  • sleep和wait的区别:
    • sleep不释放锁,wait释放锁。
    • sleep通过Thread调用,wait通过锁定的资源对象调用
    • sleep超时会自动唤醒,wait需要通过notify唤醒,wait(timeout)通过可以超时自动唤醒。
    • sleep用于线程暂停,wait通过线程间通信。
  • 乐观锁和悲观锁
    • 乐观锁认为共享资源被多线程操作时都是安全的,不会产生问题,在提交时校验操作是否成功,失败就重试。悲观锁认为所有的操作都可能产生问题,同一时刻只能有一个线程可以操作共享资源。
    • 乐观锁适用于读多的情况(乐观锁一般会通过循环来不停的进行重试,直到成功,如果写多的话,那么就会不停的重试,占用cpu资源)。悲观锁适用于写多的情况,使用独占锁。
    • 乐观锁通过加版本号的方式来实现或者通过cas来实现,在使用cas操作时可能会存在aba问题。
  • volatile: volatile关键字主要有两个作用:防止指令冲排序,保证线程间资源的可见性。主要是通过内存屏障来实现的。
    • 指令冲排序:现代cpu为了提高效率,指令是存在并行执行的,当两个指令之间没有依赖关系,那么就可能发生指令重排序,(在单线程下即使发生指令重排序也不会有问题,因为指令重排序的前提就是不会对结果产生影响)但是在多线程下可能会出现问题。
    • 共享资源可见性:在java内存模型中,每个线程都是有自己的虚拟机栈的,线程内创建的变量资源等默认都是分配在栈空间的。但是对于共享资源,资源是分配在堆内存上的,多线程想要操作变量时都会先将数据复制一份到自己的栈空间,然后在栈空间内对数据进行操作,操作完成后再将数据写入到堆内存中。那么在多线程情况下,同一份数据在多个线程内都有自己的副本,各自对副本的修改是没办法顺序同步的,也就是说线程a并不知道线程b已经修改了数据,那么线程a修改的其实是一个无效的数据。通过volatile关键字修饰的变量,在每次读数据时都会到主内存中去读取,而不是访问栈内的副本
    • 内存屏障:内存屏障是操作系统提供的保证缓存一致的功能。主要有两个指令load、store。load代表从主内存读数据到cpu缓存中,store代表将数据写会主内存。两个命令组合就形成了内存屏障。组合有loadstore、storeload、loadload、storestore。
    • volatile不能保证原子性:volatile只能保证每次都到主内存中读取数据,读到的数据是最新的,但是并不能保证写入的顺序性,不能保证在写会内存前,没有其他线程修改资源信息。
  • synchronized:同步,保证线程有序执行。可以保证共享资源的原子性、可见性。
    • synchronized通过加锁的方式,保证同一时刻只能有一个线程操作共享资源。同步代码块synchronized(对象),是通过monitorenter指令来加锁,锁信息存放在对象头markword中,通过monitorexit来释放锁。synchronized锁方法时,会为方法添加ACC_SYNCHRONIZED标识,标记次方法为同步方法,执行时,也是通过为对象加锁实现的。
    • synchronized锁升级:synchronized在早期性能很差,后来进行了优化,出现了锁升级。锁针对对象来操作的,锁的状态变化为:无锁-> 偏向锁-> 轻量级锁-> 重量级锁。主要是通过markword的后2为来标识锁状态的。01无锁、偏向锁,00轻量级锁,10重量级锁。当偏向锁升级为轻量级锁时会发生锁撤销,也就是当前拥有偏向锁的线程会失去锁,重新开始竞争锁。锁只能升级,不能降级,轻量级锁是通过cas来获取锁,循环重试获取锁,当循环次数超过10次,或者等待线程数大于10,那么就会升级为重量级锁,重量级锁性能很差。
    • 偏向锁撤销:偏向锁撤销需要等待全局安全点,所有线程暂停,这时候查看当前持有偏向锁的线程是否存活同时是否正在执行同步代码块,如果满足以上条件,那么偏向锁升级cas锁,仍然是此线程持有锁。
  • AQS(abstractQueuedSynchronized):aqs核心思想就是被请求的共享资源空闲,那么请求资源的线程直接获取资源,同时资源状态改为锁定状态。如果请求的资源处于锁定状态,那么就需要一套阻塞线程等待、唤醒、锁分配机制,同时需要一个队列(CLH)维护等待的线程。
    • aqs通过clh队列维护阻塞等待线程,并不存在真正的队列,而是通过节点间的链表关系来关联。
    • aqs通过为state字段赋值来锁定资源,state为0代表资源空闲,state资源被锁定,则+1,重入锁就再+1,释放锁就要-1。当state重新变为0时,资源被释放。
  • threadlocal:为每个线程创建自己的私有变量,其他线程无法访问,保证线程安全。
    • 再thread对象中存在两个变量,threadLocals、inhritableThreadLocals。是threadLocalMap类型的。ThreadLocalMap是一个只有数组维护的Map接口实现,key就是threadlocal对象本身,被使用弱引用包裹了,value就是线程的私有变量值。
    • 在第一次调用threadlocal的set方法时,会判断线程的threadlocals是否被初始化,如果没有则初始化一个map,然后保存值,如果已经存在,那么通过hash算法确定位置,然后放入对应的位置。
    • threadLocalMap只有数组,并没有链表,如果发生hash冲突时,就从hash位置向后找,找到第一个为空的位置,或者key为空的节点,插入此位置。
    • 因为threadlocalmap的key为弱引用,因此当threadlocal被回收时,失去了强引用后,此key就可能被回收,就会发生内存泄漏。所以需要显示调用remove清除key。
  • Atomic原子类:底层cas。
  • 线程池:
    • 线程池创建参数:
      • corePoolSize: 核心线程数
      • maxPoolSize:最大线程数
      • workQueue:工作队列
      • keepAliveTime:超时时间
      • timeUnit: 超时时间单位
      • threadFactory: 线程工厂
      • rejectedExecutionHandler: 拒绝策略
    • 线程池内部变量:
      • ctl: 一个int类型数字,高3位代表线程池状态, 低29位代表线程池大小。
      • workers:一个hashset集合,线程池实例。存放线程集合的地方
    • 线程池工作流程:
      1. 查看当前线程池大小是否大于核心线程数,如果小于则创建线程执行。否则向下。
      2. 判断当前阻塞队列是否已满,如果不满则将任务放入阻塞队列,否则向下。
      3. 判断当前线程数量是否大于最大线程数,如果小于创建新的线程执行,否则执行拒绝策略。

jvm

java内存区域

  • 堆:线程共享区域,所有的对象创建后都在这里。
  • 方法区:线程共享区域,存放类信息、方法信息、字段信息、常量、静态变量等信息。1.8后被移到元空间,常量池被移到堆中。
  • 线程栈:线程私有
    • 虚拟机栈:线程栈内java程序运行时使用
    • 本地方法栈:跨语言调用时使用
    • 程序计数器:记录下一行要执行的命令
  • 元空间:1.8之后的永久代,方法区的内容。

jvm垃圾回收

  • 堆内存布局:

    • 年轻代:
      • eden: 对象优先分配在此区域,当此区域内存不够时,会发起一次gc,如果清理后的剩余空间还是不够放,则对象直接进入老年代。
      • survivor0(s0):eden发起gc时,会将存活的对象放入此区域,同时s1内的对象也会放入此区域,同时将s1清空。
      • survivor1(s1):s0和s1交替使用,发生gc时,如果s0为空,就将s1的对象放入s0,将s1清空;如果s1为空,就讲s0的数据放入s1,将s0清空。每次s0->s1的数据复制,都会将对象的年龄+1,当满足条件时就会将此区域的数据放入老年代
    • 老年代:存放大对象、长期存活对象的地方。
  • 对象死亡判断方法:

    • 引用计数法:对对象引用进行计数,如果计数为0,代表没有引用指向此对象,对象死亡,可以回收。无法解决循环引用的问题。
    • 根可达算法:从一个根节点往下找,所有可以找到的对象都是存活的对象,否则都是死亡对象,可以被回收。
      • 虚拟机栈:
      • 本地方法栈:
      • 方法区常量、静态变量:
      • 同步锁持有的对象:
  • 垃圾收集算法:

    • 标记-清除:先标记可回收对象,然后清除此对象。会导致内存碎片。
    • 标记-复制:将内存分为相等的两部分,同时只使用一半内存,回收时将存活的对象放入空的另一半内存。然后清除当前内存。内存利用率不高。
    • 标记-整理:将所有存活的对象向一端移动,然后清除剩余的内存。没有内存碎片。同时内存利用率还高。
  • 垃圾收集器:

    • serial:单线程回收器,回收速度慢。年轻代回收器。

    • serial old: 单线程老年代回收器。

    • parallel scavenge: 多线程回收器,年轻代。多线程只是在标记阶段是并发标记。

    • parallel old:多线程老年代回收器。

    • parallel new:parallel scavenge的优化版本,年轻代,主要是为了配合cms使用。

    • cms:多线程并发标记清除算法,主要分为4部:

      • 初始标记:暂停所有线程,记录所有与根节点相连的对象。
      • 并发标记:用户线程和gc线程同时执行,从根节点向下找所有可达对象
      • 重新标记:gc线程查找并发标记阶段引用发生的变化
      • 并发清除:用户线程和cpu线程同时执行。

      cms垃圾收集器的缺点是:

      1. 并发清除会导致大量内存碎片。
      2. 无法处理浮动垃圾,只能等待下次垃圾回收。
    • G1: g1垃圾回收器采用分治算法来处理的。从整体上看属于标记-整理算法,从局部上看属于标记-复制算法。通过将一整块内存分割成一个一个的region,最大分为2048个region,每个分区最大是32MB。在g1中没有固定的年轻代和老年代划分,每个region都可以是年轻代/老年代。在发生gc时,将一个region内的存活对象复制到另一个region中,并标记此region的分代信息,就可以将之前的整个region清空了。

      • 初始标记
      • 并发标记
      • 最终标记
      • 筛选回收

类文件详情

  • 一个类文件结构主要包含如下内容:
    1. 类文件包含4位的标志位,例如java的是CAFEBABE。
    2. minor version:小版本号
    3. major version:大版本号
    4. 常量池
    5. 访问控制符
    6. 当前类、父类
    7. 接口列表
    8. 字段列表
    9. 方法列表
    10. 属性列表
  • 类加载过程:
    • 加载:加载类文件内容,转成class结构
    • 链接
      • 校验:校验类文件标志位是否合法,类文件内容是否合法
      • 准备:将static修饰的变量赋值为默认值,将final修饰的变量赋值,
      • 解析:替换引用
    • 初始化
  • 类加载器:双亲委派模型。

spring框架

@Transactional注解原理

spring事物分为两种

  • 编程式事物:通过编码的方式来实现事物,此方式对业务有入侵。编程式事物就是通过在代码中注入TransactionManager

        @Autowired
        private PlatformTransactionManager platformTransactionManager;
    
        public void test() {
            // 定义事物属性信息,事物传播机制、事物隔离级别、事物超时时间
            DefaultTransactionDefinition td = new DefaultTransactionDefinition();
            // 设置传播机制
            td.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
            // 设置隔离级别
            td.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
            // 设置事物超时时间
            td.setTimeout(-1);
    
            TransactionStatus ts = platformTransactionManager.getTransaction(td);
            try {
                // 业务代码
                // ......
                platformTransactionManager.commit(ts);
            } catch (Exception e) {
                platformTransactionManager.rollback(ts);
            }
    
        }
    
  • 声明式事物:通过注解的方式来引入事物,简单,对业务无入侵。声明式事物其实就是对业务代码做了层代理,在代理类中注入事物管理器,并管理事务的定义,提交,回滚等。代理类内的内容与上面硬编码的方式差不多。详见TransactionInterceptor#invoke方法

spring bean生命周期(bean定义实例化过程)

主要代码如下:AbstractAutowireCapableBeanFactory#createBean。建议看过下面流程再回过头来看代码

	protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

   ...
		// 5. 类加载阶段
		Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
		if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
			mbdToUse = new RootBeanDefinition(mbd);
			mbdToUse.setBeanClass(resolvedClass);
		}
    ...
    // 6.1  处理调用 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation方法,给一个机会再初始阶段就返回一个用户实现的代理类。
		try {
			Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
			if (bean != null) {
				return bean;
			}
		}
		// 6.2 实例化bean
			Object beanInstance = doCreateBean(beanName, mbdToUse, args);
		...
	}	

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {
    ...
		if (instanceWrapper == null) {
      // 6.2 实例化bean
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		final Object bean = instanceWrapper.getWrappedInstance();
		Class<?> beanType = instanceWrapper.getWrappedClass();
		if (beanType != NullBean.class) {
			mbd.resolvedTargetType = beanType;
		}

    // 6.3 最后一步,bean合并阶段
		synchronized (mbd.postProcessingLock) {
			if (!mbd.postProcessed) {
				try {
					applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"Post-processing of merged bean definition failed", ex);
				}
				mbd.postProcessed = true;
			}
		}

		// 6. 将bean创建的bean加入3级缓存
		if (earlySingletonExposure) {
			...
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}
    ... 
		Object exposedObject = bean;
		try {
      // 7 属性赋值阶段
			populateBean(beanName, mbd, instanceWrapper);
      // 8 bean初始化阶段
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
    ....
		return exposedObject;
	}

// 6.2 下面这个方法全部都是实例化bean阶段的调用
	protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {

		// 6.2 返回一个指定的构造器:实现了 SmartInstantiationAwareBeanPostProcessor
		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
		if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
      // 在此方法内通过反射的方式创建bean实例
			return autowireConstructor(beanName, mbd, ctors, args);
		}
		....
	}

//7 属性赋值阶段
	protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
		
    // 7.1 bean实例化后执行,调用InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation方法
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof InstantiationAwareBeanPostProcessor) {
					InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
					if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
						return;
					}
				}
			}
		}
    .......
      // 处理自动装配的字段属性值
      int resolvedAutowireMode = mbd.getResolvedAutowireMode();
		if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
			MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
			// Add property values based on autowire by name if applicable.
			if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
				autowireByName(beanName, mbd, bw, newPvs);
			}
			// Add property values based on autowire by type if applicable.
			if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
				autowireByType(beanName, mbd, bw, newPvs);
			}
			pvs = newPvs;
		}
    .......
		// 7.2 属性赋值前,调用InstantiationAwareBeanPostProcessor#postProcessProperties 修改属性信息
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof InstantiationAwareBeanPostProcessor) {
					InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
					PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
					if (pvsToUse == null) {
						if (filteredPds == null) {
							filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
						}
						pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
						if (pvsToUse == null) {
							return;
						}
					}
					pvs = pvsToUse;
				}
			}
		}
		.....
		// 7.3 属性赋值
			applyPropertyValues(beanName, mbd, bw, pvs);
		}
	}
// 8初始化阶段
	protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
		// 8.1 执行BeanFactoryAware等接口
			invokeAwareMethods(beanName, bean);
		}

		Object wrappedBean = bean;
		if (mbd == null || !mbd.isSynthetic()) {
      // 8.2 执行初始化前调用 BeanPostProcessor#postProcessBeforeInitialization
			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
		}

		try {
      // 8.3 执行初始化方法
			invokeInitMethods(beanName, wrappedBean, mbd);
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					(mbd != null ? mbd.getResourceDescription() : null),
					beanName, "Invocation of init method failed", ex);
		}
		if (mbd == null || !mbd.isSynthetic()) {
      // 8.4 执行初始化后调用 BeanPostProcessor#postProcessAfterInitialization
			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
		}

		return wrappedBean;
	}
  1. bean定义阶段。可以通过注解、xml、properties等方式来定义bean信息。

  2. bean解析阶段。通过读取bean定义阶段的文件,解析bean定义信息,创建beanDefinition对象。

  3. bean注册阶段。将第2阶段解析创建的beanDefinition对象注册进spring容器。BeanDefinitionRegistry

  4. bean合并阶段。初始定义的bean信息可能并不完整,例如存在父类的情况,此阶段将父bean定义信息合并过来。

  5. bean类加载阶段。找到bean定义时指定的类路径,加载类信息。

  6. bean实例化阶段。调用InstantiationAwareBeanPostProcessor实现。

    • 实例化前:BeanPostProcessor#postProcessBeforeInstantiation
    • 实例化:doCreateBean方法内,createBeanInstance方法。此阶段如果有实现SmartInstantiationAwareBeanPostProcessor类,那么将会调用此类的实例化方法。SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors返回一个bean的构造器。然后对其使用反射初始化:BeanUtils#instantiateClass
    • bean合并阶段:如果有实现MergedBeanDefinitionPostProcessor,那么将会调用此方法最后一次对bean定义信息作修改,在此操作之后,如果满足条件会将实例化的bean加入3级缓存
  7. bean属性赋值阶段。见AbstractAutowireCapableBeanFactory#populateBean

    • 实例化后:InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation

    • bean属性赋值前阶段:调用InstantiationAwareBeanPostProcessor#postProcessProperties方法。在为bean实例初始属性赋值前对属性值进行修改,和下面的方法功能一样,下面的已经被弃用。

    • 调用InstantiationAwareBeanPostProcessor#postProcessPropertyValues方法。已弃用。为了兼容,会在上面方法返回null时调用此方法。

    • bean属性赋值阶段:通过反射为bean属性进行赋值。

  8. bean初始化阶段。通过BeanPostProcessor接口实现

    • 调用部分实现了Aware接口的方法。invokeAwareMethods(),调用BeanNameAware、BeanClassLoaderAware、BeanFactoryAware对应的方法

    • bean初始化前。调用所有的BeanPostProcessor#postProcessBeforeInitialization方法。

    • bean初始化。

      • 调用PostConstruct注解标注的初始化方法。PostConstruct注解的处理是在BeanPostProcessor

        方法中实现的,因此实际调用方法的时机应该是在postProcessBeforeInitialization中。从功能上定义到这里了,所以它在下面所有初始化方法前调用。具体实现见:InitDestroyAnnotationBeanPostProcessor

      • 如果实现了InitializingBean则调用对应的afterPropertiesSet方法。

      • 如果指定了init方法。@Bean(initMethod="xxx")则通过反射调用对应方法。

    • bean初始化后。调用所有的BeanPostProcessor#postProcessAfterInitialization方法。

  9. 所有单例bean初始化完成。调用所有实现了SmartInitializingSingleton接口的afterSingletonInstantiated方法。

  10. bean使用阶段。

  11. bean销毁阶段。

spring 3级缓存原理

spring的3级缓存代码写在:DefaultSingletonBeanRegistry类中。

	/** 一级缓存 */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** 三级缓存,存放创建bean的函数式接口 */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	/** 二级缓存 */
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

3级缓存主要是用来解决单例bean循环依赖的问题的。spring容器中bean对象的创建是通过调用AbstractBeanFactory#doGetBean方法实现的。跟着源码一层一层向下看(建议可以先看下面的处理流程再回过头来看代码):

	protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    .....
		// 获取单例bean
		Object sharedInstance = getSingleton(beanName);
    ....
     // 从1、2、3级缓存都没有获取到,则创建类
				if (mbd.isSingleton()) {
          // 注意这里调用的是getSingleton(beanName, ()-> createBean())。 源码放在下面了
					sharedInstance = getSingleton(beanName, () -> {
						try {
              // 创建bean,可以参照上面的bean生命周期
							return createBean(beanName, mbd, args);
						}
		...
  }

	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 从一级缓存获取bean
		Object singletonObject = this.singletonObjects.get(beanName);
    // 如果一级缓存不存在
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
        // 从二级缓存找bean
				singletonObject = this.earlySingletonObjects.get(beanName);
        // 二级缓存也不存在,而且允许循环依赖(默认是允许的)
				if (singletonObject == null && allowEarlyReference) {
          // 到3级缓存找创建bean的函数式接口
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
            // 通过函数式接口创建一个bean,并将此bean放入2级缓存,并从3级缓存中移除此bean的工厂类
						singletonObject = singletonFactory.getObject();
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}
  
	public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		....
		synchronized (this.singletonObjects) {
      // 先到一级缓存找
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
				... 
          // 一级缓存没找到,调用工厂方法创建bean实例(此时bean实例在3级缓存)
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
				...
				if (newSingleton) {
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}
  
  protected void addSingleton(String beanName, Object singletonObject) {
		synchronized (this.singletonObjects) {
      // 将bean加入一级缓存
			this.singletonObjects.put(beanName, singletonObject);
      // 从3级缓存删除
			this.singletonFactories.remove(beanName);
      // 从2级缓存删除
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}                                 

那么当发生循环引用时,例如类A依赖类B,类B依赖类A。假设此时先创建类A的bean。流程如下:

  1. 先到一级缓存查找类A的bean,找不到
  2. 到二级缓存找类A的bean,找不到
  3. 到3级缓存找创建类A的工厂方法。如果还是找不到,那么就会直接返回null了。
  4. 3层缓存都找不到类A的bean,调用createBean方法创建类A的bean,并将其加入3级缓存。
  5. 类A创建成功,然后beanA字段赋值,会发现需要注入字段类B的bean。那么先从1级缓存中找类B的bean,找不到。
  6. 到二级缓存中找类B的bean,仍然找不到
  7. 到3级缓存中找类B的工厂方法,如果仍然找不到,则返回null。
  8. 然后调用createBean方法创建类B的实例,并将其放入3级缓存。
  9. 初始化beanB,发现需要依赖A的实例,
  10. 然后beanB字段赋值,发现需要注入字段A。会调用getBean方法查找A的bean,然后从1、2、3级缓存找beanA,最终从3级缓存找到了beanA。然后将beanA从3级缓存移动到2级缓存,并从3级缓存移除。
  11. beanB初始化,创建完成。将bean B加入一级缓存,并从2、3级缓存中移除,并返回beanB到A的赋值阶段。
  12. A继续初始化,此时字段B已经可以从一级缓存中拿到了, 然后A初始化完成,将A加入一级缓存,并从2、3级缓存中移除。

spring aop原理

springaop自动装配底层是通过AnnotationAwareAspectJAutoProxyCreator类来实现的。而这个类又实现了SmartInstantiationAwareBeanPostProcessor接口,因此类的代理动作是在实例化阶段实现的。主要是通过下面这个方法来实现的。

	default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
		return bean;
	}

AnnotationAwareAspectJAutoProxyCreator的父类AbstractAutoProxyCreator重写了这个方法:

	@Override
	public Object getEarlyBeanReference(Object bean, String beanName) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		this.earlyProxyReferences.put(cacheKey, bean);
		return wrapIfNecessary(bean, beanName, cacheKey);
	}

通过上面的bean生命周期,我们知道在bean实例化的时候,会将一个创建bean的函数式接口放入三级缓存。如果没有被aop代理,那么返回的就是初始化时创建的对象,但是如果类被代理的话,那么这里就会返回一个代理类。

	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
		....
		// Create proxy if we have advice.
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
      // 创建代理类并返回
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

未完待续。。。。

标签:总结,缓存,java,mbd,beanName,面试,bean,调用,线程
From: https://www.cnblogs.com/Zs-book1/p/17120466.html

相关文章

  • java面试题(七)
    1.21说一说hashCode()和equals()的关系参考答案hashCode()用于获取哈希码(散列码),eauqls()用于比较两个对象是否相等,它们应遵守如下规定:如果两个对象相等,则它们必须有相同的......
  • javascript 高级编程系列 - Web Workers
    Webworkders的规范让javascript在后台运行脱离了UI线程,从而解决了大量计算阻塞UI线程导致卡死的问题。在Webworkers没有出现之前,我们可以使用window.setTimeout异步方......
  • Java 查找Panel 里的某个组件 比如 按钮
    遇到到一个需求,需要获取界面里的一个按钮,但是这个按钮是封装的父类嵌入的,知道label的值。 写了一个递归获取它1privateJButtonLookupTheButton(Componentcontai......
  • Java入门
    Java特性与优势简单性面向对象可移植性(跨屏台)高性能分布式动态性(反射)多线程安全性健壮性Java的三大版本JavaSE:标准版(桌面程序、控制台......
  • 开学测试总结
    新学期的开学测试已经过去了,但是成绩却不甚理想。很多功能都没有成功做出来。究其根本,应当是我在寒假时懈怠了。寒假期间未曾学习新知识,复习老知识。经过这次测试,我理解......
  • 【开发宝典】Java并发系列教程(四)
    作者:京东零售刘跃明Monitor概念Java对象的内存布局对象除了我们自定义的一些属性外,还有其它数据,在内存中可以分为三个区域:对象头、实例数据、对齐填充,这三个区域组成起来才......
  • HTML5+CSS3(六)-全面详解(学习总结---从入门到深化)
    目录​​CSS简介​​​​ CSS概念​​​​为什么需要CSS​​​​CSS和HTML之间的关系​​​​ 语法​​​​学习效果反馈​​​​ CSS的引入方式​​​​ 内联样式(行内......
  • java深拷贝和浅拷贝介绍
    浅拷贝概念  深拷贝概念@Data@Slf4jpublicclassSheepimplementsCloneable{privateStringname;privateintage;privateStringcolor;privateShe......
  • jvm优化base java GC垃圾回收
    OutOfMemoryError错误Java堆内存的OutOfMemoryError异常是实际应用中常见的内存溢出异常情况。当出现Java堆内存溢出时,异常堆栈信息“java.lang.OutOfMemoryError”会跟着......
  • Java 之 invokedynamic
    简述Addanewbytecode,invokedynamic,thatsupportsefficientandflexibleexecutionofmethodinvocationsintheabsenceofstatictypeinformation.--JSR......