Java八股汇总

  1. 静态内部类和非静态内部类的区别?

    • 非静态内部类在编译完成后,会隐含地保存着一个引用,该引用指向创建它的外部类。而静态内部类没有,它的创建不依赖于外部类的创建。
    • 对于静态内部类,外部类加载时,内部类并不会被加载进内存,只有真正使用的时候,才会被加载,因此实现了懒加载。
  2. 单例模式的各种实现?

    • 饿汉式:在类加载时就创建一个实例,优点是线程安全。缺点是如果实例一直未被使用,也会一直占用空间。
    • 懒汉式:第一次调用get时才创建实例,优点节省了内存空间,但是需要解决线程安全问题。一般多使用双重检验锁
    • 静态内部类:在内部类中创建实例,可以实现懒加载,同时保证了线程安全。
    • 枚举
  3. ArrayList和LinkedList的区别

    • ArrayList内部基于动态数组实现,会根据实际存储的元素动态地扩容和缩容。LinkedList底层使用双向链表实现。
    • ArrayList支持快速随机访问,LinkedList不支持。
    • ArrayList在初始化时,容量为0,第一次添加元素时,会扩容到10,之后每次满了之后再添加时,会发生1.5倍扩容。即添加第11个元素时触发。有小数时采用向下取整。
  4. LRU怎么实现?为什么要双向链表?能不能是队列?为什么需要hashmap?hashmap在什么情况下会去get?

    • LRU,底层是使用HashMap和双向链表实现。双向链表是为了保证增删时的复杂度为O(1)。
    • 如果采用队列,那么在增删时,需要对元素操作会增多。
    • hashmap是为了实现查询时的复杂度为O(1)。
    • 获取元素或者加入元素时,都会用到get,加入时利用get判断是否存在。
  5. java为什么要重写equals和hashcode,重写时需要注意什么?

    • 默认的equals内部实现是利用==,而==比较的是两个对象的内存地址。因此,这往往不符合需求,需要进行重写。
    • 重写equals时,一定要重写hashcode,因为在使用HashMap或者HashSet时,需要利用hashcode进行映射,我们需要保证equals相同的对象,它们的hashcode也相同。
  6. 操作系统线程和java线程有什么关系?
    java底层会调用pthread_create来创建线程,本质上java程序创建的线程,就是和操作系统线程是一样的,是1对1线程模型。

  7. java不可变的类有哪些?

    • String
    • Integer、Float、Boolean
    • Enum
    • 日期类型。
  8. CAS的作用,解决什么问题?ABA问题

    • CAS全称为“比较并交换”,用于实现乐观锁,用于解决多线程并发时的线程安全问题。
    • ABA问题,是指一个变量V的初始值为A,在准备赋值时检测的时候,它仍然是A,但是不能就此说明,该值未被修改过。
  9. jvm虚拟机调优参数有哪些?
    -Xmx:堆最大值
    -Xms:初始堆大小
    -Xss:设置java线程的栈大小
    -Xmn:设置年轻代的大小

  10. java对象头结构里面有什么内容?

    • Mark word
      • 对象哈希码
      • 锁信息
      • GC信息
    • Class pointer:指向对象的类数据,包括对象所属类的Class对象引用。
  11. 抽象类和接口的区别

    • 抽象类可以包括成员方法的具体实现,而接口只能包括抽象方法。
    • 一个类只能继承一个抽象类,可以继承多个接口。
    • 抽象类是对类整体的抽象,而接口是对行为的抽象。
  12. 线程池:是一种线程复用技术。
    不使用线程池的问题:用户每发起一个请求,后台都需要创建一个新线程来处理,而创建新线程开销很大,并且请求过多,创建的线程也会过多,这样严重影响系统性能。
    优点:提高线程的利用率;加快程序的响应速度;便于统一管理线程对象;可以控制最大并发数

  13. 使用ThreadPoolExecutor构建线程池

    • corePoolSize:核心线程数;
    • maximumPoolSize:最大线程数;
    • keepAliveTime:临时线程存活时间,空闲多久被销毁;
    • unit:存活时间的单位;
    • workQueue:任务队列;
    • threadFactory:线程工厂;
    • handler:拒绝策略。
      任务队列
    • LinkedBlockingQueue:大小不限;
    • ArrayBlockingQueue:可以指定队列大小。
      任务拒绝策略
    • AbortPolicy:默认策略,丢弃任务并抛出异常;
    • DiscardPolicy:丢弃任务;
    • DiscardOldestPolicy:丢弃队列中等待最久的任务,然后把新任务加到队列中;
    • CallerRunsPolicy:由主线程负责调用任务。
      线程池处理任务
    • 处理Runnable任务:execute(Runnable command)
    • 处理Callable任务:submit(Callable<T> task)
    • shutdown:等全部任务执行完,再关闭线程池
    • shutdownNow:立刻关闭线程池。返回未执行的任务列表
  14. 创建线程的方式有哪些?Callable和其他的区别是什么?

    • 通过继承Thread
    • 通过继承Runnable接口
    • 通过继承Callable接口,Callable接口结合FutureTask可以实现获取线程执行的返回值。
  15. HashMap的线程安全问题

    • JDK1.7之前采用头插法,在多线程操作同一个槽位时,可能出现环形链表,此外也可能发生数据覆盖问题。
    • JDK1.8采用尾插法,虽然避免了环形链表,但是仍然存在数据覆盖问题。
  16. HashMap的扩容

    HashMap每次扩容,会增加两倍,扩容因子是0.75,扩容过程中,会创建一个新的数组,将原有的旧值重新hash映射。

  17. Synchronized的原理

    synchronized底层是通过monitorenter和monitorexit来实现的,因为每一个对象都有一个monitor,当线程执行monitorenter时,就会尝试获取对象的monitor,只有获取成功,才能进入执行。而monitor维护一个变量,只有变量为0时,线程才可以获取monitor。同一个线程多次获取时,变量会不断加1。

  18. volatile的原理,如何理解

    volatile的作用

    • 可以保证多线程环境下共享变量的可见性
    • 通过增加内存屏障防止多个指令之间的重排序

    可见性是指当一个线程对共享变量的修改,其他线程可以立即看到修改后的值。可见性问题本质上是由于CPU缓存造成的,CPU缓存虽然解决了CPU和内存速度不一致的问题,但是也带来了缓存一致性问题,在多线程情况下,缓存一致性问题也就带来了可见性问题

    底层实现:
    volatile会发出lock指令,对当前cpu缓存进行上锁,这样就会让当前cpu独占缓存,让其他cpu的此缓存段失效。这样,其他线程在使用该缓存数据时,就会去主存中读取。而此cpu对数据的更改也会及时刷新到主存。
    volatile禁止指令重排的原理——内存屏障——一条CPU指令
    在指令间插入一条内存屏障指令,就是告诉编译器和CPU,任何指令都不能和这条内存屏障指令重排序,因此就实现了内存屏障前后指令的有序性。

  19. 什么是公平锁和非公平锁?

    • 公平锁:多个线程按照申请锁的顺序来获取锁,线程来了之后,直接进入队列中排队。队列中的第一个线程才能获得锁。
      • 优点:每个线程都有机会获得执行。
      • 缺点:整体执行速度更慢,吞吐量小。
    • 非公平锁:多个线程加锁时,先直接尝试获取锁,抢到了就直接占用,抢不到才会进入队尾等待。
      • 优点:整体执行速度块,吞吐量大。
      • 缺点:可能产生饥饿问题。
  20. 为什么非公平锁的吞吐量比公平锁大?

    • 对于公平锁,线程来了之后需要先去队列等待,轮到自己后才会去尝试获取锁。这个过程涉及到线程从运行态转到休眠态,再从休眠态转到运行态。涉及到从用户态转换到内核态,这个转换过程比较慢。
    • 非公平锁,会首先尝试CAS获取锁,获取成功就不用进入等待队列了。
  21. ReentrantLock在公平锁和非公平锁的实现上的区别?

    唯一区别就是在于公平锁获取锁的时候,多了一个限制条件,判断当前等待队列是否为空。非公平锁直接CAS获取,公平锁只有为空是才CAS获取。

  22. 什么是Spring框架?

    Spring是一款开源的Java开发框架,旨在提高开发人员的开发效率以及系统的可维护性。

    Spring内部包括了很多模块,利用这些模块很方便的进行项目开发,两个核心的功能是:IoC和AOP。

  23. Springboot的启动流程

    • 加载启动类
      • 启动类是使用@SpringBootApplication注解标注的类。
      • 扫描启动类所在的包和子包,自动配置相应的Bean.
    • 加载配置文件
      • 默认会从application.propertiesapplication.yaml中加载配置。
    • 创建spring容器
    • 加载自动配置
    • 运行springboot应用程序
  24. Bean的生命周期?

    • 创建Bean
    • 属性赋值
    • 初始化
      • 通过查看Bean是否实现了某些接口,然后回调相应的方法。
      • 例如:BeanNameAware接口,会传入bean的名字
      • BeanClassLoaderAware接口,会传入bean的类加载器
      • BeanPostProcessor
    • 销毁
  25. 介绍一下AOP和IOC

    IOC

    是为了解决代码耦合度高的问题,在IOC思想中,使用对象的时候,程序不要自己通过new的方式创建,而是由外部对象提供。

    也就是说对象的创建权由程序转移到了外部,这种思想叫做控制反转。

    Spring提供了一个IOC容器来充当这个外部,IOC容器负责对象的创建、初始化工作,在容器中的对象称为Bean。

    AOP

    面向切面编程,是一种编程范式,可以用于安全、事务、日志等任务的管理,提高了代码的复用性和管理的便携性。

    作用:在不改变原始代码的情况下,对方法进行功能增强。

    核心概念:

    • 代理:AOP的核心本质采用代理模式实现的。
    • 连接点:任意方法。
    • 切入点:被匹配的连接点。
    • 通知:若干个方法的共性功能,体现为一个方法。
    • 切面:描述通知和切入点的对应关系。
    • 目标对象:被代理的原始对象。
  26. Spring AOP 有代理失效的情况吗?

    • 非Spring管理的对象
    • 同一个Bean对象内部方法之间的调用:aop是基于代理的,只有代理对象才能触发aop拦截。
    • 静态方法:aop只能拦截非静态方法,因为静态方法无法被重写。
    • final方法:final方法不可重写,aop无法生成代理对象。
    • 异步方法:因为异步方法在执行时会创建新线程,aop拦截器无法拦截其他线程中的方法调用。
  27. Springboot常用的启动器

    • spring-boot-starter-web:包含了spring mvc和tomcat嵌入式服务器,用于快速构建web应用程序;
    • mybatis-spring-boot-starter
    • spring-boot-starter-test
  28. Jvm内存区域/jvm内存模型

    可以分为运行时数据区直接内存两部分。

    运行时数据区:java栈、本地方法栈、程序计数器、方法区和java堆。其中java栈、本地方法栈和程序计数器属于线程私有空间。

    • 程序计数器:用来指示当前线程执行的字节码位置,因为线程切换过程中,每个线程都需要记录自己执行的位置,因此程序计数器属于线程私有的。
    • java栈是java方法执行的内存模型,每个方法执行时,都会在java栈中创建一个栈帧,用于存放局部变量表、操作数栈、动态链接和方法出口等信息。
    • java堆:是java内存中最大的一块,是所有线程共享的,用于存储对象实例,字符串常量池就在堆里面。
    • 方法区:jdk1.8改成了元空间,放在本地内存中,用来存放已经被虚拟机加载的类信息、静态变量、常量和JIT编译后的代码等数据。
    • 直接内存:是由java虚拟机直接向操作系统申请的内存空间。适用于申请少,访问频繁的场景。
  29. JVM的内存分配原则

    • 对象优先在eden区分配:Eden空间不足时,发起minor gc;
    • 大对象直接进入老年代:这是一种优化策略,可以减少新生代的gc频率和成本;
    • 长期存活的对象将进入老年代:对象在survivor中每经过一次minor gc,年龄就增加一岁,达到一定年龄,就会被晋升到老年代。
  30. GC的区域

    Partial GC:部分GC

    • young GC/minor GC:当新生代的eden区满时触发;
    • Old GC/major GC:只针对老年代进行垃圾收集。

    Full GC:收集整个java堆和方法区

    • 当准备触发young gc时,如果统计数据发现,young gc的平均晋升大小比目前老年代剩余空间大,则直接触发full gc。
    • 永久代空间不足时,触发full gc;
    • 调用system.gc()。
  31. 为什么full gc的效率比较低?

    因为full gc要回收的区域比较多,包括新生代、老年代和方法区,其中老年代采用的是标记清除法。

  32. 为什么full gc时,用户程序不能运行?

    用户程序运行时,引用的使用状态是动态变化的,因为full gc是要对整个内存空间进行回收,可能会造成用户程序的信息丢失。

  33. gc有哪些算法和收集器?

