各大厂面试整理 20 道 Java 后端开发面试题总结

1、Zookeeper 中都有哪些服务器角色?

Leader

Leader服务器是整个ZooKeeper集群工作机制中的核心,其主要工作有以下两个。

1)事务请求的唯一调度和处理者,保证集群事务处理的顺序性。

2)集群内部各服务器的调度者。

Follower

从角色名字上可以看出,Follower服务器是ZooKeeper集群状态的跟随者,其主要工作有以下三个。

1)处理客户端的非事务请求,转发事务请求给Leader服务器。

2)参与事务请求Proposal的投票。

3)参与Leader选举投票。

Observer

Zookeeper3.3.0版本以后引入的一个全新的服务器角色,在不影响集群事务处理能力的基础上提升集群的非事务处理能力从字面意思看,该服务器充当了一个观察者的角色—其观察ZooKeeper集群的最新状态变化并将这些状态变更同步过来。

1)Observer服务器在工作原理上和Follower基本是一致的,对于非事务请求,都可以进行独立的处理,而对于事务请求,则会转发给Leader服务器进行处理。

2)对比Follower唯一的区别是Observer不参与任何形式的投票,包括事务请求Proposal的投票和Leader选举投票。简单地讲,Observer服务器只提供非事务服务,通常用于在不影响集群事务处理能力的前提下提升集群的非事务处理能力。另外,Observer的请求处理链路和Follower服务器也非常相近。

2、RabbitMQ 有几种广播类型?

direct(默认方式):最基础最简单的模式,发送方把消息发送给订阅方,如果有多个订阅者,默认采取轮询的方式进行消息发送。

headers:与direct类似,只是性能很差,此类型几乎用不到。

fanout:分发模式,把消费分发给所有订阅者。

topic:匹配订阅模式,使用正则匹配到消息队列,能匹配到的都能接收到。

3、Java 中 i++ 和 ++i 有什么区别?

概念区别

i++表示先引用i变量的数值然后再对i进行加1的操作,而++i是先对i变量进行加1的操作,然后再引用i变量的数值。

表达式形式区别

i++将“++”放在变量的后面,++i将“++”放在变量的前面。

预算优先级区别

i++中的“++”运算符的优先级比++i中“++”运算符的优先级高。

int i = 1,j = 1,h = 1;
j = i++;
System.out.println(j);

i = 1;
h = ++i;
System.out.println(h);

执行j = i++,先将i变量的值1赋值给j,此时j=1,i才等于2;执行h = ++i,会先将i变量的值1加1变成2,然后赋值给h,此时h的值为2。

4、什么是网络层?

网络层的任务就是选择合适的网间路由和交换结点,确保计算机通信的数据及时传送。在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组和包进行传送。在TCP/IP体系结构中,由于网络层使用IP协议,因此分组也叫IP数据报 ,简称数据报。

互联网是由大量的异构(heterogeneous)网络通过路由器(router)相互连接起来的。互联网使用的网络层协议是无连接的网际协议(Intert Prococol)和许多路由选择协议,因此互联网的网络层也叫做网际层或IP层。

发送端在层与层之间传输数据时,每经过一层时会被打上一个该层所属的首部信息。反之,接收端在层与层之间传输数据时,每经过一层时会把对应的首部信息去除。

5、JSP 模版引擎如何解析 ${} 表达式?

目前开发中已经很少使用JSP模版引擎,JSP虽然是一款功能比较强大的模板引擎,并被广大开发者熟悉,但它前后端耦合比较高。

其次是JSP页面的效率没有HTML高,因为JSP是同步加载。而且JSP需要Tomcat应用服务器部署,但不支持Nginx等,已经快被时代所淘汰。

JSP页面中使用${表达式}展示数据,但是页面上并没有显示出对应数据,而是把${表达式}当作纯文本显示。

原因分析:这是由于jsp模版引擎默认会无视EL表达式,需要手动设置igNoreEL为false。

