常见面试题

1. 微服务篇

1.SpringCloud常见组件有哪些?

  • 注册中心组件: Eureka、Nacos等
  • 负载均衡组件: Ribbon
  • 远程调用组件:OpenFeign
  • 网关组件:Zuul、Gateway
  • 服务保护组件:Hystrix、Sentinel
  • 服务配置管理组件: SpringCloudConfig、Nacos

2.Nacos与Eureka的区别有哪些?

  • nacos与euraka都实现了服务注册、发现功能
  • nacos实例有永久和临时之分,eureka只有临时实例
  • 健康检测方面nacos对临时实例采用心跳模式,对永久实例是主动请求。而eureka只支持心跳模式。
  • 对于服务的发现,nacos支持定时拉取和订阅推送两种方式,而eureka只支持定时拉取模式。
  • Nacos集群默认采用AP模式,当有非临时实例是采用CP模式;Eureka采用AP模式。

3.Sentinel与Hystix的线程隔离有什么区别?
Hystix默认是基于线程池实现的线程隔离,每一个被隔离的业务都会创建一个独立的线程池,线程过多的话会带来额外的CPU开销,性能一般,隔离性强。
Sentinel默认是基于信号量实现的线程隔离,不需要创建线程池,性能较好,但是隔离性一般。

4.Sentinel与Gateway的限流有什么区别?
Gateway限流模式比较单一,而Sentinel限流模式则有好几种,可以根据不同的需求使用不同的模式。
主要的原因就是Gateway采用的是基于Redis实现的令牌桶算法。而Sentinel不同模式使用的算法不同。
Sentinel不同模式及使用的算法:

  • 默认限流模式,使用的是滑动时间窗口算法。
  • 排队等待模式,使用的是漏桶算法
  • 热点参数模式,使用的是令牌桶算法

5.feign是怎么解决负载均衡的?
feign集成了负载均衡器ribbon,通过ribbon实现负载均衡的。
源码中大概的实现流程如下:
ribbon内部实现了接口ClientHttpRequestInterceptor(http客户端请求拦截器),会拦截客户端的http请求,再拦截的请求中获得微服务的服务名称。然后调用RibbonLoadBalancerClient类下的excute方法,该方法回去注册中心拉去服务列表。最后获取负载均衡规则,根据规则对服务列表进行负载均衡处理。默认的规则是ZoneAvoidanceRule,根据zone(区域,可能机房在不同的城市)选择服务列表,然后轮询。规则可以在配置文件中手动指定。

总结如下:

  • 拦截器获取服务名称
  • 调用均衡方法
    • 去注册中心拉取服务列表
    • 获取负载均衡规则
    • 根据负载均衡规则去轮询服务列表

2. JVM篇

1. jvm内存结构

  • 程序计数器
    用于存放下一条指令的地址。
    特点:线程私有,不存在内存溢出。

  • java虚拟机栈
    每个线程运行时所需要的内存。每个栈由一个或多个栈帧(Frame)组成,栈帧就是每次方法调用时所占用的内存。每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。

  • 本地方法栈
    一些带有 native 关键字的方法就是需要 JAVA 去调用本地的C或者C++方法,因为 JAVA 有时候没法直接和操作系统底层交互,所以需要用到本地方法栈,服务于带 native 关键字的方法。


  • 通过new关键字创建的对象,都会使用堆内存。可以使用-Xmx指定堆内存大小。
    特点:它是线程共享的,有垃圾回收机制,会内存溢出。

  • 方法区
    方法区是一个概念,在jdk1.6及之前是通过永久代来实现的,jdk1.8及以后是通过元空间实现的。其位置也是不一样的,永久代使用的是虚拟机设置的内存,而元空间使用的是系统的本地内存。
    方法区用于存储类的信息(方法、属性、构造器等)、类加载器以及常量池。

线程共享:

  • 方法区

线程私有

  • 程序计数器
  • java虚拟机栈
  • 本地方法栈

2. 垃圾回收是否涉及栈内存?
不涉及,栈内存时方法调用的时候产生的栈帧内存,而栈帧内存会在方法调用结束后自动回收。

3. 栈内存分配的越大越好吗?
栈内存可以通过 -Xss指定大小,但并不是越大越好。栈内存大可以更深层次的调用方法或者更多次的递归调用,但是由于物理内存大小是固定的,所以栈内存太大会导致线程数变少。

4. 方法内的局部变量是否线程安全?

  • 如果局部变量没有逃离方法的作用范围,那就是线程安全的。
  • 如果局部变量逃离了方法的作用范围,可以被其他线程访问到,就不是线程安全的。如下图:
    常见面试题_第1张图片
    上图只有m1方法是线程安全的,因为别的线程无法访问到sb变量;m2方法作为参数使用,再调用该方法前主线程或其他线程是可以对该参数进行修改的,所以线程不安全;m3作为返回值,也是可以被其他线程访问到进行修改的,也不是线程安全的。

5. 如果线上忽然CPU占用过高,怎么排查?

  • top:命令定位哪个进程对cpu的占用过高,获得进程Id。
  • ps H -eo pid,tid,%cpu | grep 进程id:进一步定位哪个线程占用cpu过高,然后将有问题的线程id转换成16进制,用于下一步的对比。
  • jstack 进程id:可以根据进程id查看该进程下的线程信息,然后根据上一步的16进制线程id找到有问题的线程。

