『美团网』2019最新社招Java面试题分享——Spring+JVM+多线程

本文转载自:『美团网』2019最新社招Java面试题分享——Spring+JVM+多线程


一. Spring

1. 谈谈你对Spring的理解

关键点

  • 企业框架,目前最流行,没有之一
  • AOP、IOC、Spring MVC

2. Spring中用到了哪些设计模式

  • 工厂模式,比如 BeanFactory
  • 代理模式,在Aop实现中用到了JDK的动态代理
  • 单例模式,Bean的创建默认就是单利的

3. IoC的启动过程

  • Resource文件的定位,即找到bean的配置文件
  • 通过特定的reader解析该bean配置文件,抽象成beanDefinition类
  • 将beanDefinition向容器注册,写入到一个大的HashMap中

4. BeanFactory 和 ApplicationContext 区别

  • 功能,BeanFactory负责读取bean配置,管理bean的加载,实例化,维护bean之间的依赖关系,负责bean的声明周期;ApplicationContext除了提供上述BeanFactory所能提供的功能之外,还提供了国际化支持、资源访问、事件传递、队Web的支持等功能
  • BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化;而ApplicationContext则相反,它是在容器启动时,一次性创建了所有的Bean。
  • BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册

5. Bean 的生命周期

  • 实例化一个Bean,也就是我们通常说的new
  • 按照Spring上下文对实例化的Bean进行配置,也就是IOC注入
  • 如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的是Spring配置文件中Bean的ID
  • 如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(),传递的是Spring工厂本身(可以用这个方法获取到其他Bean)
  • 如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文,该方式同样可以实现步骤4,但比4更好,以为ApplicationContext是BeanFactory的子接口,有更多的实现方法
  • 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用After方法,也可用于内存或缓存技术
  • 如果这个Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法
  • 如果这个Bean关联了BeanPostProcessor接口,将会调用postAfterInitialization(Object obj, String s)方法 注意:以上工作完成以后就可以用这个Bean了,那这个Bean是一个single的,所以一般情况下我们调用同一个ID的Bean会是在内容地址相同的实例
  • 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean接口,会调用其实现的destroy方法
  • 最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法

6. Bean的作用域

  • singleton(默认): 在Spring的IoC容器中只存在一个对象实例,所有该对象的引用都共享这个实例。Spring 容器只会创建该bean定义的唯一实例,这个实例会被保存到缓存中,并且对该bean的所有后续请求和引用都将返回该缓存中的对象实例,一般情况下,无状态的bean使用该scope。
  • prototype:每次对该bean的请求都会创建一个新的实例,一般情况下,有状态的bean使用该scope。
  • request:每次http请求将会有各自的bean实例,类似于prototype。
  • session:在一个http session中,一个bean定义对应一个bean实例。
  • global session:在一个全局的http session中,一个bean定义对应一个bean实例。典型情况下,仅在使用portlet context的时候有效。

7. AOP

实现原理:默认,接口基于JDK动态代理,类为cglib

注意事项

  • 嵌套失效问题
  • final类直接报错问题(动态代理的实现原理)

8. Spring MVC

请求过程

  • 用户请求DispatchServlet
  • DispatchServlet根据请求路径调用具体HandlerMapping返回一个HandlerExcutionChain
  • DispatchServlet调用HandlerAdapter适配器
  • HandlerAdapter调用具体的Handler处理业务
  • Handler处理结束返回一个具体的ModelAndView给适配器
  • 适配器将ModelAndView给DispatchServlet
  • DispatchServlet把视图名称给ViewResolver视图解析器
  • ViewResolver返回一个具体的视图给DispatchServlet
  • 渲染视图,展示给用户

二. JVM

1. 内存划分

JVM规范,将内存分为 程序计数器、Java栈,也叫虚拟机栈、本地方法栈、方法区、堆

  • 程序计数器 程序指令保存,从当前指令到下一个指令,从程序计数器获取下一个指令的地址,直到执行所有的指令;线程私有
  • Java栈(虚拟机栈) 保存方法栈帧,当调用一个方法,则新创建一个栈帧,当前的方法始终保持在栈帧的顶部;线程私有
  • 本地方法栈 保存本地方法栈帧,当调用一个本地方法,则新创建一个栈帧,当前的方法始终保持在栈帧的顶部;线程私有
  • 方法区 保存类信息、静态常量、常量;线程共享
  • 堆 保存对象,最最主要的垃圾收集之处;线程共享

2. 对象存活

  • 引用计数算法:对对象引用进行计数,引用则计数器 +1,引用失效 -1;计数为 0 时候认为不在引用,可以进行回收;不能处理对象循环引用问题
  • 可达性分析算法:主要采取此方式,采用虚拟机栈、类引用对象、常量对象、本地方法引用对象作为根,判断对象到根是否存在引用关系,不可达则认为不在引用,可以进行回收;

3. GC回收算法

  • 标记-清除算法:对要回收的对象,先进行标志,后进行清除,你懂得;但是,久之,会存在内存不连续,比如,一次垃圾回收,回收了,(0,1)和(0,3)和(0,5)三个位置,但是没有回收(0,2)和(0,4),那下次的内存,就无法使用(0,1)-(0,5)的连续空间;
  • 复制算法:为改进上面的内存碎片问题而产生,对内存分为两部分,交替回收其中一部分,存活的对象复制到另一部分空间,你懂得;但是,有点浪费,比如,内存分为,(0,1)-(0,3)和(0,3)-(0,5)两个部分,某次回收(0,1)-(0,3)空间,将存活对象拷贝到(0,3)-(0,5),而后在(0,1)-(0,3)分配对象;
  • 标记-整理算法:为改进上面的内存浪费问题而产生,对要回收的对象,先进行标志,后进行清除,你懂得,然后,将存活的对象,进行整理,移动到边界位置,似的剩余空间连续;比如,一次垃圾回收,回收了,(0,1)和(0,3)和(0,5)三个位置,但是没有回收(0,2)和(0,4),然后,将(0,2)和(0,4)移动到(0,1)和(0,2),使得(0,3)-(0,5)的空间连续;