<%@ page isELIgnored="false" %>

6、什么是双亲委派模型?

双亲委派模型要求除了顶层的启动类加载器外,其他的类加载器都应当有自己的父类加载器。

这里类加载器之间的父子关系一般不会以继承关系来实现,而是都使用组合关系来复用父加载器的代码。

工作过程

如果一个类加载器收到了类加载的请求时,它首先不会自己去尝试加载这个类,而是把这个请求委派给自己的父类加载器去加载,每一个层次的类加载器都是如此。

因此所有的加载请求最终都应该传递到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

好处

Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类Object,它放在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给启动类加载器进行加载。

因此Object类在程序的各种类加载器环境中都是同一个类,判断两个类是否相同是通过classloader.class这种方式进行的,所以哪怕是同一个class文件如果被两个classloader加载,那么他们也是不同的类。

7、Callable 和 Runnable 有什么区别?

Callable接口比Runnable接口要新一点,它是在JDK1.5版本时发行的。

Callable接口和Runnable接口都是设计来代表一个任务(task),这个任务可以被任意线程执行, 但两者间还是有一些明显的差异。最主要的差异体现在在Callable接口可以在内部的call()方法返回执行的结果,而Runnable接口则不行。

通俗易懂的解释就是Callable接口和Runnable接口都能用来编写多线程,但实现Callable接口的任务线程能返回执行结果,而实现Runnable接口的任务线程不能返回结果。另一个明显的差别是Callable接口可以抛出checked exception,因为它的call()方法抛出了这个异常。

Callable接口通常需要和Future/FutureTask结合使用,用于获取异步任务中的结果。

总结

Callable接口比Runnable接口要新一些,前者源于JDK1.5版本.前者源于JDK1.0版本。

Runnable接口使用run()方法来描述一个任务(task),而Callable接口使用call()方法。run()方法不会返回结果, 因为它的返回类型是void,而Callable是个支持泛型的接口,当要实现(implement)一个Callable接口时就会提供一个返回值类型。run()方法不会抛出checked exception异常, 而call()方法可以。

8、Spring Boot 中如何解决跨域问题?

跨域可以在前端通过JSONP来解决,但是JSONP只可以发送GET请求,无法发送其他类型的请求。

在RESTful风格的应用中,就显得非常鸡肋,因此推荐在后端通过(CORS,Cross-origin resource sharing)来解决跨域问题。

这种解决方案并非Spring Boot特有的,在传统的SSM框架中,就可以通过CORS来解决跨域问题,只不过之前是在XML文件中配置CORS,现在可以通过实现WebMvcConfigurer接口然后重写addCorsMappings方法解决跨域问题。

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowCredentials(true)
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .maxAge(3600);
    }
}

项目中前后端分离部署,所以需要解决跨域的问题。使用cookie存放用户登录的信息,在spring拦截器进行权限控制,当权限不符合时,直接返回给用户固定的json结果。

注意:当用户退出登录状态时或者token过期时,由于拦截器和跨域的顺序有问题,出现了跨域的现象。http请求先经过filter,到达servlet后才进行拦截器的处理,如果把cors放在filter中就可以优先于权限拦截器执行。

@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(urlBasedCorsConfigurationSource);
    }
}

9、Netty 和 Tomcat 有什么区别?

作用不同:Tomcat是Servlet容器,可以视为Web服务器,而Netty是异步事件驱动的网络应用程序框架和工具用于简化网络编程,例如TCP和UDP套接字服务器。

协议不同:Tomcat是基于http协议的Web服务器,而Netty能通过编程自定义各种协议,因为Netty本身自己能编码/解码字节流,所有Netty可以实现,HTTP服务器、FTP服务器、UDP服务器、RPC服务器、WebSocket服务器、Redis的Proxy服务器、MySQL的Proxy服务器等等。

10、你都知道哪些微服务技术栈?