回收算法

  • 标记-清除法:过程分为标记和清除两个阶段,首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。主要由两个缺点:一个是效率问题,标记和清除过程不够高效,另一个是空间问题,标记清除之后会产生大量不连续的内存碎片。
  • 复制法:针对新生代,缺点是①可用内存变小;②不适合老年代:如果存活对象数量比较大,复制性能会变得很差。
  • 标记-整理法:将所有存活对象移向一端,然后直接清理掉边界以外的内存。
  • 分代收集法:没有什么新思想,只是根据对象的存活周期,将内存分成几块,然后根据不同块的特点应用合适的收集算法。

垃圾收集器

  • serial收集器

    单线程收集器,工作时需要暂停其他所有的工作线程,直到它收集结束。

    新生代采用复制,老年代采用标记-整理。

  • ParNew收集器

    Serial的多线程版本,除了使用多线程外,其他行为和serial完全一样,在多核cpu下有更好的表现。

    多线程是指多个垃圾收集线程,用户线程依然要停止工作。

  • Parallel Scavenge收集器

    多线程收集器,该收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量。

  • Serial old 收集器:serial的老年代版本。

  • parallel old收集器:parallel scavenge的老年代版本。

  • CMS收集器(Concurrent Mark Sweep)

    是一种以获取最短停顿时间的收集器,第一次实现了让垃圾收集线程与用户线程同时工作。

    CMS是一种标记-清除算法的实现。

    运作流程:

    • 初始标记:暂停所有其他线程,并记录下直接与root相连的对象;(停顿)
    • 并发标记:同时开启GC和用户线程,用一个闭包结构去记录可达对象,不过,因为用户线程会不断更新引用,所以不能保证可达性分析的实时性;
    • 重新标记:为了修正并发标记期间,因用户程序运行而产生的标动。(停顿)
    • 并发清除:开启用户线程,同时GC线程开启对未标记的区域做清理。

    优点:并发收集、低停顿。

    缺点:

    • 对cpu资源敏感;
    • 无法处理浮动垃圾;
    • 使用的是标记-清除算法,会产生大量空间碎片。
  • G1收集器

    G1(Garbage-First)是一款可以指定最大停顿时间、基于Region内存布局、按收益动态确定回收的多线程并发收集器。

    在满足GC停顿时间要求的同时,还具备高吞吐量的特征。它从JDK7开始推出,JDK9作为默认的垃圾收集器。

    G1收集器在后台维护一个优先列表,每次根据允许收集的时间,优先回收价值最大的区域,这也是名字的由来。

    G1的垃圾收集包括YoungGC、MixedGC和FullGC。

    运作步骤:

    • 初始标记:只有回收线程运行。(停顿)
    • 并发标记:回收线程和用户线程同时运行。
    • 最终标记:只有回收线程运行。(停顿)
    • 筛选回收:只有回收线程运行。(停顿)

    Region内存布局:

    1. 把连续的java堆划分成多个大小相等的独立区域(Region),默认最多2048个;
    2. Region的大小可以通过参数设置,取值范围是1~32MB;
    3. 每个Region都可以扮演新生代的Eden、Survivor或者老年代空间;
  1. CMS和G1的区别?

    • 使用范围不同
      • CMS是老年代的收集器。
      • G1是老年代和新生代收集器。
    • STW的时间不同:CMS停顿两次;G1停顿三次。
    • 垃圾碎片
      • CMS采用标记-清除算法,会产生内存碎片。
      • G1采用标记-整理,不会产生内存碎片。
    • 垃圾回收过程不同
    • CMS会产生浮动垃圾:CMS在并发清除阶段,是垃圾收集线程和用户线程同时工作的,会产生浮动垃圾。
  2. 什么情况下选择CMS,什么情况下选择G1?

    CMS

    • 低延迟需求
    • 老年代收集
    • 对内存碎片不敏感

    G1

    • 大内存堆:适合管理大内存堆,能够有效处理GB以上的堆内存。
    • 对内存碎片敏感
    • 比较平衡的性能:在停顿时间和吞吐量之间保持平衡的场景。
  3. 常用的集合

    HashMap、HashSet、LinkedHashSet、ArrayList、LinkedList

    线程安全的集合

    • Hashtable:实现了Map接口,利用synchronized关键字进行了加锁。
    • ConcurrentHashMap:JDK1.7采用分段锁,JDK1.8取消了分段锁,采用CAS和synchronized,在简单操作的情况下采用CAS,复杂操作时,采用synchronized,对哈希槽进行加锁(锁的是链表或红黑树的首节点),只要hash不冲突,就不会产生并发。
    • Vector:使用synchronized对方法了加锁。
    • CopyOnWriteArrayList:写时复制,存在写操作时,复制一份副本给写操作,写完后替换原始数据,保证读读共享、读写共享、写写互斥。修改操作时,利用ReentrantLock加锁,避免多线程修改时,复制出多个副本。
    • CopyOnWriteArraySet。
  4. ConcurrentHashMap怎么实现的线程安全?

    • JDK1.7采用锁分段技术,每个ConcurrentHashMap里面包含一个segment数组,每个segment数组里面包括一个HashEntry数组,类似一个小的HashMap。锁分段技术,就是给每段数据配一把锁。
    • JDK1.8时,采用CAS和synchronized来实现。
      • 添加元素时,先判断容器是否为空,如果为空,则使用CAS来进行初始化;
      • 如果不为空,查看添加位置是否有元素,没有则使用CAS来添加;
      • 有则使用synchronized来添加,有元素比较复杂,需要一一比较链表节点。
  5. 并发编程的三种特性

    原子性:一个或多个操作要么全部成功,要么全部失败。(synchronized、各种锁和原子类)

    可见性:一个线程对共享变量的修改,另一个线程立马就可以看到。(synchronized、volatile和各种锁)

    有序性:由于指令重排序问题,代码的执行顺序未必就是编写代码时候的顺序。(volatile)

  6. 为什么会有原子性问题?

    因为多线程并发导致的,本质是因为CPU在执行指令时,是分多阶段完成的,在这些阶段未全部完成之前,如果新的线程操作了该线程的变量,就会导致原子性被破坏。

  7. jvm为什么会打乱我们的指令顺序?

    内存操作速度远慢于CPU运行速度,所以造成了CPU空置,为了提高CPU的利用率,虚拟机会按照自己的一些规则跳过执行慢的代码,优先执行快的,因此就造成了指令乱序。

  8. 字符串常量池

    用来存放所有的String对象,是堆内存中的一段空间。String对象是不可变的,我们创建新的字符串时:

    • 如果字符串常量池中存在,直接返回已有字符串对象的引用;
    • 如果字符串常量池中不存在,先创建一个对象放入常量池中,再将对象引用返回。
  9. 线程池的原理,来了一个任务后的处理流程

    线程池的原理是通过复用固定数量的线程来执行任务,而不是每次都创建新线程。

    • 如果当前运行的线程数小于核心线程数,那么就会新建一个线程来执行任务;(即使有空闲的核心线程也要新建)
    • 如果当前运行的线程数等于或大于核心线程数,但是小于最大线程数,那么就把任务放入任务队列等待执行;
    • 如果任务队列满了,当前运行的线程数小于最大线程数,就会新建一个临时线程执行任务;
    • 如果已经达到最大线程数了,就会执行拒绝策略。
  10. java栈和堆的区别

    • 栈的内存占用是在编译期间就确定了,而堆是动态的在运行时确定的。
    • 栈是线程私有的,而堆是共享空间。
  11. 介绍一下ThreadLocal?

    ThreadLocal提供了线程局部变量,每个线程都有自己独立的变量副本,互不干扰。主要用于解决多线程环境下的变量隔离问题。

    线程池和ThreadLocal一起用会有什么问题吗?(内存泄漏和脏数据)主要问题是内存泄漏,因为线程池中的线程是复用的,ThreadLocal变量不会被回收,可能导致内存泄漏。此外,还可能有脏数据的问题,因为线程复用,ThreadLocal中的数据可能没有及时清理。

  12. keepAliveTime对核心线程是否生效,能否杀死核心线程?

    默认不会回收核心线程,即使它们已经空闲了,这是为了减少创建线程的开销,因为核心线程通常是要长期保持活跃的。

    也可以通过设置allowCoreThreadTimeOut的参数为true,这样子核心线程也可以被回收,回收时间由keepAliveTime控制。

  13. 如何解决哈希碰撞?

    • 使用高质量的哈希函数,使key尽可能分散在可用的位置上;
    • 使用链表处理;
    • 链表过长时,转为红黑树;
    • 动态调整数组的长度。
    • 线性探测
    • 二次探测
  14. 单例模式的实现

    单例模式是一种创建型设计模式,核心思想是保证一个类只有一个实例,并提供一个全局访问点来访问这个实例。

    实现要求:

    • 私有的构造函数:防止外部代码直接创建实例;
    • 私有的静态成员变量:保证类的唯一实例;
    • 公有的静态方法:通过公有的静态变量获取类实例。
  15. CAS及其具体实现

    CAS全称为“Compare and Swap”即比较-替换。是一种实现乐观锁的方式,具体做法是:

    线程在处理一个变量时,会先记录一下该变量的值,后面操作完毕写回去的时候,先判断该变量是否被修改,如果没有,将结果写入,如果已经被修改了,则重新操作。

  16. 跨域问题怎么解决?java层面的跨域问题怎么解决?

    跨域问题是由于浏览器出于安全考虑,指定的同源策略,要求:对同源请求放行,异源请求限制。跨域发生在服务器响应后的浏览器验证阶段。

    域名不同、端口不同、协议不同都会导致跨域问题。

    解决方法:

    • 通过服务器设置允许跨域:在后端服务器上进行配置,允许指定的域名或者ip地址访问接口。例如在controller类上,添加CrossOrigin注解。
    • 使用Nginx代理服务器,跨域是因为浏览器的同源策略,我们可以通过一个没有跨域的代理服务器帮我们请求。
  17. 类加载的过程

    加载、连接、初始化、使用和卸载。连接包括:验证、准备、解析。

    • 加载
      • 通过全类名获取定义此类的二进制字节流;
      • 将字节流所代表的静态存储结构转换为方法区的运行时数据结构;
      • 在内存中生成一个代表该类的Class对象。
    • 验证:确保Class文件的字节流包含的信息符合JVM规范,运行时不会危害JVM的自身安全。
      • 文件格式验证:Class文件格式检查。
      • 元数据验证:字节码语义检查,比如类的继承是否正确。
      • 字节码验证:程序语义检查,比如函数参数是否正确,对象类型转化是否合理。
      • 符号引用验证:类的正确性检查,比如引用对象是否存在。
    • 准备:为类变量分配内存并设置默认值。JDK7之后,类变量和字符串常量池存放在java堆里面。
    • 解析:JVM将常量池内的符号引用替换为直接引用的过程。
    • 初始化:成员变量、静态变量的赋值,静态代码块的执行。
    • 类卸载。

    符号引用是一种编译时的引用,是类的包名和类名,由于编译时还没有创建对象,此时使用符号来表示引用的对象,解析的时候就是将符号引用换成直接引用。

  18. 双亲委派模型

    当一个类加载器收到加载请求时,它会先将请求转发给父类加载器。在父类加载器没有找到所请求的类时,该类加载器才会去尝试自己加载。

    jvm判断两个java类是否相同:不仅仅看类的全类名,还需要看加载此类的类加载器是否一样,只有两者都相同的情况,才认为两个类是相同的。

    双亲委派模型的作用:

    • 保证类的唯一性:通过委派机制,确保所有加载请求都会传递到启动类加载器,避免了不同类加载器重复加载相同类的情况。
    • 保证安全性:java核心库被启动类加载器加载,而启动类加载器只加载信任的类路径中的类,这样可以防止不可信的类假冒核心类。
    • 支持隔离和层次划分:双亲委派模型支持不同层次的类加载器服务于不同的类加载需求,如应用程序类加载器加载用户代码,扩展类加载器加载扩展框架,启动类加载器加载核心库,保证了各个层级类加载器的职责清晰,便于维护。
  19. synchronized和volatile的区别

    • synchronized解决了多线程访问共享资源时,可能出现的线程安全问题。
    • volatile解决了变量在多线程环境下的可见性和指令重排序问题,保证了变量的修改对其他线程是可见的。
    • 当一个变量被volatile修饰时,线程读取该变量时会直接从内存中读取,而不会使用缓存,同时对该变量的修改也会立即刷回内存。
  20. synchronized锁升级过程

    synchronized可以分为无锁、偏向锁、轻量级锁和重量级锁。

    偏向锁:只有一个线程进入同步代码块,不存在竞争时,使用偏向锁;

    轻量级锁:当第二个线程竞争锁时,发现已经被第一个线程获取,那么就会让当前线程进行自旋获取锁,此时锁会升级为轻量级锁。

    重量级锁:如果自旋之后,还是无法获取锁,那么就会在操作系统层面挂起,此时锁升级为重量级锁。

  21. mybatis和mybatis-plus的区别

    • 编码方式:mybatis需要使用大量的xml配置和SQL语句,而mybatis-plus使用注解和API的方式,代码更简洁。
    • 功能:mybatis-plus提供了很多额外功能,比如条件构造器、代码生成器、分页插件等。
  22. java的多态

    多态通过将子类对象赋值给父类引用,从而实现相同方法的不同表现。

    实现条件:

    • 存在继承关系;
    • 子类必须重写父类的方法;
    • 父类引用指向子类对象。
  23. java重载

    同一个类中,多个方法的方法名相同,参数列表不同。

    参数列表不同:参数个数、参数类型、类型次序。

  24. 零拷贝的原理

    零拷贝:是指数据读取和写入过程中,不通过CPU来搬运数据,所有的数据传输都是通过DMA来运输的。零拷贝相较于传统的文件传输过程,减少了一次系统调用和4次CPU传输数据。

    传统文件传输过程包括:磁盘->磁盘控制缓冲区——>CPU缓存——>用户空间——>socket缓存——>网卡

    首先引入了DMA解决了磁盘控制缓冲区<=>CPU缓存这段过程中CPU的参与,之后引入sendfile,解决了CPU缓存需要经过用户空间中转这一步骤,直接由cpu缓存到socket缓存。最后,引入SG-DMA,可以将数据由CPU缓存直接到网卡。

    至此,整个数据拷贝过程不再需要cpu亲自搬运数据。

  25. Lock和synchronized的区别

    • lock是一个接口,而synchronized是一个关键字。
    • synchronized可以给类、方法和代码块加锁;而lock只能给代码块加锁。
    • synchronized不需要手动获取锁和释放锁,发生异常会自动释放锁,不会造成死锁;而lock需要手动自己加锁和释放锁,容易造成死锁。
    • 通过lock可以知道是否成功获取锁,而synchronized无法办到。
  26. 分布式锁有哪些实现方案?

    • 基于关系型数据库比如mysql实现分布式锁;

      利用数据库的唯一约束,对锁字段设置唯一约束,这样子多线程插入时只有一个能够插入成功。

      缺点:

      • 高度依赖单点数据库的可用性(可以搭建主备)
      • 锁没有失效时间,一旦加锁的线程奔溃,就会导致锁无法释放。(设置定时任务,定时把超时的数据清理一遍)
      • 锁是非重入的,同一个线程在没有释放锁之前,无法再次获得锁。(记录获得锁的主机和线程信息,如果是相同的线程,直接重入)
      • 受制于数据库性能限制,并发能力有限。(无法解决)
    • 基于分布式协调服务ZooKeeper实现分布式锁;

      ZooKeeper的分布式锁是基于临时顺序节点Watcher(事件监听器)实现的。

      • 首先,要有一个持久节点/locks,客户端获取锁就是在locks下创建临时顺序节点;
      • 假如客户端1创建了/locks/lock1节点,创建成功后,会判断lock1是不是locks下的最小子节点;
      • 如果是,则获取锁成功,否则,获取锁失败;
      • 获取失败。客户端1就会在locks/lock0上注册一个事件监听器,作用是监听当前节点被删除的事件(锁释放)。
      • 之后,客户端1就加锁成功,还避免了无效自旋
    • 基于分布式键值存储系统,例如redis实现分布式锁。

      利用SETNX插入锁,只有不存在时才能插入成功。

      • 高可用:主备或者共识算法。设置过期时间,看门狗机制。
      • 可重入:每个锁关联占有它的线程,在锁被占用时,判断是否是自己的锁。

    为什么要用临时顺序节点?

    每个数据节点在ZooKeeper中被称为znode,它是ZooKeeper中数据的最小单元。

    znode分为四大类:

    • 持久节点:一旦创建就一直存在即使Zookeeper集群宕机,直至将其删除。
    • 临时节点:临时节点的生命周期是与客户端会话绑定的,会话消失则节点消失。临时节点只能做叶子节点。
    • 持久顺序节点:具有顺序性。
    • 临时顺序节点:具有顺序性。
  27. String是基本数据类型吗?

    String不是基本数据类型,而是引用数据类型。基本数据类型包括short、int、long、float、double、char、byte、boolean

  28. String可以修改吗?为什么这么设计?

    String是不可变类型的,不可以修改,每次修改都会重新指向新的对象。

    为什么这么设计?

    • 在java中,字符串存放在字符串常量池中,多个字符串可以共享同一个实例进而节省内存空间,如果string是可变的,那么在修改字符串之后,就会导致使用相同字符串的地方出现问题。
    • 线程安全,不可变的字符串可以保证在多线程环境下的线程安全性。
    • 缓存哈希值,由于字符串是不可变的,那么我们就可以在第一次调用hashcode时,对哈希值进行缓存,避免后续多次计算。
  29. gc root对象有哪些?

    • 虚拟机栈中正在引用的对象
    • 本地方法栈中正在引用的对象
    • 静态属性引用的对象
    • 方法区常量引用的对象
  30. 怎么判断一个对象是否存活?

    • 引用计数法
    • 可达性分析法
  31. 线程的cpu比较高,怎么排除

    线程cpu占用率过高主要有两种原因:

    • 代码中某个位置读取数据量比较大,导致系统内存耗尽,从而导致Full GC过多,系统缓慢;
    • 代码中有比较耗费cpu的操作,导致cpu占用增高。

    所以,可以通过查看是否存在过多的full gc,来排除第一个可能。

    如果是线程中存在耗费cpu的操作,可以通过jstack命令查看线程的堆栈信息,判断是否存在死锁问题或者发生阻塞的位置。

  32. oom怎么排除

    oom发生区域可能是堆、栈和方法区。

    堆可以分为两种情况:

    • 内存泄漏:如果是内存泄漏可以通过工具查看泄露对象到GC root是怎样的引用链,导致垃圾回收器无法回收它们;
    • 内存溢出:如果是内存溢出,可以看看最大堆内存是否还有向上调整的空间。

    栈可以分为两种情况:

    • 栈溢出。
    • OOM:因为栈的容量在编译期间就确定了,因此可以考虑是否是线程过多。

    方法区主要存放类信息,如果发生OOM,往往是因为在运行期间产生了大量的类,因此就需要考虑是否是动态代理的原因。

  33. 一致性hash算法

    是用于解决分布式缓存等问题的一种算法。

    普通的hash算法(hashcode%size),如果size发生变化,那么几乎所有的历史数据都需要重新hash,移动,代价非常大。

    一致性hash算法,对固定值2^32取模,保持size不变,从而保证了hash函数前后的一致性。

    使用步骤:

    • 将服务器映射到hash环上;
    • 将对象key映射到服务器上:将key映射到环上,然后顺时针方向遇到的第一个服务器,便是当前对象要缓存到的服务器;
    • 这样子如果某台服务器宕机或者新增,只会影响该服务后面服务器上面的key。

    存在的问题:可能引发缓存雪崩。

    缓存服务器有可能在hash环上比较集中,导致某台服务器承受压力比较大。因为如果一台服务器宕机,那么该服务器的所有压力都到了后面那一台,以此类推,会导致整个服务器集群崩溃。

    解决方法:虚拟节点,每一个实际节点映射多个虚拟节点,虚拟节点均匀分布在hash环上。

  34. jdk17有什么新特性,团队从jdk8转到jdk17,怎么知道优化了哪里

    新特性:

    • 本地变量类型var
    • 增强的Switch表达式
  35. ThreadLocal如何实现的?用来哪种数据结构?

    ThreadLocal是java中用于解决线程安全问题的一种机制,它允许线程内部有一个局部变量,从而避免线程间的资源同步问题。

    Thread类内部,有一个ThreadLocalMap的成员变量,这个Map内部维护一个Entry数组,数组默认大小为16,每个Entry代表一个局部变量,也就是说默认一个线程可以支持16个ThreadLocal局部变量。

    Map的key,是ThreadLocal本身,value是要存放的局部变量。

    当我们调用某一个ThreadLocal的get方法时,内部其实先获取当前线程对象,然后再进行数据查找的。

  36. 什么是内存池,内存池有哪几种

    所谓池化技术,就是程序先向系统申请过量的资源,然后自己管理,以备不时之需。之所以,提前申请,是因为每次申请都需要较大的开销,不如提前申请好。

    内存池:是指程序预先从操作系统申请一块足够大的内存,此后,当程序需要申请内存时,不再直接向操作系统申请,而是直接从内存池中获取;同理,当程序释放内存时,也是直接归还到内存池中。只有当程序退出时,内存池才真正归还给系统。

    种类:

    • 按照线程安全可以分为:单线程内存池和多线程内存池;
    • 按照可分配内存大小可分为:固定内存池和可变内存池,所谓固定就是指应用程序每次从内存池中分配出来的内存单元大小事先确定,固定不变。可变是指可以按需申请,应用范围更广。
  37. 工厂模式、适配器模式、装饰器模式、观察者模式

    • 简单工厂模式

      工厂内部需要知道产品的生产细节,每当有新产品加入时,就需要对工厂类进行修改,违反了开闭原则。

    • 工厂模式

      工厂模式中,工厂类是一个抽象类,规定了工厂需要有哪些方法,但是具体实现交由具体的子类实现。这样子,每当新增加产品时,需要新建一个工厂子类。

    • 适配器模式

      将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配无法一起工作的两个类能够一起工作。

    • 装饰器模式

      动态地给一个对象添加一些额外的功能。就增加功能来说,装饰器模式比生成子类更加灵活。

    • 观察者模式(发布订阅模式)

      对象间的一种一对多的依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆可得到通知并自动更新。

  38. 软件设计原则

    • 单一职责:一个类或者模块只负责完成一个职责。

    • 开闭原则:程序对于扩展是开放的,对于修改是封闭的。(扩展优于修改)

      比如:我有一个生产线A,现在我需要生产B产品,按照开闭原则,我应该新建一条生产线B,而不是修改生产线A使其能生产B。

    • 里氏替换原则:在一个可以接收父类的位置,我们替换成子类对象,那么运行的结果,应该是可以预期的,即子类的结果和父类是一致的。

    • 接口隔离原则:不要在一个接口中定义太多的方法,应该将一个大接口合理的拆分成不同的接口。

    • 依赖倒置原则:在高层类和底层类之间引入中间抽象层,高层类和底层类都依赖于中间抽象层。

    • 最少知识原则:一个类对自己需要调用的类的内部结构知道的最少,实现细节自己不关心。

  39. BIO、NIO和AIO

    • BIO:Blocking IO 阻塞式IO

      同步阻塞、在读写动作完成之前,线程会一直阻塞在那里。

    • NIO:non-Blocking IO 非阻塞式IO

      同步非阻塞、可以构建多路复用、一个线程监听多个客户端连接。

      同步是指线程不断地轮询IO事件是否就绪。

    • AIO:Asynchronous IO 异步非阻塞式IO

      异步非阻塞、异步IO是基于事件和回调机制实现的,应用操作之后会直接返回,当后台处理完成,操作系统会通知相应的线程进行后续操作。

  40. 分布式架构对比单体架构的优点

    • 增大了系统容量
    • 加强了系统的可用性
    • 可扩展性高
  41. java的异常体系结构

    Java的异常体系主要基于两大类:Exception(异常)和Error(错误)。

    • Error:表示运行时环境的错误。错误是程序无法处理的严重问题,例如:系统崩溃、虚拟机错误、动态链接失败等。通常程序不应该捕获这类错误。例如:OutOfMemoryError、StackOverflowError等。
    • 异常:表示程序本身可以处理的异常。可以分为:
      • 非运行时异常:在编译时,就必须被捕获或者声明抛出。通常是外部错误,比如,文件不存在、类未找到。
      • 运行时异常:空指针异常、数组越界,运行时异常是不需要在编译时强制捕获或声明的。
  42. try{return “a”}finally{return “b”};这条语句返回啥?

    finally块中的return会覆盖try中的,因此返回b。

  43. 什么是反射?

    Java反射机制是在运行中,对于任意一个类,都能够知道这个类中的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取类和对象内部信息的功能称为java的反射机制。

  44. 为什么有了synchronized,还要给出Lock?

    因为synchronized是由jvm内部提供的关键字,我们无法直接对其进行扩展功能。而Lock是接口,我们可以通过继承的方式更容易实现功能的扩展,例如其子类ReentrantLock,增加了可中断锁、公平锁、多条件变量等新功能。

  45. CAS的缺点及其解决方案

    • ABA问题:加上时间戳或者版本号;
    • 自旋消耗资源:指定自旋的最大次数;
    • 多变量共享一致性问题:CAS只能针对一个变量,如果是多个变量操作,可以加锁或者封装成对象类。
  46. DCL问题

    在java中,DCL通常是指Double-Checked Locking(双重检查锁)。是一种用于多线程环境下懒加载单例对象的技术,它的基本思路是:加锁之前先判断一次空,获得锁之后,在判断一次空,经过双重判断后,确定为空,再去创建对象。

    但是,由于java内存模型允许指令重排,可能会导致DCL失效,或者对象并未创建之前,就被使用的情况。

    因此,需要利用volatile禁止指令重排序。

  47. 时间和空间局部性原理

    • 时间局部性:是指在某一时刻被访问的数据或指令,很有可能在不久的将来再次被访问。
      • 缓存
      • 分页
    • 空间局部性:是指如果某个存储位置被访问,那么与其相邻的存储位置也有很大概率在不久的将来被访问。
      • 缓存行
      • 预读取
  48. Spring MVC 处理流程

    1. 用户发送请求至前端控制器DispatcherServlet;
    2. DispatcherServlet收到请求后,调用处理器映射器HandlerMapping;
    3. 处理器映射器根据请求url找到具体的处理器,生成处理器执行链(包括处理器和处理器拦截器)返回给前端控制器;
    4. 前端控制器根据处理器获取处理器适配器HandlerAdapter并执行;适配器做一些参数封装、数据格式转换等工作。
    5. 执行处理器;
    6. 执行完成后,返回ModelAndView;
    7. 处理器适配器将执行结果返回到前端控制器;
    8. 前端控制器将结果传递给视图解析器;
    9. 视图解析器解析后返回具体视图;
    10. 然后前端控制器对其进行渲染,之后响应用户。
  49. Spring 拦截器和切面哪个先执行?

    拦截器先执行。

    Spring执行的顺序:Filter——>Interceptor——>Aspect;

    过滤器——>拦截器——>切面。

  50. at least once机制同层面的还有什么机制?

    at most once。

    这两个分别是:最少一次和最多一次。

    分布式系统传输过程中,失败与否是使用最大等待时间(time out)来判断的。如果超过这个时间,就判定失败。这就涉及到是否重传的问题。如果不重传,这就认定是返回过程中丢失了,此时就是at most once;否则重传,就是认定发送过程中丢失了,这就是at least once。

  51. Thread.sleep(0)有意义吗?

    这个方法是java线程调度的一部分,它可以让当前线程停止执行,并进入阻塞状态,让出cpu的执行权,同时操作系统会设置一个定时器,当定时器到了一个,操作系统会唤醒这个线程,虽然传递的时间为0,但是依然会让当前线程进入阻塞状态。

  52. jvm是什么,解决了什么问题?

    jvm全称为java 虚拟机,是java程序运行的基础,负责字节码的解释执行、内存管理和垃圾回收。它具有跨平台的特性,可以一次编写,多平台运行。

  53. 为什么HashMap里的数组是按照2倍增长?

    散列函数计算索引:HashMap 使用键的散列码(hash code)和位运算来计算键的索引。当长度为 2 的倍数时,通过位运算可以用更高效的方式计算索引,即 (n - 1) & hash,其中 n 是容量,hash 是键的散列码。这样可以避免取模运算,提高计算效率。

    平均分布:当长度为 2 的倍数时,哈希表中的槽位可以更均匀地分布键值对,减少碰撞的可能性。如果容量不是 2 的倍数,通过取模运算来计算索引时,可能会导致部分槽位无法被充分利用,使得冲突的几率增加。

    扩容效率:当哈希表需要扩容时,新的容量通常会设置为当前容量的两倍。这样,当容量为 2 的倍数时,扩容时只需要进行简单的位运算,而不需要重新计算每个键的索引,提高了扩容的效率。

  54. 如果hashcode和equals不一起重写,会有什么问题?

    会出现信息重复和内存泄漏。

    比如:两个student学号一样,存储两次,那么我们会认为只保存一份。实际上保存了两份,当我们删除的时候,必然也会删除一次,因此另一份可能永远也用不到了。

  55. 为什么有了垃圾回收机制,还会出现内存泄漏?

    内存泄漏是指一个对象已经不被使用了,但是还存在被其他对象引用着,这种情况无论是引用计数法还是可达性分析法,都不会把对象归为需要回收。例如:静态集合类、单例对象等。

  56. ThreadLocal为什么会存在内存泄漏?

    每个线程内部有一个ThreadLocalMap,而这个ThreadLocalMap将ThreadLocal当作key。当我们使用线程池时,即使线程不再被使用,由于线程依然存活,那么ThreadLocalMap就会存在,它内部的key是弱引用,当直接引用设置为null时,ThreadLocal会被回收,但是value是强引用,不会被回收。
    在这里插入图片描述

  57. gc有哪些危害呢,或者为什么我们要降低gc的频率呢?

  58. 我们为什么要stop the world?

    可达性分析法中,分析GC ROOT可以引用到的对象进行标记的过程会导致java线程停顿,这是因为整个分析过程需要在一个稳定的环境下进行,如果分析过程中,用户线程还在运行,对象的引用关系还在发生变化,那么无法保证分析结果的准确性。

  59. 你说了标记,那我们标记的时间点怎么选择呢,其实就是考察safepoint

    jvm会在方法调用、循环跳转、异常跳转等位置放置安全点,jvm通过主动中断的方式达到全局STW。

  60. bean的生命周期是什么?初始化和实例化有什么区别?

    • 实例化:通过反射机制来实现,Bean对象创建,并分配内存空间;
    • Bean依赖注入;
    • 初始化:属性设置,执行一些初始化操作等。
    • 使用
    • 销毁
  61. 线程之间的协作除了wait/notify,还有什么?

    还可以通过共享内存、CountDownLatch。

    CountDownLatch 的实现原理比较简单,它主要依赖于 AQS(AbstractQueuedSynchronizer)框架来实现线程的同步。

    CountDownLatch 内部维护了一个计数器,该计数器初始值为 N,代表需要等待的线程数目,当一个线程完成了需要等待的任务后,就会调用 countDown() 方法将计数器减 1,当计数器的值为 0 时,等待的线程就会开始执行。

  62. hashmap在扩容时,可以执行get,put方法吗?如何进行查询?

    • 在扩容过程中,查询操作仍然可以进行。由于hashmap并未对方法加锁,多线程访问时会出现查询不正确的情况。
    • HashMap在扩容的过程中,会先创建一个新的数组,并赋值给原始的table变量,之后才去进行迁移数据。
    • 因此,扩容时执行hashmap的put和get方法,是在新数组上进行的,会存在查询不准确的问题。
      在这里插入图片描述
  63. JWT

    JWT由三部分组成:头部、荷载和签名。签名部分是由加密算法生成,无法反向解密,可以用于防止头部和荷载里面的信息被篡改。

    头部和荷载部分是由Base64编码生成,是可以解码回原样的,因此敏感信息不要放在JWT中。

    JWT的缺点:jwt一旦派发出去,在失效之前都是有效的,没办法撤销jwt。解决这个问题,可以在业务层增加判断逻辑,比如使用redis增加黑名单机制,每次使用前先查询是否在黑名单中。

    JWT解决分布式会话共享问题:

    在传统的基于会话和Cookie的身份验证中,会话信息通常存储在服务器上。但是在集群部署中,不同服务器之间没有共享的会话信息,这会导致用户在不同服务器之间切换时,需要重新登录,或者引入额外的共享机制(如:redis)。

    而JWT的令牌中包含必要的身份信息(不包含密码),使得服务器无需存储会话信息,从而解决了集群部署中的身份验证和会话管理问题。

    原理是,只要用户有令牌就说明肯定有一台服务器对用户进行了验证,那么其他服务器就可以直接放行。

  64. ConcurrentHashMap 怎么解决 ABA 的?

    是通过版本号机制解决的。

  65. 直接内存

    直接内存是一块特殊的内存缓冲区,并不在jvm内部,而是在本地内存上分配的。

    JDK1.4中新加入的NIO(Non-Blocking IO,也被称为New IO),引入了一种基于通道和缓冲区的IO方式,它可以直接使用Native函数直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样在一些场景中能显著提高性能,因为避免了在java堆和Native堆之间来回复制数据。

  66. 引用类型有哪些?有什么区别?

    • 强引用:代码中普遍存在的赋值方式,强引用关联的对象,永远不会被回收;
    • 软引用:SoftReference定义的,指那些有用但不必要的对象,系统发送内存溢出前会对这类引用的对象进行回收;
    • 弱引用:WeakReference定义的,强度比软引用更低,一次GC回收时,弱引用对象一定会被回收;
    • 虚引用:是最弱的引用关系,必须和ReferenceQueue一起使用,同样发生GC时,也会被回收。
  67. java中哪里使用到了弱引用?

    • 缓存系统:希望在内存压力之下,能够自动清理这部分对象,释放内存;
    • 对象池:在对象池中,弱引用可以用来管理那些暂时不使用的对象,当对象不再被强引用时,它们可以被回收;
    • ThreadLocalMap中Map的key和ThreadLocal对象之间就是弱引用。
  68. 创建对象的过程?

    • 类加载检查

      虚拟机遇到一条new指令时,首先检查这个类的符号引用能否在常量池中找到,并且检查这个类是否已经被加载过,如果没有则执行类的加载过程。

    • 分配内存

      通过类加载检查后,虚拟机为新对象分配内存。对象所需的内存大小在类加载完成后就可确定。

    • 初始化零值

      内存分配完成后,虚拟机需要将分配的内存初始化为0(不包括对象头),这一步操作保证了对象的实例字段在java代码中可以不赋初值就直接使用,程序访问到的是0;

    • 进行必要的设置

      比如:这个对象属于哪个类的实例、对象的哈希值、GC分代年龄等信息。

    • 执行init方法

      执行构造函数,变量赋初值。

  69. SpringBoot拦截器的处理流程?Filter和Interceptor哪个先执行?它俩分别拦截什么?

    Filter依赖于Servlet容器,基于函数回调实现,可以拦截所有请求,覆盖范围更广,但是无法获取ioc容器中的bean。

    Interceptor依赖于Spring框架,基于java反射实现,只能拦截controller请求,可以获取ioc容器中的bean,我们可以在拦截器中注入一个service,可以调用业务层逻辑。

    Filter——>Interceptor

  70. BigDecimal为什么不会丢失精度?

    BIgDecimal底层是通过将数值放大,利用十进制整数来计算,然后再进行小数点位置缩放得到结果。

  71. 一个子类,一个父类,new对象的时候,构造器和静态代码块中的语句执行顺序

    父类静态代码块——>子类静态代码块——>父类构造方法——>子类构造方法

  72. JDK内部使用了哪些设计模式?

    • 单例模式:Runtime类。
    • 工厂模式:线程池。
    • 适配器模式:线程的第三种创建方式,利用futureTask来适配callable接口。
    • 代理模式:jdk的动态代理。
    • 迭代器模式:集合的迭代器。
  73. i++是线程安全的吗?

    不是。因为i++,实际上包含了读取i的当前值,增加它,把结果写回去三个操作,并不是原子操作,无法保证线程安全。

  74. 静态代理和动态代理的区别?

    • 静态代理:由程序员创建或者特定工具创建,在代码编译时就确定了被代理的类,静态代理通常只能代理一个类。
    • 动态代理:在代码运行期间,运用反射机制动态创建生成。动态代理代理的是一个接口下的多个实现类。
  75. Spring AOP和AspectJ AOP的区别?

    Spring AOP属于动态代理,AspectJ AOP属于静态代理。

  76. 什么是依赖注入?实现依赖注入的方式有哪些?

    不通过new的方式在类内部创建所依赖的对象,而是将依赖的对象在外部创建好之后,通过构造函数、函数参数等方式传递给类使用。

  77. Lock有哪些实现?

    • ReentrantLock
    • ReentrantReadWriteLock
  78. synchronized可以用在静态方法中吗?

    可以。

  79. Integer可以直接==比较吗?

    Integer默认创建了[-128,127]之间的缓存数据,因此在这之间使用==是可以的,但是超出这范围就不可以了。

  80. 如果要保证一个任务必须执行,抛弃策略选什么?

    选择抛弃队列中等待时间最久的任务。

  81. ConcurrentHashMap的key可以为null吗?

    可以。

  82. SpringAOP的底层是什么?

    反射技术。

  83. spring的类初始化的过程中,我们有时候需要干涉它的初始化过程,请说一下spring提供了哪些接口?

  84. No such method exception是怎么回事?

    没有找到对应的方法。

    • 方法真的不存在;
    • 替换jar包没有重新加载项目;
    • Maven不是自动加载的,并且也没有手动加载。
  85. JDK1.8默认的垃圾收集器是什么?

    Parallel Scavenge(新生代)+Parallel Old(老年代)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

回眸间灵珊现

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值