Java面试题总结 11数据库与JVM综合篇(附答案)
Posted GooReey
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java面试题总结 11数据库与JVM综合篇(附答案)相关的知识,希望对你有一定的参考价值。
第十一章 数据库
一、数据库的三范式是什么?
1、列不可再分;
2、每一行数据只做一件事,只与一列相关,主键;
3、每个属性都与主键有直接关系,而不是间接关系;
三大范式只是设计数据库的基本理念,可以建立冗余较小、结构合理的数据库。如果有特殊情结,当然要特殊对待,数据库设计最重要的是看需求和性能,需求>性能>表结构。
所以不能一味的追求三范式建立数据库。
二、一张自增表里面总共有 7 条数据,删除了最后 2 条数据,重启 mysql 数据库,又插入了一条数据,此时 id 是几?
一般情况下,我们创建的表类型是InnoDB。
不重启MySQL,如果新增一条记录,id是8;
重启,ID是6;因为InnoDB表只把自增主键的最大ID记录在内存中,如果重启,已删除的最大ID会丢失。
如果表类型是MyISAM,重启之后,最大ID也不会丢失,ID是8;
InnoDB必须有主键(建议使用自增主键,不用UUID,自增主键索引查询效率高)、支持外键、支持事务、支持行级锁。
系统崩溃后,MyISAM很难恢复;
综合考虑,优先选择InnoDB,MySQL默认也是InnoDB。
三、如何获取当前数据库版本?
//MySQL,,mysql -v
select version();
//Oracle
select * from v$version;
四、说一下 ACID 是什么?
ACID是数据库事务执行的四大基本要素,包括原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。
1、原子性
整个事务中的所有操作,要么全部完成,要不全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被roolback回滚到事务开始前的状态,就像这个事务从未执行过一样。
2、一致性
事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少。
3、隔离性
隔离状态执行事务,使他们好像是系统在给定时间内执行的唯一操作。
如果有两个事务,运行在相同的时间内,执行相同的功能,事务的隔离性确保每一个事务在系统中认为只有自己在使用系统。这种属性称为串行化,为了防止事务操作间的混淆,必须串行化或序列化请求,使得在同一时间仅有一个请求用于同一数据。
4、持久性
一个成功的事务将永久的改变系统的状态。
五、char 和 varchar 的区别是什么?
- char的长度是固定的,varchar的长度的可变的;
- char的效率比varchar的效率高;
- char占用空间比varchar大,char在查询时需要使用trim;
六、float 和 double 的区别是什么?
1、float 和 double 的区别是什么?
(1)内存中占有的字节数不同
单精度浮点数在内存中占有4个字节;
双精度浮点数在内存中占有8个字节;
(2)有效数字位数不同
单精度浮点数有效数字8位;
双精度浮点数有效数字16位;
(3)数值取值范围不同
单精度浮点数的表示范围:-3.40E+38~3.40E+38
双精度浮点数的表示范围:-1.79E+308~-1.79E+308
(4)在程序中处理速度不同
一般来说,CPU处理单精度浮点数的速度比双精度浮点数的速度快
如果不声明,默认小数是double类型,如果想用float,要进行强转;
2、例如
float f = 1.3;会编译报错,正确的写法是float f = (float)1.3;或者float a = 1.3f;(f或F都可以不区分大小写)
3、注意
float是八位有效数字,第七位会四舍五入;
4、面试题
(1)java中3*0.1==0.3将会返回什么?true还是false?
答:返回false,因为浮点数不能完全精确的表示出来,一般会损失精度;
(2)java中float f = 3.4;是否正确?
答:不正确。因为3.4是双精度浮点数,将双精度赋给单精度属于向下转型,会造成精度损失,因此需要强制类型转换float=(float)3.4;或者写成float f = 3.4f;
七、Oracle分页sql
#不带排序的
SELECT * FROM (
SELECT ROWNUM AS rowno, t.* FROM worker t where ROWNUM <=20) table_alias
WHERE table_alias.rowno > 10;
#带排序的
SELECT * FROM (
SELECT tt.*, ROWNUM AS rowno FROM (
SELECT t.* FROM worker t ORDER BY wkid aSC) tt WHERE ROWNUM <= 20) table_alias
WHERE table_alias.rowno >= 10;
八、数据库如何保证主键唯一性
1、主键约束
主键列上没有任何两行具有相同值(即重复值),不允许空(NULL);
2、唯一性约束
保证一个字段或者一组字段里的数据都与表中其它行的对应数据不同。和主键约束不同,唯一性约束允许为null,但是只能有一行;
3、唯一性索引
不允许具有索引值相同的行,从而禁止重复的索引和键值;
4、三者的区别
- 约束是用来检查数据的正确性;
- 索引是用来优化查询的;
- 创建唯一性约束会创建一个约束和一个唯一性索引;
- 创建唯一性索引只会创建一个唯一性索引;
- 主键约束和唯一性约束都会创建一个唯一性索引。
九、如何设计数据库
1、数据库设计最起码要占用这个项目开发的40%以上的时间
2、数据库设计不仅仅停留在页面demo的表面
页面内容所需字段,在数据库设计中只是一部分,还有系统运转、模块交互、中转数据、表之间的联系等等所需要的字段,因此数据库设计绝对不是简单的基本数据存储,还有逻辑数据存储。
3、数据库设计完成后,项目80%的设计开发都要存在你的脑海中
每个字段的设计都要有他存在的意义,要清楚的知道程序中如何去运用这些字段,多张表的联系在程序中是如何体现的。
4、数据库设计时就要考虑效率和优化问题
数据量大的表示粗粒度的,会冗余一些必要字段,达到用最少的表,最弱的表关系去存储海量的数据。大数据的表要建立索引,方便查询。对于含有计算、数据交互、统计这类需求时,还有考虑是否有必要采用存储过程。
5、添加必要的冗余字段
像创建时间、修改时间、操作用户IP、备注这些字段,在每张表中最好都有,一些冗余的字段便于日后维护、分析、拓展而添加。
6、设计合理的表关联
若两张表之间的关系复杂,建议采用第三张映射表来关联维护两张表之间的关系,以降低表之间的直接耦合度。
7、设计表时不加主外键等约束关联,系统编码阶段完成后再添加约束性关联
8、选择合适的主键生成策略
数据库的设计难度其实比单纯的技术实现难很多,他充分体现了一个人的全局设计能力和掌控能力,最后说一句,数据库设计,很重要,很复杂。
十、性别是否适合做索引
区分度不高的字段不适合做索引,因为索引页是需要有开销的,需要存储的,不过这类字段可以做联合索引的一部分。
十一、如何查询重复的数据
1、查询重复的单个字段(group by)
select 重复字段A, count(*) from 表 group by 重复字段A having count(*) > 1
2、查询重复的多个字段(group by)
select 重复字段A, 重复字段B, count(*) from 表 group by 重复字段A, 重复字段B having count(*) > 1
3、删除所有重复的数据
-- 慎重考虑后执行,后悔记得及时回滚。
delete from table group by 重复字段 having count(重复字段) > 1
十二、数据库一般会采取什么样的优化方法?
1、选取适合的字段属性
- 为了获取更好的性能,可以将表中的字段宽度设得尽可能小。
- 尽量把字段设置成not null
- 执行查询的时候,数据库不用去比较null值。
- 对某些省份或者性别字段,将他们定义为enum类型,enum类型被当做数值型数据来处理,而数值型数据被处理起来的速度要比文本类型块很多。
2、使用join连接代替子查询
3、使用联合union来代替手动创建的临时表
注意:union用法中,两个select语句的字段类型要匹配,而且字段个数要相同。
4、事务
要么都成功,要么都失败。
可以保证数据库中数据的一致性和完整性。事务以begin开始,commit关键字结束。
如果出错,rollback命令可以将数据库恢复到begin开始之前的状态。
事务的另一个重要作用是当多个用户同时使用相同的数据源时,它可以利用锁定数据库的方式为用户提供一种安全的访问方式,这样就可以保证用户的操作不被其他的用户干扰。
5、锁定表
尽管事务是维护数据库完整性的一个非常好的方法,但却因为它的独占性,有时会影响数据库的性能,尤其是在大应用中。
由于在事务执行的过程中,数据库会被锁定,因此其它用户只能暂时等待直到事务结束。
有的时候可以用锁定表的方法来获得更好的性能,
共享锁:其它用户只能看,不能修改
lock table person in share mode;
对于通过lock table 命令主动添加的锁来说,如果要释放它们,只需发出rollback命令即可。
6、使用外键
锁定表的方法可以维护数据的完整性,但是它却不能保证数据的关联性,这个时候可以使用外键。
7、使用索引
索引是提高数据库查询速度的常用方法,尤其是查询语句中包含max()、min()、order by这些命令的时候,性能提高更为显著。
一般来说索引应该建在常用于join、where、order by的字段上。尽量不要对数据库中含有大量重复的值得字段建立索引。
8、优化的查询语句
在索引的字段上尽量不要使用函数进行操作。
尽量不要使用like关键字和通配符,这样做法很简单,但却是以牺牲性能为代价的。
避免在查询中进行自动类型转换,因为类型转换也会使索引失效。
十三、索引怎么定义,分哪几种
- b-tree索引,如果不建立索引的情况下,oracle就自动给每一列都加一个B 树索引;
- normal:普通索引
- unique:唯一索引
- bitmap:位图索引,位图索引特定于只有几个枚举值的情况,比如性别字段;
- 基于函数的索引
十四、mysql 的内连接、左连接、右连接有什么区别?
- 内连接,显示两个表中有联系的所有数据;
- 左链接,以左表为参照,显示所有数据,右表中没有则以null显示
- 右链接,以右表为参照显示数据,,左表中没有则以null显示
第十二章 RabbitMQ
一、RabbitMQ的使用场景有哪些?
1、解决异步问题
例如用户注册,发送邮件和短信反馈注册成功,可以使用RabbitMQ消息队列,用户无需等待反馈。
2、服务间解耦
订单系统和库存系统,中间加入RabbitMQ消息队列,当库存系统出现问题时,订单系统依旧能正常使用,降低服务间耦合度。
3、秒杀系统
利用RabbitMQ的最大值,实现秒杀系统。
二、RabbitMQ有哪些重要的角色?有哪些重要的组件?
1、RabbitMQ有哪些重要的角色?
客户端、RabbitMQ、服务端。
2、有哪些重要的组件?
(1)connectionFactory(连接管理器)
应用程序与RabbitMQ之间建立连接的管理器。
(2)Channel(信道)
消息推送使用的信道。
(3)RoutingKey(路由键)
用于把生产者的数据分配到交换机上。
(4)Exchange(交换机)
用于接受和分配消息。
(5)BindKey(绑定键)
用于把交换机的消息绑定到队列上
(6)Queue(队列)
用于存储生产者消息。
三、RabbitMQ中 vhost 的作用是什么?
vhost可以理解为mini版的RabbitMQ,其内部均含有独立的交换机、绑定、队列,最重要的是拥有独立的权限系统,可以做到vhost范围内的用户控制。从RabbitMQ全局考虑,不同的应用可以跑在不同的vhost上,作为不同权限隔离的手段。
第十三章 JVM
一、说一下 jvm 的主要组成部分?及其作用?
JVM包括类加载子系统、堆、方法区、栈、本地方法栈、程序计数器、直接内存、垃圾回收器、执行引擎。
1、类加载子系统
类加载子系统负责加载class信息,加载的类信息存放于方法区中。
2、直接内存
直接内存是在Java堆外的、直接向系统申请的内存空间。访问直接内存的速度会由于Java堆。出于性能的考虑,读写频繁的场合可能会考虑使用直接内存。
3、垃圾回收器
垃圾回收器可以对堆、方法区、直接内存进行回收。
4、执行引擎
执行引擎负责执行虚拟机的字节码,虚拟机会使用即时编译技术将方法编译成机器码后再执行。
二、说一下 jvm 运行时数据区?
运行时数据区包括堆、方法区、栈、本地方法栈、程序计数器。
1、堆
堆解决的是对象实例存储的问题,垃圾回收器管理的主要区域。
2、方法区
方法区可以认为是堆的一部分,用于存储已被虚拟机加载的信息,常量、静态变量、即时编译器编译后的代码。
3、栈
栈解决的是程序运行的问题,栈里面存的是栈帧,栈帧里面存的是局部变量表、操作数栈、动态链接、方法出口等信息。
(1)栈帧
每个方法从调用到执行的过程就是一个栈帧在虚拟机栈中入栈到出栈的过程。
(2)局部变量表
用于保存函数的参数和局部变量。
(3)操作数栈
操作数栈又称操作栈,大多数指令都是从这里弹出数据,执行运算,然后把结果压回操作数栈。
4、本地方法栈
与栈功能相同,本地方法栈执行的是本地方法,一个Java调用非Java代码的接口。
5、程序计数器(PC寄存器)
程序计数器中存放的是当前线程所执行的字节码的行数。JVM工作时就是通过改变这个计数器的值来选取下一个需要执行的字节码指令。
三、什么是类加载器,类加载器有哪些?
1、什么是类加载器?
类加载器负责加载所有的类,其为所有被载入内存的类生成一个java.lang.Class实例对象。
2、类加载器有哪些?
JVM有三种类加载器:
(1)启动类加载器
该类没有父加载器,用来加载Java的核心类,启动类加载器的实现依赖于底层操作系统,属于虚拟机实现的一部分,它并不继承自java.lang.classLoader。
(2)扩展类加载器
它的父类为启动类加载器,扩展类加载器是纯java类,是ClassLoader类的子类,负责加载JRE的扩展目录。
(3)应用程序类加载器
它的父类为扩展类加载器,它从环境变量classpath或者系统属性java.lang.path所指定的目录中加载类,它是自定义的类加载器的父加载器。
四、说一下类加载的执行过程?
当程序主动使用某个类时,如果该类还未被加载到内存中,JVM会通过加载、连接、初始化3个步骤对该类进行类加载。
1、加载
加载指的是将类的class文件读入到内存中,并为之创建一个java.lang.Class对象。
类的加载由类加载器完成,类加载器由JVM提供,开发者也可以通过继承ClassLoader基类来创建自己的类加载器。
通过使用不同的类加载器可以从不同来源加载类的二进制数据,通常有如下几种来源:
- 从本地文件系统加载
- 从jar包加载
- 通过网络加载
- 把一个Java源文件动态编译,并执行加载
2、连接
当类被加载之后,系统为之生成一个对应的Class对象,接着进入连接阶段,连接阶段负责将类的二进制数据合并到JRE中。
类连接又可分为三个阶段:
(1)验证
文件格式验证
元数据验证
字节码验证
符号引用验证
(2)准备
为类的静态变量分配内存,并设置默认初始值。
(3)解析
将类的二进制数据中的符号引用替换成直接引用。
3、初始化
为类的静态变量赋予初始值。
五、JVM的类加载机制是什么?
JVM类加载机制主要有三种:
1、全盘负责
类加载器加载某个class时,该class所依赖的和引用其它的class也由该类加载器载入。
2、双亲委派
先让父加载器加载该class,父加载器无法加载时才考虑自己加载。
3、缓存机制
缓存机制保证所有加载过的class都会被缓存,当程序中需要某个class时,先从缓存区中搜索,如果不存在,才会读取该类对应的二进制数据,并将其转换成class对象,存入缓存区中。
这就是为什么修改了class后,必须重启JVM,程序所做的修改才会生效的原因。
六、什么是双亲委派模型?
如果一个类收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器执行,如果父加载器还存在其父加载器,则进一步向上委托,依次递归,请求将最终到达顶层的启动类加载器,如果父类加载器可以完成父加载任务,就成功返回,如果父加载器无法完成加载任务,子加载器才会尝试自己去加载,这就是双亲委派模型。
双亲委派模式的优势:
- 避免重复加载;
- 考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委派模式传递到启动加载器,而启动加载器在核心Java API中发现同名的类,发现该类已经被加载,就不会重新加载网络传递的Integer类,而直接返回已加载过的Integer.class,这样可以防止核心API库被随意篡改。
七、怎么判断对象是否可以被回收?
1、引用计数算法
(1)判断对象的引用数量
通过判断对象的引用数量来决定对象是否可以被回收;
每个对象实例都有一个引用计数器,被引用+1,完成引用-1;
任何引用计数为0的对象实例可以被当做垃圾回收;
(2)优缺点
优点:执行效率高,程序受影响较小;
缺点:无法检测出循环引用的情况,导致内存泄漏;
2、可达性分析算法
通过判断对象的引用链是否可达来决定对象是否可以被回收。
如果程序无法再引用该对象,那么这个对象肯定可以被回收,这个状态称为不可达。
那么不可达状态如何判断呢?
答案是GC roots,也就是根对象,如果一个对象无法到达根对象的路径,或者说从根对象无法引用到该对象,该对象就是不可达的。
以下三种对象在JVM中被称为GC roots,来判断一个对象是否可以被回收。
(1)虚拟机栈的栈帧
每个方法在执行的时候,JVM都会创建一个相应的栈帧(操作数栈、局部变量表、运行时常量池的引用),当方法执行完,该栈帧就从栈中弹出,这样一来,方法中临时创建的独享就不存在了,或者说没有任何GC roots指向这些临时对象,这些对象在下一次GC的时候便会被回收。
(2)方法区中的静态属性
静态属性数据类属性,不属于任何实例,因此该属性自然会作为GC roots。这要这个class在,该引用指向的对象就一直存在,class也由被回收的时候。
class何时会被回收?
- 堆中不存在该类的任何实例
- 加载该类的classLoader已经被回收
- 该类的java.lang.class对象没有在任何地方被引用,也就是说无法通过反射访问该类的信息
(3)本地方法栈引用的对象
八、说一下 jvm 有哪些垃圾回收算法?
1、对象是否已死算法
- 引用计数器算法
- 可达性分析算法
2、GC算法
(1)标记清除算法
如果对象被标记后进行清除,会带来一个新的问题--内存碎片化。如果下次有比较大的对象实例需要在堆上分配较大的内存空间时,可能会出现无法找到足够的连续内存而不得不再次触发垃圾回收。
(2)复制算法(Java堆中新生代的垃圾回收算法)
- 先标记待回收内存和不用回收内存;
- 将不用回收的内存复制到新的内存区域;
- 就的内存区域就可以被全部回收了,而新的内存区域也是连续的;
缺点是损失部分系统内存,因为腾出部分内存进行复制。
(3)标记压缩算法(Java堆中老年代的垃圾回收算法)
对于新生代,大部分对象都不会存活,所以复制算法较高效,但对于老年代,大部分对象可能要继续存活,如果此时使用复制算法,效率会降低。
标记压缩算法首先还是标记,将不用回收的内存对象压缩到内存一端,此时即可清除边界处的内存,这样就能避免复制算法带来的效率问题,同时也能避免内存碎片化的问题。
老年代的垃圾回收算法称为“Major GC”。
九、说一下 jvm 有哪些垃圾回收器?
十、JVM栈堆概念,何时销毁对象
- 类在程序运行的时候就会被加载,方法是在执行的时候才会被加载,如果没有任何引用了,Java自动垃圾回收,也可以用System.gc()开启回收器,但是回收器不一定会马上回收。
- 静态变量在类装载的时候进行创建,在整个程序结束时按序销毁;
- 实例变量在类实例化对象时创建,在对象销毁的时候销毁;
- 局部变量在局部范围内使用时创建,跳出局部范围时销毁;
十一、新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?
新生代回收器:Serial、ParNew、Parallel Scavenge
老年代回收器:Serial Old、Parallel Old、CMS
新生代回收器一般采用的是复制算法,复制算法效率较高,但是浪费内存;
老生代回收器一般采用标记清楚算法,比如最常用的CMS;
十一、详细介绍一下 CMS 垃圾回收器?
CMS 垃圾回收器是Concurrent Mark Sweep,是一种同步的标记-清除,CMS分为四个阶段:
初始标记,标记一下GC Root能直接关联到的对象,会触发“Stop The World”;
并发标记,通过GC Roots Tracing判断对象是否在使用中;
重新标记,标记期间产生对象的再次判断,执行时间较短,会触发“Stop The World”;
并发清除,清除对象,可以和用户线程并发进行;
十二、简述分代垃圾回收器是怎么工作的?
分代回收器分为新生代和老年代,新生代大概占1/3,老年代大概占2/3;
新生代包括Eden、From Survivor、To Survivor;Eden区和两个survivor区的 的空间比例 为8:1:1 ;
垃圾回收器的执行流程:
- 把 Eden + From Survivor 存活的对象放入 To Survivor 区;
- 清空 Eden + From Survivor 分区,From Survivor 和 To Survivor 分区交换;
- 每次交换后存活的对象年龄+1,到达15,升级为老年代,大对象会直接进入老年代;
- 老年代中当空间到达一定占比,会触发全局回收,老年代一般采取标记-清除算法;
往期精彩内容:
以上是关于Java面试题总结 11数据库与JVM综合篇(附答案)的主要内容,如果未能解决你的问题,请参考以下文章