微服务描述 技术名称
服务开发 Springboot、Spring、SpringMVC
服务配置与管理 Netflix公司的Archaius、阿里的Diamond等
服务注册与发现 Eureka、Consul、Zookeeper等
服务调用 REST、RPC、gRPC
服务熔断器 Hystrix、Envoy等
负载均衡 Ribbon、Nginx等
服务接口调用(客户端调用服务发简单工具) Feign等
消息队列 kafka、RabbitMQ、ActiveMQ等
服务配置中心管理 SpringCloudConfig、Chef等
服务路由(API网关) Zuul等
服务监控 Zabbix、Nagios、Metrics、Spectator等
全链路追踪 Zipkin、Brave、Dapper等
服务部署 Docker、OpenStack、Kubernetes等
数据流操作开发包 SpringCloud Stream(封装与Redis,Rabbit、Kafka等发送接收消息)
事件消息总线 Spring Cloud Bus

11、TCP 中什么是粘包和拆包?

TCP的数据发送都是靠流,流由一个接一个的数据包组成。

发送过程中tcp会把数据拆成很多个包,也有可能将小的数据合成一个大包。

12、Java 中什么是 ParNew 垃圾收集器?

ParNew垃圾收集器其实是Serial收集器的多线程版本,也使用复制算法,除了使用多线程进行垃圾收集之外,其余的行为和Serial 收集器完全一样,ParNew垃圾收集器在垃圾收集过程中同样也要暂停所有其他的工作线程。

ParNew收集器默认开启和CPU数目相同的线程数,可以通过-XX:ParallelGCThreads 参数来限制垃圾收集器的线程数。

【Parallel:平行的】

ParNew虽然是除了多线程外和Serial收集器几乎完全一样,但是ParNew垃圾收集器是很多java虚拟机运行在Server模式下新生代的默认垃圾收集器。

13、Zookeeper 中什么是 ZAB 协议?

ZAB协议是为分布式协调服务Zookeeper专门设计的一种支持崩溃恢复的原子广播协议。

ZAB协议包括两种基本的模式:崩溃恢复和消息广播。

崩溃恢复:在正常情况下运行非常良好,一旦Leader出现崩溃或者由于网络原因导致Leader服务器失去了与过半Follower的联系,那么就会进入崩溃恢复模式。为了程序的正确运行,整个恢复过程后需要选举出一个新的Leader,因此需要一个高效可靠的选举方法快速选举出一个Leader。

消息广播:类似一个两阶段提交过程,针对客户端的事务请求, Leader服务器会为其生成对应的事务Proposal,并将其发送给集群中的其余所有机器,再分别收集各自的选票,最后进行事务提交。

当整个zookeeper集群刚刚启动或者Leader服务器宕机、重启或者网络故障导致不存在过半的服务器与Leader服务器保持正常通信时,所有进程(服务器)进入崩溃恢复模式,首先选举产生新的Leader服务器,然后集群中Follower服务器开始与新的Leader服务器进行数据同步,当集群中超过半数机器与该Leader服务器完成数据同步之后,退出恢复模式进入消息广播模式,Leader服务器开始接收客户端的事务请求生成事物提案来进行事务请求处理。

14、AMQP是什么?

AMQP(Advanced Message Queueing Protocol)协议是一个开放的标准的的协议,它定义了系统之间如何传递消息。

AMQP不仅定义了consumer、producer、broker之间如何交互,也定义了消息的格式和命令的交换。

RabbitMQ就是AMQP协议的Erlang的实现(当然RabbitMQ还支持STOMP2、MQTT3等协议)AMQP的模型架构和RabbitMQ的模型架构是一样的,生产者将消息发送给交换器,交换器和队列绑定。

RabbitMQ中的交换器、交换器类型、队列、绑定、路由键等都是遵循的AMQP协议中相应的概念。

目前RabbitMQ最新版本默认支持的是AMQP0-9-1。

15、说一说 Spring MVC 注解原理?

注解本质是一个继承了Annotation的特殊接口,其具体实现类是JDK动态代理生成的代理类。

