JAVA常见面试题问题简述(持续更新中)
JAVA常见面试题问题简述
1. springcloud和dubbo的区别
①相比之下springcloud 的社区会更加活跃,解决问题的速度也会越来越快,dubbo相对来说如果碰到没有解决的问题,就不得不去维护框架源码。
②架构完整度来说,dubbo只是解决了服务治理,需要其他的组件,需要自己去适配。springcloud 就拥有很完善的一些列解决方案,如服务网关,断路器,分布式配置,等等
③服务的调用方式:dubbo 是rpc, springcloud 是rest API, dubbo的服务调用有很强的依赖性,因此不论开发、测试、集成环境都需要严格的管理版本依赖,才不会出现服务方与调用方的不一致导致应用无法编译成功等一系列问题。往往如果服务方有改动的话,需要更新然后install 才能进行后续开发; REST API 服务方和调用方的依赖可以说就是接口文档,如果接口修改了,文档没有及时修改也很有可能出现问题,所以最好就是集成swagger,让每个服务的代码与文档一体化,就能解决。所以在分布式环境下,REST方式的服务依赖要比RPC方式的依赖更为灵活。
④对外提供服务时,由于不知道别人是什么语言写的,都会以REST的方式提供出去,但是dubbo如果要对外提供服务就需要在实现一层,将rpc转换为rest接口对外提供。
总结:dubbo就像一台组装电脑,各组件选择度是比较高的,但是很有可能因为某个组件的问题,导致整个电脑出问题。 但如果是技术大佬这都不是问题。spring cloud 就像品牌机,对各组件的整合兼容做了大量的测试,保证了机器拥有更高的稳定性。
2.谈一下spring的理解
① spring 为什么会出现:初学java 的手 jsp+servlet+mysql+jdbc 在操作dao 或者service层的时候都需要new 或者 单例模式来获取一个bean 这样不仅耦合度比较高,而且在内存大量创建实例,销毁实例,比较浪费CPU资源,所有spring就出现了,由spring来控制对象的依赖关系,对象的生命周期。
②在业务系统里除了要实现业务功能之外,还要实现如权限拦截、性能监控、事务管理等非业务功能。通常的作法是非业务的代码穿插在业务代码中,从而导致了业务组件与非业务组件的耦合。
3.谈谈springmvc的流程
①用户发起请求到前端控制器(DispatcherServlet),该控制器会过滤出哪些请求可以访问Servlet、哪些不能访问。就是url-pattern的作用,并且会加载springmvc.xml配置文件。
②前端控制器会找到处理器映射器(HandlerMapping),通过HandlerMapping完成url到controller映射的组件,简单来说,就是将在springmvc.xml中配置的或者注解的url与对应的处理类找到并进行存储,用map<url,handler>这样的方式来存储。
③HandlerMapping有了映射关系,并且找到url对应的处理器,HandlerMapping就会将其处理器(Handler)返回,在返回前,会加上很多拦截器。
④DispatcherServlet拿到Handler后,找到HandlerAdapter(处理器适配器),通过它来访问处理器,并执行处理器。
⑤执行处理器
⑥处理器会返回一个ModelAndView对象给HandlerAdapter
⑦通过HandlerAdapter将ModelAndView对象返回给前端控制器(DispatcherServlet)
⑧前端控制器请求视图解析器(ViewResolver)去进行视图解析,根据逻辑视图名解析成真正的视图(jsp),其实就是将ModelAndView对象中存放视图的名称进行查找,找到对应的页面形成视图对象
⑨返回视图对象到前端控制器。
⑩视图渲染,就是将ModelAndView对象中的数据放到request域中,用来让页面加载数据的。
⑪通过第8步,通过名称找到了对应的页面,通过第10步,request域中有了所需要的数据,那么就能够进行视图渲染了。最后将其返回即可。
4.说说JVM内存模型
内存模型:主要定义了程序中各个变量的访问规则
JAVA内存模型主要分为主存和工作线程,当工作线程需要用到主存中的数据时,需要将主存中的数据拷贝一份到工作线程中。
5.说说JAVA内存结构
- 各内存空间的理解
①方法区:当类被加载到jvm时,需要一个内存空间来记录类的信息,如类的全类名,变量名,方法名,访问权限等等。
②堆:当一个类通过new 被创建时,需要一个区域来保存类的实例对象,如 Student student = new Student(); 这时候Student的实例对象保存在堆内存中。
③虚拟机栈:当运行某个类的方法时,如果方法有局部变量那么就需要一个区域来存放局部变量,这个内存区域叫做虚拟机栈,每个线程都有自己的虚拟机栈。栈的数据机构为LIFO 后进先出。 为什么要设计这样的数据结构呢:当a方法调用b方法时,a先进栈,然后b进栈,b运行完后,继续执行a的方法。
④当底层API不是java语言写的时候,就需要调用native方法,native方法的实现根据不同的操作系统会有不同,这也是jvm夸平台的原因,比如最简单的System.out 也是调用的native方法。既然后native方法与java语言本身写的方法不同,这里就需要一个线程对应的内存空间,也就是本地方法栈,来存放native方法中的局部变量表之类的信息。
6,类加载过程
①加载:将外部的.class文件调入内存,在.class文件加载至方法区后,会在堆中创建一个java.lang.Class对象,对来封装类的信息,类加载的最终产物就是位于堆中的Class对象(注意不是目标类对象),该对象封装了类在方法区中的数据结构,并且向用户提供了访问方法区数据结构的接口,即Java反射的接口。
②将java类的二进制代码合并到JVM的运行状态之中的过程
验证:确保加载的类信息符合JVM规范,没有安全方面的问题
准备:正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配
解析:虚拟机常量池的符号引用替换为字节引用过程
概念解释:
JVM中的直接引用和符号引用
符号引用:
官方:符号引用以一组符号来描述所引用的目标。符号引用可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可,符号引用和虚拟机的布局无关。
个人理解:在虚拟机编译java类为class文件,在编译时不知道引用类的地址,这个时候多以符号来代替。在JVM加载类的解析阶段将这个符号转化为实际的内存地址。
直接引用:
官方:直接引用和虚拟机的布局是相关的,不同的虚拟机对于相同的符号引用所翻译 出来的直接引用一般是不同的。如果有了直接引用,那么直接引用的目标一定被加载 到了内存中。
个人理解:1. 直接指向目标的指针(指向对象,类变量,类方法的指针)
2. 相对偏移量(指向实例的变量,方法的指针)
3. 一个间接定位到对象的句柄。
PS:偏移量:存储单元的实际地址与其所在段的段地址之间的距离称为段内偏移,也称为“有效地址或偏移量”
③初始化
这个阶段主要是对类变量初始化,是执行类构造器的过程。
换句话说,只对static修饰的变量或语句进行初始化。
如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
7.类加载器的层次结构
Bootstrap ClassLoader:<JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中。是由C++来实现的
Extension ClassLoader:<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库
Application ClassLoader:负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用系统类加载器
8.理解双亲委派模型
采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。可能你会想,如果我们在classpath路径下自定义一个名为java.lang.SingleInterge类(该类是胡编的)呢?该类并不存在java.lang中,经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,将反向委托给子类加载器加载,最终会通过系统类加载器加载该类。但是这样做是不允许,因为java.lang是核心API包,需要访问权限,强制加载将会报出如下异常。