4. 类加载过程

jvm中class类的加载过程,大致分为这几个步骤

  • 加载(load)
  • 根据全类名,加载类的二进制字节流
  • 将字节流转存方法区
  • 生成Class对象作为访问入口
  • 验证(verify)
  • class文件的格式验证,验证是否符合JVM规范
  • class中的元数据验证,验证是否符合Java规范
  • class的字节码验证,验证数据流控制流不会危害JVM环境
  • 准备(prepare)
  • 给变量分配内存
  • 初始化零值(比如int默认为0,boolean默认为false)
  • final变量直接赋值
  • 解析
  • 符号引用变为直接引用
  • 类、字段、方法、接口方法解析
  • 初始化
  • 初始化变量
  • 构造函数
  • static块

5. 双亲委派机制

Java中,大概有三种类型加载器,启动类加载器(Bootstrap)<- 标准扩展类加载器(Extension)<- 应用程序类加载器(Application )<- 上下文类加载器(Custom),从右到左,尽量父类进行加载,当父类无法进行加载时候,才会使用子类进行加载

  • 意义
  • 防止同一个JVM,内存中出现两份class二进制字节码
  • 加载过程
  • 从已加载的类查找是否已经存在,存在不需要再次加载
  • 若不存在,则去parent中查找,存在不需要再次加载
  • 若不存在,递归在parent中查找,直到找到为止
  • 若找遍所有parent均不存在,且当前加载器已经没有parent加载器,则调用当前类加载器的findClass方法,如果能加载,结束
  • 如果不能,则递归返回child类加载器,继续调用findClass方法,如果能加载,结束
  • 如果找遍所有child的findClass方法,还是不能加载,则抛出异常
  • 破坏双亲委派机制
  • 将parent设为null
  • 重写load(String,boolean)方法,改变类的查找机制。

三. 多线程

1. 死锁的四个条件

  • 互斥
  • 请求与保持
  • 不剥夺
  • 循环等待

2. 检查死锁

  • Jconsole查看死锁
  • Jstack查看死锁

3. volatile

  • JMM
  • 内存可见性
  • 防止指令重排序
  • 不能保证原子性

4. synchronized

作用

  • 互斥访问
  • 内存可见性
  • 防止指令重排序

用法

  • 修饰普通方法
  • 修饰静态方法
  • 修饰代码块

注意点

  • 当一个线程在访问对象的 synchronized 方法,因为对象只有一把锁,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized实例方法,但是其他线程还是可以访问该实例对象的其他非synchronized方法
  • 实现原理为,对象监视器,Monitor

5. volatile vs synchronized vs lock

  • 来源差异:volatile、synchronized为Java关键字; lock是Java类
  • 代价开销:volatile不是锁,代价最小; lock是一般基于AQS,相对比synchronized代价小; synchronized代价最大
  • 简单性:volatile、synchronized为Java关键字,JVM全权帮忙维护,只要我们能正确使用,不需要我们太多关心维护; lock是Java类,有很多方法可以调用,灵活性最好,但是需要自己控制锁的获取、释放

6. 进程间通信

  • 管道pipe
  • 命名管道FIFO
  • 消息队列MessageQueue
  • 共享存储SharedMemory
  • 信号量Semaphore
  • 套接字Socket
  • 信号 ( sinal )

7. 原子操作类

  • CountDownLatch

    • 某线程需要等待多个线程执行完毕,再执行。
    • 实现多个线程共同等待,同时开始执行任务,不可重用。(此类似于CyclicBarrier,可重用)
  • CyclicBarrier

    • CyclicBarrier允许一组线程互相等待,直到到达某个公共屏障点。
    • 与CountDownLatch不同的是该barrier在释放等待线程后可以重用,所以称它为循环(Cyclic)的屏障
  • CountDownLatch、CyclicBarrier差别

    • CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同: CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行; 而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
    • CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
  • Semaphore

    • Semaphore是一个计数信号量,它的本质是一个”共享锁”。
    • 信号量维护了一个信号量许可集。线程可以通过调用acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可。
    • Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。
  • Exchanger

    • 用于进行线程间的数据交换。
    • 两个线程通过exchange方法交换数据
    • 该工具类的线程对象是成对的
  • ThreadLocal

    • 每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal实例本身,value 是真正需要存储的 Object。
    • ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。值得注意的是图中的虚线,表示 ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。
    • ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。
    • ThreadLocal里面使用了一个存在弱引用的map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例。这个Map的确使用了弱引用,不过弱引用只是针对key。每个key都弱引用指向threadlocal。 当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收。 但是,我们的value却不能回收,而这块value永远不会被访问到了,所以存在着内存泄露。因为存在一条从current thread连接过来的强引用。只有当前thread结束以后,current thread就不会存在栈中,强引用断开,Current Thread、Map value将全部被GC回收。最好的做法是将调用threadlocal的remove方法,这也是等会后边要说的。
    • 使用static的ThreadLocal,延长了ThreadLocal的生命周期,可能导致内存泄漏。
    • 分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么就会导致内存泄漏,因为这块内存一直存在。

本文转载自:『美团网』2019最新社招Java面试题分享——Spring+JVM+多线程

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值