[经验]java 高级面试解析

https://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&mid=2247485010&idx=1&sn=f0db6836ef75f62eb9cfe31b71762ffd&chksm=ebd6397edca1b068f2d77b810876089fee1c045549307bdf9b4248c6249e45f0656aa49477ac&mpshare=1&scene=1&srcid=0309t8WynReib9iBvEKvtRPH&key=1f0b925ccdb44e31ee6b4d1a958d1374d9a8568973377a844db3fea73bb840b439849a8d442082d91d1e042ad667740e47b8e496eb449cd235975c5db5a4fa858e100b3fc25f0ff0fa16d7610b6a4ac6&ascene=0&uin=MTA2NzUxMDAyNQ%3D%3D&devicetype=iMac+MacBookAir6%2C2+OSX+OSX+10.10.5+build(14F2511)&version=11020012&lang=zh_CN&pass_ticket=GuXAkVTedtGXn5QtvLlnzuz0cqSm9cX55zxM4txqha%2BcVH5Q4sL5roC1LR7jvW8s

 

 

JVM的内存结构

根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。

 

1、Java虚拟机栈:

线程私有;每个方法在执行的时候会创建一个栈帧,存储了局部变量表,操作数栈,动态连接,方法返回地址等;每个方法从调用到执行完毕,对应一个栈帧在虚拟机栈中的入栈和出栈。

 

2、堆:

线程共享;被所有线程共享的一块内存区域,在虚拟机启动时创建,用于存放对象实例。

 

3、方法区:

线程共享;被所有线程共享的一块内存区域;用于存储已被虚拟机加载的类信息,常量,静态变量等。

 

4、程序计数器:

线程私有;是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有”的内存。

 

5、本地方法栈:

线程私有;主要为虚拟机使用到的Native方法服务。

 

大体回答如上,类似文章请移驾:

JVM的内存区域划分

JVM知识点梳理

JVM内存分配与回收

JVM内存管理机制

 

 

数组在内存中如何分配

1、简单的值类型的数组,每个数组成员是一个引用(指针),引用到栈上的空间(因为值类型变量的内存分配在栈上)

2、引用类型,类类型的数组,每个数组成员仍是一个引用(指针),引用到堆上的空间(因为类的实例的内存分配在堆上)

 

 

springmvc的核心是什么,请求的流程是怎么处理的,控制反转怎么实现的

核心:

控制反转和面向切面

 

请求处理流程:

1、首先用户发送请求到前端控制器,前端控制器根据请求信息(如URL)来决定选择哪一个页面控制器进行处理并把请求委托给它,即以前的控制器的控制逻辑部分;

2、页面控制器接收到请求后,进行功能处理,首先需要收集和绑定请求参数到一个对象,并进行验证,然后将命令对象委托给业务对象进行处理;处理完毕后返回一个ModelAndView(模型数据和逻辑视图名);

3、前端控制器收回控制权,然后根据返回的逻辑视图名,选择相应的视图进行渲染,并把模型数据传入以便视图渲染;

4、前端控制器再次收回控制权,将响应返回给用户。

 

控制反转如何实现:

我们每次使用spring框架都要配置xml文件,这个xml配置了bean的id和class。

spring中默认的bean为单实例模式,通过bean的class引用反射机制可以创建这个实例。

因此,spring框架通过反射替我们创建好了实例并且替我们维护他们。

A需要引用B类,spring框架就会通过xml把B实例的引用传给了A的成员变量。



接口有什么用

1、通过接口可以实现不相关类的相同行为,而不需要了解对象所对应的类。

2、通过接口可以指明多个类需要实现的方法。

3、通过接口可以了解对象的交互界面,而不需了解对象所对应的类。

另:Java是单继承,接口可以使其实现多继承的功能。

 

大体回答如上,类似文章请移驾:

深入理解Java的接口和抽象类

 

说说tcp三次握手,四次挥手

 

https://blog.csdn.net/silyvin/article/details/79143761

分布式环境下的session(举例两种):

 

服务器session复制

原理:任何一个服务器上的session发生改变(增删改),该节点会把这个 session的所有内容序列化,然后广播给所有其它节点,不管其他服务器需不需要session,以此来保证Session同步。

优点:可容错,各个服务器间session能够实时响应。

缺点:会对网络负荷造成一定压力,如果session量大的话可能会造成网络堵塞,拖慢服务器性能。

 

session共享机制