通过反射获取注解时,返回的也是Java运行时生成的动态代理对象。

通过代理对象调用自定义注解的方法,会最终调用AnnotationInvocationHandler的invoke方法,该方法会从memberValues这个Map中查询出对应的值,而memberValues的来源是Java常量池。

16、Mybatis 中如何实现模糊查询 like 语句?

方式一:在Java代码中添加sql通配符。

string name = “%微信公众号“Java精选”%”;
list<name> names = mapper.selectJingXuanLike(name);
<select id=”selectJingXuanLike”>
 select * from foo where t_article like #{value}
</select>

方式二:在sql语句中拼接通配符,会引起sql注入。

string name = “微信公众号“Java精选””;
list<name> names = mapper.selectJingXuanLike(name);
<select id=”selectJingXuanLike”>
     select * from foo where t_article like '%${value}%'
</select>

17、说一说 Java 中方法区\永久代(线程共享)?

永久代(Permanent Generation)用于存储被 JVM 加载的类信息、 常量、 静态变量、 即时编译器编译后的代码等数据。

HotSpot VM把GC分代收集扩展至方法区,即使用Java堆的永久代来实现方法区,这样HotSpot的垃圾收集器就可以像管理Java堆一样管理这部分内存,而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收和类型的卸载,因此收益一般很小)。

运行时常量池(Runtime Constant Pool)是方法区的一部分。

Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池 (Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。 Java虚拟机对Class文件的每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。

18、a==b 与 a.equals(b) 有什么区别? a==b 与 a.equals(b) 有什么区别?

假设a和b都是对象

a==b是比较两个对象内存地址,当a和b指向的是堆中的同一个对象才会返回true。

a.equals(b)是比较的两个值内容,其比较结果取决于equals()具体实现。

多数情况下需要重写这个方法,如String类重写equals()用于比较两个不同对象,但是包含的字母相同的比较:

public boolean equals(Object obj) {
	if (this == obj) {// 相同对象直接返回true
		return true;
	}
	if (obj instanceof String) {
		String anotherString = (String)obj;
		int n = value.length;
		if (n == anotherString.value.length) {
			char v1[] = value;
			char v2[] = anotherString.value;
			int i = 0;
			while (n-- != 0) {
				if (v1[i] != v2[i])
					return false;
				i++;
			}
			return true;
		}
	}
	return false;
}

19、应用服务 8080 端口被意外占用如何解决?

1)按键盘WIN+R键,打开后在运行框中输入“CMD”命令,点击确定。

2)在CMD窗口,输入“netstat -ano”命令,按回车键,即可查看所有的端口占用情况。

3)找到本地地址一览中类似“0.0.0.0:8080”信息,通过此列查看8080端口对应的程序PID。

4)打开任务管理器,详细信息找到对应的应用PID(若不存在通过设置可以调出来),右键结束任务即可。

20、JSP 中静态包含和动态包含有什么区别?

动态包含

<jsp:include page="jingxuan.jsp" flush="true"/> 

比如a.jsp动态导入了b.jsp,只有当服务器访问a.jsp中的b.jsp模块时,java才会编译执行b.jsp文件,将其结果动态包含进来。

1、会将多个jsp页面分别再编写成java文件,编译成class文件。
2、jsp文件中允许有相同的变量名,每个页面互不影响。
3、当java代码比较多优先选用动态导入。
4、效率相对较低,耦合性低。

动态包含用于加载经常变化的、要求显示最新版本内容的数据。

静态包含

<%@ include file="jingxuan.htm"%> 

1、会将多个jsp页面合成一个jsp页面,再编写成java文件,编译成class文件。
2、jsp文件中不允许有相同的变量名。
3、当java代码比较少或者没有java代码是优先选用静态导入。
4、效率相对较高,耦合性高。

静态包含一般用于加载进页面显示后就再也不变的数据。

上一篇:docker 安装consul


下一篇:kafka的用法问题