下面是白凤2020的Java面经总结,由于小白确实没什么经验和能力,很遗憾没有收到实习offer(共计5家公司,算法题另外有时间会写一篇博客),整理这些内容的初衷是记录面试的体验,以后方便复习,希望对大家有所帮助,少踩一些坑,成功拿到心仪的offer>>>
堆、栈、堆栈、队列
堆
-
堆通常是一个可以被看做一棵树的数组对象,满足下列性质
- 堆中的某个节点的值总是不大于或者不小于其父节点的值
- 堆总是一颗完全二叉树
将根节点最大的堆叫做最大堆或者大根堆,根节点最小的堆叫做最小堆或者小根堆。常见的堆有二叉堆、斐波那契堆等
-
堆是在程序运行时,而不是在程序编译时,申请某个大小的内存空间。即动态分配内存,对其访问和对一般内存的访问没有区别
-
堆是应用程序在运行的时候请求操作系统分配给自己内存,一般是申请/给予的过程
-
堆是指程序运行时申请的动态内存,而栈只是一种使用堆的方法(即先进后出)
栈
- 栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。
- 栈就是一个桶(先进后出)
- 栈是操作系统在建立某个进程或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有LIFO的特性,在编译的时候可以指定需要的Stack的大小
堆栈
注意:其实堆栈本身就是栈,只是换了个抽象的名字
堆栈的特性:最后一个放入堆栈中的物体总是被最先拿出来,这个特性通常被称为后进先出(LIFO)队列。堆栈中定义了操作,PUSH和POP
堆、栈区别总结
- 堆栈空间分配
- 栈(操作系统):由操作系统自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈
- 堆(操作系统):一般由程序员分配释放,若程序员不释放,程序结束后可能由OS回收,分配方式类似于链表
- 堆栈缓存方式
- 栈使用的是一级缓存,它们通常都是被调用时处于存储空间中,调用完毕立即释放
- 堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来说低一些
- 堆栈数据结构的区别
- 堆(数据结构):堆可以被看成是一颗树,如:堆排序
- 栈(数据机构):先进后出
队列
- 队列是一种特殊的线性表,允许表的前端(front)进行删除,后端(rear)进行插入,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除的端称为队头
- 队列中没有元素时,称为空队列
- 建立顺序队列结构必须为其静态分配或动态申请一片连续的存储空间,并设置两个指针进行管理。front指向队头元素,rear指向队尾(即下一个入队元素的存储位置)
- 队列采用的FIFO(first in first out), 动态创建,动态释放,因此不存在溢出情况。由于链表由结构体间接而成,遍历方便
堆、栈、队列之间的区别
- 堆是在程序运行时,而不是在程序编译时,申请某个大小的内存空间。即动态分配内存,对其访问和对一般内存的访问没有区别
- 栈就是一个桶(LIFO)
- 队列FIFO
重载和重写
重载
方法名相同,但是参数必须有区别(参数不同可以使类型不同,顺序不同,个数不同)
重写
又称覆盖(Override),子类基础父类的方法,并重新实现该方法
- 方法重写时,必须存在继承关系
- 方法重写时,方法名和形式参数必须一致
- 方法重写时,子类的权限修饰符需要大于或等于父类的权限修饰符
- 方法重写时,子类的返回值类型必须小于或等于父类的返回值类型
- 方法重写时,子类的异常类型要小于父类的异常类型
Java对象的生命周期
Java对象的生命周期主要包括以下七个阶段:
- 创建阶段(Created)
- 应用阶段(In Use)
- 不可见阶段(Invisible)
- 不可达阶段(Unreachable)
- 收集阶段(Collected)
- 终结阶段(Finalized)
- 对象空间重分配阶段(De-allocted)
1、创建阶段
在创建阶段系统通过下面的几个步骤来完成对象的创建过程
- 为对象分配存储空间
- 开始构造对象
- 从超类到子类对static成员进行初始化
- 超类成员变量按顺序初始化,递归调用超类的构造方法
- 子类成员变量按顺序初始化,子类构造方法调用
一旦这些对象被创建,并给分派给某些变量赋值,这个对象的状态就切换到了应用阶段
2、应用阶段
对象至少被一个强引用持有者
3、不可见阶段
当一个对象处于不可见阶段时,说明程序本身不再持有该对象的任何强引用,虽然这些引用仍然是存在的,简单来说就是程序的执行已经超出该对象的作用域了
4、不可达阶段
对象处于不可达阶段是指该对象不再被任何强引用所持有
5、收集阶段
在分配该对象时,JVM需要在垃圾回收器上注册该对象,以便在回收时能够执行该重载方法;在该方法的执行时需要消耗CPU时间且在执行完该方法后会重新执行回收操作,即至少需要垃圾回收器对该对象执行两次GC
注意:
不要重载finazlie()方法,原因有两点
- 会影响JVM的对象分配与回收
- 可能造成该对象的再次“复活”
6、终结阶段
当对象执行完finalize()方法后仍然处于不可达状态时,则该对象进入终结阶段。在该阶段是等待垃圾回收器对该对象空间进行垃圾回收
7、对象空间重新分配阶段
垃圾回收器对该对象的所占用的内存空间进行回收或者再分配了,则该对象彻底消失了,称之为“对象空间重新分配阶段”
Jvm内存区域
- 方法区:在java的虚拟机中存放已经加载的类信息、常量、静态变量以及方法代码的区域
- 常量池:常量池是方法区的一部分,主要用来存放常量和类中的符号引用等信息
- 堆区:用于存放类的实例对象
- 栈区:也叫java虚拟机栈, 是由一个一个的栈帧组成的后进先出的栈式结构,栈帧中存放方法运行时产生的局部变量、方法出口等信息。当调用一个方法时,虚拟机栈中就会存放这些数据,当方法调用完成时,栈帧消失,如果方法中调用了其他方法,则继续在栈顶创建新的栈帧
- 本地方法栈
- 程序计数器
Java类的生命周期
当我们编写一个java的源文件后,经过编译会生成一个后缀名为class的文件,这种文件叫做字节码文件,只有这种字节码文件才能在java虚拟机中运行,java类的生命周期就是指一个class文件从加载到卸载的全过程。
一个java类的完整的生命周期会经历加载、连接、初始化、使用、卸载五个阶段,当然也有在加载或者连接之后没有被初始化就直接使用的情况
1、加载
java找到需要加载的类并把类的信息加载到jvm方法区中,然后在堆区中实例化一个java.lang.Class对象,作为方法区中这个类的信息的入口
java类的加载方式
- 根据类的全路径名找到相应的class文件
- 从jar文件中读取
- 从网络中读取
- 根据一定的规则生成,比如设计模式中的动态代理模式,就是根据相应的类自动生成它的代理类
- 从非class文件中获取
2、连接
这个阶段相对较复杂,一般会根据加载阶段和初始化阶段交叉进行,这个阶段的主要任务就是做一些加载后的验证工作及一些初始化前的准备工作,可以细分为三个步骤:验证、准备和解析
-
验证
类加载后必须要验证这个类是否合法,保证加载的类能被jvm运行
-
准备
为类的静态变量分配内存并设为jvm默认的初值,对于非静态的变量,则不会为它们分配内存。需要注意的是,静态变量的初值为jvm默认的初值,而不是我们在程序中设定的值。如基本类型为0, boolean为false,引用类型为null,常量默认值为程序设定值(final static)
-
解析
把常量池中的符号引用转换为直接引用
连接阶段完成后会根据使用的情况(直接引用还是被动引用)来选择是否对类进行初始化
3、初始化
如果一个类被直接引用,就会触发类的初始化。在java中,直接引用的情况有:
- 通过new关键字实例化对象、读取或设置类的静态变量、调用类的静态方法等
- 通过反射方式执行以上三种行为
- 初始化子类的时候,会触发父类的初始化
- 作为程序入口直接运行时(如直接调用main()方法)
除了以上四种情况,其他使用类的方法叫做被动引用,而被动引用不会触发类的初始化
类的初始化过程:按照顺序自上而下运行类中的变量赋值语句和静态语句,如果有父类,则首按照顺序运行父类中的变量赋值语句和静态语句
static{
System.out.println("原来static可以这样用~");
}
在类的初始化阶段,只会初始化与类相关的静态赋值语句和静态语句,也就是有static关键字修饰的信息,而没有static修饰的赋值语句和执行语句在实例化对象的时候才会运行
4、使用
类的使用包括主动引用和被动引用,主动引用在初始化部分已说了,以下为被动引用
- 引用父类的静态字段,只会引起父类的初始化,而不会引起子类的初始化
- 定义类数组,不会引起类的初始化
- 引用类的常量,不会引起类的初始化
5、卸载
类在使用完后若满足下面的情况,就会被卸载
- 该类的所有对象都已被回收,也就是java堆中不存在该类的任何实例
- 加载该类的ClassLoader已经被回收
- 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法
如果以上三个条件全部满足,jvm就会在方法区垃圾回收的时候对类进行卸载,类的卸载过程其实就是在方法区中清空类信息,java类的整个生命周期就结束了
Java类对象的引用
- 强引用(StrongReference)
这是Java最常见的引用方式,创建一个对象,并把它赋给一个引用变量,程序通过该变量来操作实际的对象,当一个对象被其他引用变量引用时,它处于激活状态,就不可能被垃圾回收
- 软引用(SoftReference)
需要通过SoftReference类来实现,当一个对象只具有软引用时,它有可能被回收,即当内存足够时,它不会被回收;内存不足时,系统将会回收它。所以软引用通常用于对内存敏感的程序中
- 弱引用(WeekReference)
需要通过WeekReference类来实现,它和软引用很像,但比软引用级别更低。对于只有弱引用的对象而言,当系统垃圾回收机制运行时,不管内存是否足够,该对象所占的内存总会被回收,但并不是说就立即回收
- 虚引用(PhantomReference)
需要通过PhantomReference实现,虚引用完全类似没有引用,主要用于跟踪对象被垃圾回收的状态,它不能单独使用,必须和引用队列(ReferenceQueue)联合使用
上面三个类都有一个get方法用于获取它们所引用的对象
使用这些类可以避免在程序运行期间将对象留在内存中,这样垃圾收集器就可以随意地释放对象,可以尽可能减少程序在生命周期中所占用的内存大小
Jvm理解
由java语言编写的程序需要经过编译步骤,但这个编译步骤并不会生成特定的机器码,而是生成一种由平台无关的字节码(*.class文件)。这种字节码不是可执行的,必须使用java解释器来执行。所有java语言既不是纯粹的编译型语言,也不是纯粹的解释型语言。java程序的执行过程必须经过先编译,后解释两个步骤
Java语言里负责解释执行字节码文件的是Java虚拟机,即jvm(Java Virtual Manchine java虚拟机)。所有平台上的jvm向编译器提供相同的编程接口,而编译器只需要面向虚拟机,生成虚拟机能理解的代码,然后由虚拟机来解释执行。当使用java编译器编译java程序时,生成的是与平台无关的字节码,这些字节码不面向任何平台,只面向jvm。
Jvm是一个抽象的计算机,和实际的计算机一样,它具有指令集并使用不同的存储区域。负责执行指令,还要管理数据、内存和寄存器
线程池工作原理
作用
- 降低资源消耗(线程池的重用)
- 提高响应速度
- 提高线程的可管理型 (控制并发数等···)
数据库连接池
数据库连接池(Connection polling)是程序启动时建立足够的数据库连接,并将这些连接组成一个连接池,由程序动态地对池中的链接进行申请,使用,释放。
一、机制
一般来说,java应用程序访问数据库的过程是
- 装载数据库驱动程序
- 通过JDBC建立数据库连接
- 访问数据库,执行SQL语句
- 断开数据库连接
使用了数据库连接池的机制
- 程序初始化时创建连接池
- 使用时间向连接池申请可用连接
- 使用完毕,将连接返还给连接池
- 程序退出时,断开所有连接,并释放资源
数据库连接池负责分配,管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重建一个
二、连接池的工作原理
连接池技术的核心思想是连接复用,通过建立一个数据库连接池以及一套连接使用、分配和管理策略,使得该连接池中 的连接可以得到高效安全的复用,避免了数据库连接频繁建立、关闭的开销
连接池的工作原理主要由三部分组成,分别为连接池的建立、连接池中连接的使用管理、连接池的关闭
-
连接池的建立
-
连接池的管理
连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对系统的性能有很大的影响
-
连接池的关闭
当应用程序退出时,关闭连接池中所有的连接,释放连接池相关的资源,该过程正好与创建相反
SQL优化
一个SQL执行时间过长,长期占用mySQL会话连接,会耗尽连接数。这样其他请求就无法建立连接。一个SQL返回时间过长,对用户体验也会造成影响
慢SQL的常见类型
-
索引问题
查询没有走索引,索引的分区度不高,问题索引,索引的选择问题,排序,索引无法命中的问题。
-
分页查询
查询结果一次返回;数据量太大时,分页的查询也很慢
-
子查询
子查询会创建临时表,一定程度上会降低数据库实例的性能,可将子查询改为连表查询
-
连表查询
连接多个表的问题,用数据量较小的表驱动数据量较多的表;移除没有必要的表;拆分为多个SQL;避免join操作
常见的优化策略
- 将难以优化的SQL隔离开,再从库中执行
- 时间上将相关任务异步化,优先级降低
- 监控慢SQL执行进程,超过一定时长,杀死该进程
- 另一方面,有些离线分析的业务,不应该使用MySQL,这些逻辑应该在数据分析套件中去实现
Spring,Spring MVC,Spring Boot区别
Spring boot 与 Spring MVC 的区别是什么?
Spring 框架就像一个家族,有众多衍生产品例如 boot、security、jpa等等。但他们的基础都是Spring 的 ioc和 aop ioc 提供了依赖注入的容器 aop ,解决了面向横切面的编程,然后在此两者的基础上实现了其他延伸产品的高级功能。Spring MVC是基于 Servlet 的一个 MVC 框架 主要解决 WEB 开发的问题,因为 Spring 的配置非常复杂,各种XML、 JavaConfig、hin处理起来比较繁琐。于是为了简化开发者的使用,从而创造性地推出了Spring boot,约定优于配置,简化了spring的配置流程
说得更简便一些:Spring 最初利用“工厂模式”(DI)和“代理模式”(AOP)解耦应用组件。大家觉得挺好用,于是按照这种模式搞了一个 MVC框架(一些用Spring 解耦的组件),用开发 web 应用( SpringMVC )。然后有发现每次开发都写很多样板代码,为了简化工作流程,于是开发出了一些“懒人整合包”(starter),这套就是 Spring Boot
Spring MVC的功能
Spring MVC提供了一种轻度耦合的方式来开发web应用
Spring MVC是Spring的一个模块,是一个web框架。通过Dispatcher Servlet, ModelAndView 和 View Resolver,开发web应用变得很容易。解决的问题领域是网站应用程序或者服务开发——URL路由、Session、模板引擎、静态Web资源等等
Spring boot的功能
Spring Boot实现了自动配置,降低了项目搭建的复杂度
众所周知Spring框架需要进行大量的配置,Spring Boot引入自动配置的概念,让项目设置变得很容易。Spring Boot本身并不提供Spring框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于Spring框架的应用程序。也就是说,它并不是用来替代Spring的解决方案,而是和Spring框架紧密结合用于提升Spring开发者体验的工具。同时它集成了大量常用的第三方库配置(例如Jackson, JDBC, Mongo, Redis, Mail等等),Spring Boot应用中这些第三方库几乎可以零配置的开箱即用(out-of-the-box),大部分的Spring Boot应用都只需要非常少量的配置代码,开发者能够更加专注于业务逻辑
Spring Boot只是承载者,辅助你简化项目搭建过程的。如果承载的是WEB项目,使用Spring MVC作为MVC框架,那么工作流程和你上面描述的是完全一样的,因为这部分工作是Spring MVC做的而不是Spring Boot
对使用者来说,换用Spring Boot以后,项目初始化方法变了,配置文件变了,另外就是不需要单独安装Tomcat这类容器服务器了,maven打出jar包直接跑起来就是个网站,但你最核心的业务逻辑实现与业务流程实现没有任何变化
所以,用最简练的语言概括就是:
-
Spring 是一个“引擎”
-
Spring MVC 是基于Spring的一个 MVC 框架 ;
-
Spring Boot 是基于Spring4的条件注册的一套快速开发整合包
final关键字
- final Map,List可以修改内容,final 常量不能修改。map和list对应是栈中存储的地址,final表示地址不能修改,但是地址对应的内存区域的值是可以修改的
- 对List或者map的add或put操作,没有修改引用。而对于字符串的修改,是整个引用都要指向一个不同的字符串。
- map和list只是个地址,final表示地址不可以修改,但地址指向的内容可以修改
- 用final修饰的方法可以重写但不能重载