使用分布式缓存方案比如memcached、redis,但是要求Memcached或Redis必须是集群。

BIO、NIO和AIO的区别

 

Java BIO : 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

 

Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。

 

Java AIO: 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。

 

NIO比BIO的改善之处是把一些无效的连接挡在了启动线程之前,减少了这部分资源的浪费(因为我们都知道每创建一个线程,就要为这个线程分配一定的内存空间)

 

AIO比NIO的进一步改善之处是将一些暂时可能无效的请求挡在了启动线程之前,比如在NIO的处理方式中,当一个请求来的话,开启线程进行处理,但这个请求所需要的资源还没有就绪,此时必须等待后端的应用资源,这时线程就被阻塞了。

 

适用场景分析:

 BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解,如之前在Apache中使用。

 

 NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持,如在 Nginx,Netty中使用。

 

 AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持,在成长中,Netty曾经使用过,后来放弃。

 

类似参考文章,请移驾:

Java中BIO,NIO,AIO的理解

 

 

 

 

 

垃圾回收器的类型:

  • 串行垃圾回收器(Serial Garbage Collector)

  • 并行垃圾回收器(Parallel Garbage Collector)

  • 并发标记扫描垃圾回收器(CMS Garbage Collector)

  • G1垃圾回收器(G1 Garbage Collector)

 

垃圾回收算法:

  • 引用计数法

  • 标记清除法

  • 复制算法

  • 标记压缩算法

  • 分代算法

  • 分区算法

 

以上这些,可以自己了解一下,这里列举几篇相关文章:

JVM的内存区域划分

JVM知识点梳理

JVM内存分配与回收

JVM内存管理机制

Java虚拟机学习 - 垃圾收集器

 
 

类在虚拟机中的加载过程

 

加载Loading:

通过一个类的全限定名来获取一个二进制字节流、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构、在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

 

验证Verification:

确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并不会危害虚拟机的自身安全。

 

准备Preparation:

正式为类变量分配内存并设置类变量初始值。

 

解析Resolution:

虚拟机将常量池内的符号引用替换为直接引用的过程。

 

初始化Initialization:

类加载过程的最后一步,到了这个阶段才真正开始执行类中定义的Java程序代码。

 

使用Using:

根据你写的程序代码定义的行为执行。

 

卸载Unloading:

GC负责卸载,这部分一般不用讨论。

 

以上这些抛砖引玉,欢迎留言更清晰的类加载过程,相关文章可以阅读:

类加载器详解

详解java类的生命周期

谈谈我对面向对象以及类与对象的理解

 

强引用、软引用、弱引用、虚引用与GC的关系

 

强引用:new出的对象之类的引用,只要强引用还在,永远不会回收。

软引用:引用但非必须的对象,内存溢出异常之前回收。 

弱引用:非必须的对象,对象只能生存到下一次垃圾收集发生之前。 

虚引用:对生存时间无影响,在垃圾回收时得到通知。

 

这个相对好理解了,相关阅读如下:

Java 如何有效地避免OOM:善于利用软引用和弱引用




说一下spring中Bean的作用域

 

singleton:

    Spring IoC容器中只会存在一个共享的Bean实例,无论有多少个Bean引用它,始终指向同一对象。Singleton作用域是Spring中的缺省作用域。

prototype:

    每次通过Spring容器获取prototype定义的bean时,容器都将创建一个新的Bean实例,每个Bean实例都有自己的属性和状态,而singleton全局只有一个对象。

request:

    在一次Http请求中,容器会返回该Bean的同一实例。而对不同的Http请求则会产生新的Bean,而且该bean仅在当前Http Request内有效。

session:

    在一次Http Session中,容器会返回该Bean的同一实例。而对不同的Session请求则会创建新的实例,该bean实例仅在当前Session内有效。

global Session:

    在一个全局的Http Session中,容器会返回该Bean的同一个实例,仅在使用portlet context时有效。

 

说一下spring中Bean的生命周期

 

  • 实例化一个Bean,也就是我们通常说的new。

  • 按照Spring上下文对实例化的Bean进行配置,也就是IOC注入。

  • 如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的是Spring配置文件中Bean的ID。

  • 如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(),传递的是Spring工厂本身(可以用这个方法获取到其他Bean)。

  • 如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文。

  • 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用After方法,也可用于内存或缓存技术。

  • 如果这个Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法。

  • 如果这个Bean关联了BeanPostProcessor接口,将会调用postAfterInitialization(Object obj, String s)方法。

  • 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean接口,会调用其实现的destroy方法。

  • 最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。