6.如果程序允许很长时间都没有结果,怎么排查?

  • jstack 进程id,根据返回的信息可以查看是否有死锁情况以及出错代码的行号。

7. 简单说下垃圾回收器算法
垃圾回收器算法主要有三种:标记清楚、标记整理、复制。

  • 标记清除
    垃圾回收的时候,标记出垃圾,然后直接清除。
    优点:执行速度快
    缺点:会产生空间碎片
  • 标记整理
    垃圾回收的时候,标记出垃圾,然后清除,清除之后会对释放的内存空间进行合并整理。
    优点:内存空间连续,不会产生碎片。
    缺点:执行速度没有标记清除快。
  • 复制算法
    准备两个空间,from和to。其中from中有对象,to是空闲空间。垃圾回收的时候会标记出from中的垃圾对象,将不是垃圾的对象复制到to中,然后清除from中的对象,最后from和to互换位置。from变成to,to变成from。
    优点:不会产生碎片
    缺点:占用更多的内存空间。

8.如果一个对象的大小超过新生代内存大小,jvm会怎么处理这个大对象?
首先,这种情况是不会进行垃圾回收的,jvm会直接将这个大对象晋升的老年代,如果老年代也放不下就会先FullGC,还放不下就会触发OOM异常。

9. 说说常用的垃圾回收器
常见的垃圾回收器有,串行垃圾回收器(SerialGC)、并行垃圾回收器(ParallelGC)、CMS、G1等
低延迟的回收器:CMS、G1、ZGC。jdk8默认CMS,jdk9默认G1.
高吞吐量的回收器:ParallelGC。

  • 新生代内存不足的时候都会发生minorGC。
  • 串行和并行垃圾回收器老年代不足会发生fullGC
  • CMS老年代内存不足,如果垃圾回收失败就会退化为串行垃圾回收并执行fullGC

10.jvm内存调优
新生代的内存并不是越大越好,太小了也不合适。

  • 新生代内存太大:老年代内存占比有所降低,会更频繁地触发 Full GC。而且触发 Minor GC 时,清理新生代所花费的时间会更长
  • 新生代内存太小:频繁触发 Minor GC ,会 STW ,会使得吞吐量下降
    官方推荐新生代内存大小为总堆内存的25%-50%之间

老年代调优,以 CMS 为例:

  • CMS 的老年代内存越大越好
  • 如果系统运行一段时间没有发送Full GC,那么可以不用调优了,否则先尝试调优新生代。
  • 观察发现 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3

11.Full GC 和 Minor GC 频繁可能是什么问题
GC频繁说明内存空间紧张,大概可以推断出是新生代内存太小了。新生代内存太小会导致一些生存周期短的对象(没达到晋升条件)提前晋升到老年代,从而导致老年代对象过多内存不足发生Full GC。
解决方法:试着增加新生代内存空间,让生成周期较短的对象尽可能的留在新生代。

12.请求高峰期发生 Full GC,单次暂停时间特别长(CMS)
查看GC日志,看哪个阶段暂停时间比较长(一般CMS重新标记的时候耗时比较长),如果是重新比较比较耗时的话,可以通过 -XX:+CMSScavengeBeforeRemark 参数,然JVM在重新标记之前先对新生代对象进行垃圾回收。

13.老年代充裕情况下,发生 Full GC(jdk1.7)
jdk1.7方法区是在永久代内的,jdk1.8永久代废除,方法区在元空间内。
永久代内存不足也会导致Full GC。

14.说说双亲委派机制

  • 如果一个类加载器收到了类加载请求,它并不会直接去加载该类,而是把这个请求委托个父类去执行;
  • 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,直到请求最终达到顶层的启动类加载器;
  • 如果父类加载器能完成加载操作就成功返回,否则的话,子类才会尝试去加载,这就是双亲委派机制。

15.说出下面的结果,并说明原因?

    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "a" + "b";
        String s4 = s1 + s2;
        String s5 = "ab";
        String s6 = s4.intern();;
        System.out.println(s3 == s4);
        System.out.println(s3 == s5);
        System.out.println(s3 == s6);
    }

打印的结果是:fasle true true
解析:

  • s3是两个常量的拼接,字符串常量拼接的原理是编译期间的优化,直接去串池中寻找"ab",有就使用,没有就放入串池中,结果存在串池中
  • s4是两个变量的拼接,底层原理是new StringBuilder()对象,调用其append()方法,然后再调用toString(),结果存在堆中
  • s5的原理和s3是一样的,常量也会直接去串池中寻找"ab",有就使用,没有就放入串池中,结果存在串池中。所以s5其实就是s3放入串池中的对象。
  • s6是直接将堆中的s4对象进行串池化,jdk1.8版本是尝试将对象放入串池中,有就不放入,没有就放入,然后返回串池中的对象。而jdk1.6是将这个字符串对象放入串池,如果有则不会放入,如果没有会把这个对象复制一份放入串池,然后返回串池中的对象。

16.聊聊java内存模型(JMM)
java内存模型就是保证多线程间,对数据原子性、有序性、可见性的保障方案。
原子性
java内部通过synchronized关键字来保证数据的原子性。

3.Redis

1. Redis与数据库双写一致性
方案一:可以使用分布式读写锁,redisson下的。
方案二:设置缓存的超时时间。

你可能感兴趣的:(面试专栏,面试,Java)