Spring框架中都用到了哪些设计模式?

 

  • 代理模式:在AOP和remoting中被用的比较多。

  • 单例模式:在spring配置文件中定义的bean默认为单例模式。

  • 模板方法模式:用来解决代码重复的问题。

  • 前端控制器模式:Spring提供了DispatcherServlet来对请求进行分发。

  • 依赖注入模式:贯穿于BeanFactory / ApplicationContext接口的核心理念。

  • 工厂模式:BeanFactory用来创建对象的实例。

 

BeanFactory 和ApplicationContext的区别

 

BeanFactory和ApplicationContext都是接口,并且ApplicationContext是BeanFactory的子接口。

 

BeanFactory是Spring中最底层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能。而ApplicationContext是Spring的一个更高级的容器,提供了更多的有用的功能。 

 

ApplicationContext提供的额外的功能:国际化的功能、消息发送、响应机制、统一加载资源的功能、强大的事件机制、对Web应用的支持等等。

 

加载方式的区别:BeanFactory采用的是延迟加载的形式来注入Bean;ApplicationContext则相反的,它是在Ioc启动时就一次性创建所有的Bean,好处是可以马上发现Spring配置文件中的错误,坏处是造成浪费。

 
 

内部类和静态内部类的区别

 

内部类:

1、内部类中的变量和方法不能声明为静态的。

2、内部类实例化:B是A的内部类,实例化B:A.B b = new A().new B()。

3、内部类可以引用外部类的静态或者非静态属性及方法。

 

静态内部类:

1、静态内部类属性和方法可以声明为静态的或者非静态的。

2、实例化静态内部类:B是A的静态内部类,A.B b = new A.B()。

3、静态内部类只能引用外部类的静态的属性及方法。

 

inner classes——内部类

static nested classes——静态嵌套类

其实人家不叫静态内部类,只是叫习惯了,从字面就很容易理解了。

内部类依靠外部类的存在为前提,而静态嵌套类则可以完全独立,明白了这点就很好理解了。

 

非静态内部类中的变量和方法不能声明为静态的原因

 

静态类型的属性和方法,在类加载的时候就会存在于内存中。使用某个类的静态属性和方法,那么这个类必须要加载到虚拟机中。但是非静态内部类并不随外部类一起加载,只有在实例化外部类之后才会加载。

 

我们设想一个场景:在外部类并没有实例化,内部类还没有加载的时候如果调用内部类的静态成员或方法,内部类还没有加载,却试图在内存中创建该内部类的静态成员,就会产生冲突。所以非静态内部类不能有静态成员变量或静态方法。

 

String,StringBuilder,StringBuffer的区别

 

String 字符串常量

StringBuffer 字符串变量(线程安全)

StringBuilder 字符串变量(非线程安全)

 

性能上通常StringBuilder > StringBuffer > String。

 

String是不可变对象,每次对String类型进行改变的时候都等同于生成了一个新的String对象,然后将指针指向新的String对象,所以性能最差,对于要经常改变内容的字符串不用String。

 

StringBuffer是字符串变量,对它操作时,并不会生成新的对象,而是直接对该对象进行更改,所以性能较好。

 

StringBuilder和StringBuffer一样,是字符串变量,但是他不带有synchronized关键字,不保证线程安全,所以性能最好。在单线程的情况下,建议使用StringBuilder。

 

总体来说:

String:适用于少量的字符串操作的情况。

StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况。

StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况。

 

来一些问题:

下面这段代码的输出结果是什么?

String a = "helloworld";
String b = "hello" + "world";
System.out.println((a == b));

 

输出结果为:True。

原因是String对字符串的直接相加,会在编译期进行优化。即hello+world在编译时期,被优化为helloworld,所以在运行时期,他们指向了同一个对象。我们也可以推理,对于直接字符串的相加,String不一定比其余两个慢。

 

下面这段代码的输出结果是什么?

String a = "helloworld";
String b = "hello";       
String c = b + "world";       
System.out.println((a == c));

 

输出结果为:False。

原因是c并非两个字符串直接相加,包含了一个字符串引用,这时不会做编译期的优化。所以a、c最终生成了两个对象,这时他的效率低。


原文地址:https://www.cnblogs.com/silyvin/p/9106617.html