一、JMM部分
1、简述运行时数据区域(运行时内存)?
JVM的运行时内存主要有5个部分,分别是:程序计数器、虚拟机栈、本地方法栈、堆、方法区,其中线程共享的有堆和方法区,其余线程不共享。
程序计数器可以看做是当前线程所执行的字节码的行号指示器,用于记录正在执行的虚拟机字节指令地址,线程私有,也是唯一一个没有规定任何OutOfMemoryError的区域。
虚拟机栈用于存储基本数据类型、对象引用、和方法出口,线程是私有的,每个方法在执行的时候都会创建一个栈帧,用于存放局部变量表、操作数栈、动态连接、方法出口等,方法从调用到执行完成的过程就对应着一个栈帧在虚拟机栈出栈到入栈的过程,虚拟机栈规定了两中异常状况:栈溢出(StackOverflowError)、内存溢出(OutOfMemoryError)。
本地方法栈与虚拟机类似,不过是用于加载本地方法的,线程私有。
堆是虚拟机内存区域中最大的一块,用于存放所有对象实例和数组,是GC管理的主要区域,堆内存内部细分为新生代和老年代,其中新生代可再细分为Eden(8)区、from区(1)、to(区),默认新生代占堆空间的1/3,老年代占2/3,当堆无法再拓展的时候,会抛内存溢出(OutOfMemoryError)。
方法区是堆的一个逻辑部分,但本质是非堆,存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据等,在HotSpot中,使用了永久代来实现了方法区,而在1.8及以后的版本中废弃了永久代,改为使用本地内存中的元空间实现,之前版本的常量池,就存储到了这个元空间中。
2、Java堆和Java栈的区别?
从存储的对象上来看,虚拟机栈存储的是基础数据类型和对象的引用,堆则存储的是对象实例和数组;
从线程隔离上来看,虚拟机栈是线程隔离的,堆是线程共享的;
从异常来看,在没有内存可分配的情况下都会抛出OOM,但虚拟机栈还有可能会抛出SOF(StackOverFlowError,栈深度超过阈值);
从内存大小来看,堆是虚拟机中最大的一块,远远大于栈内存;
3、堆内存是如何分配的?
结构(堆大小 = 新生代 + 老年代 ):
新生代(1/3)(初始对象,生命周期短):Eden 区、survivior 0、survivior 1( 8 : 1 : 1)
老年代(2/3)(长时间存在的对象)
二、垃圾回收与内存分配部分
1、什么是垃圾回收(GC)?
JVM中所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的。
GC为了能够正确释放对象,会监控每个对象的运行状况,对他们的申请、引用、被引用、赋值等状况进行监控,Java程序员不用担心内存管理,因为垃圾收集器会自动进行管理。
程序员可以手工调用:System.gc() 或Runtime.getRuntime().gc() ,但JVM可以屏蔽掉显示的垃圾回收调用。
2、都有哪些垃圾回收器,这些垃圾回收器有什么特点?
常见的是CMS,1.7后出现了G1。
CMS是一种以获取最短回收停顿时间为目标设计的收集器,有着并发收集和低停顿的特点,整体回收过程分为:初始标记、并发标记、重新标记、并发清除四个步骤,初始标记,CMS老年代主要采用标记清除算法,会造成堆的大量空间碎片,容易引发FullGC。
G1实现了并发并行,可预测的停顿,与CMS相比采用了标记整理算法,解决了GMS的一个缺陷,其次G1建立了停顿时间的预测模型,能让使用者明确在指定时间片段内回收时间不超过多少毫秒。回收的整体过程有:初始标记、并发标记、最终标记、筛选回收,前两个步骤与CMS相同,最终标记的目标也与CMS相同,但不一样的是,G1将标记过程中用户发生的变更记录到了日志中,在执行最终表的时候,将这部分日志合并到了标记中,这个阶段需要停顿,但可并行执行。
最后,在筛选执行过程中,先对回收成本进行评估,根据用户期望的停顿时间制定回收计划。
3、垃圾回收主要关注哪些区域?(内存中主要在哪些区域发生GC)
程序计数器、虚拟机栈、本地方法栈生命周期与线程一致,随线程创建而创建,随线程销毁而销毁,内存回收时不需要特别的关注。
而堆和方法区则不同,均是随着方法的调用而确定创建哪些对象,随时发生变化切不可预知,因此垃圾回收机制主要针对的是堆、和方法区。
GC主要回收“死对象” (引出如何判断对象已死?)
4、垃圾回收机制是如何判断一个对象是否可以被回收的?(如何判断对象已死)
主流实现是可达性分析算法(寻根算法)来判断对象是否已死。主要通过一个GC Roots 对象作为起点,从这些结点开始向下搜索,搜索所走过的路径称为引用链,当一个对象没有与任何的引用链相连的时候则改对象就会被判定为可回收的对象,当被标记的次数超过阈值设置的标记次数后,会被垃圾回收器回收,默认是2次。
GC Root对象主要有四种:虚拟机栈中引用的对象、本地方法栈中引用的对象、方法区中常量和静态属性引用的对象。
5、简述Java中的4中引用?
强引用(StrongReference),强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题
软引用(SoftReference),如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中
弱引用(WeakReference),弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中
虚引用(PhantomReference), 虚引用在任何时候都可能被垃圾回收器回收,主要用来跟踪对象被垃圾回收器回收的活动,被回收时会收到一个系统通知。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
6、垃圾回收器什么时候会回收?怎么回收?
当对象在新生代的Eden区申请空间失败,会触发MinorGC,垃圾回收器会使用复制算法对Eden区进行回收,会先把存活的对象移动到survivor区,然后整理两个survivor区(from和to)。
当老年代、持久代内存占用达到阈值,或代码显示调用System.gc()方法,或者具上次GC后堆的各区域分配策略发生变化,会触发一次FullGC,CMS采用标记删除,G1采用标记整理。
当永久代(或称持久带、元空间)内存占用达到阈值时会触发MajorGC,会对这部分空间进行标记整理。
7、对象的内存分配策略是如何的?
对象优先在Eden区分配;大对象直接进入老年代;长期存活的对象进入老年代(默认15次minor GC);动态-对象年龄判定:在servivor中相同年龄对象的大小总和大于servivor空间的一半,年龄大过这个的对象进入老年代,不需要等待达到MaxTenuringThreshold的阈值设定。;空间分配担保:在MinorGC前,先检查老年代最大连续空间是否大于新生代所有对象总空间, 如果条件不成立,MinorGC是不安全的,虚拟机检查设置(HandlePromitionFailure)是否允许担保失败。 如果允许,则检查老年代最大可用练习空间是否大于历次晋升到老年代的平均大小,如果大于进行MinorGC,如果小于进行FullGC
三、类加载
1、什么是类加载?
类加载就是虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可供虚拟机直接使用的Java类型。
2、类的生命周期(或说类的加载过程)?
类的声明周期或说类加载的过程共有7个阶段,分别是加载、校验、准备、解析、初始化、使用、卸载,其中验证、准备、解析这三个部分统称为连接。
加载阶段主要是获取类名获取对应的二进制字节流,将这个对应的类加载到内存中;验证阶段是确保加载的类文件的字节流是否符合规范;准备阶段则为类变量分配内存并将这些变量设置为初始值;解析阶段则是将常量池内的符号引用替换为直接应用;初始化阶段是类加载过程的最后一步。初始化阶段就是执行类构造器<clint>()方法的过程(类中的静态代码块->类的构造器->父类的构造器);使用阶段;卸载阶段。
3、哪些行为会引起类加载(或说立即对类进行初始化的5中情况)?
遇到new、getstatic、putstatic或invokestatic这四条字节码指令
反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化
子类初始化的时候,如果其父类还没初始化,则需先触发其父类的初始化
虚拟机执行主类的时候(有 main(string[] args))
JDK1.7 动态语言支
4、简述类加载器类的类型和作用?
常见的类加载器有三种,分别是启动类加载器(BootStrap ClassLoader)、拓展加载器(Extension ClassLoader)、应用类加载器(Applicaiton ClassLoader)
启动类加载器负责加载<JAVA_HOME>/lib包下的类,
拓展类加载器负责加载<JAVA_HOME>/lib/ext包下的类,
应用类加载器负责加载classpath下的类,
这三个类加载器之间的层级关系称为双亲委派模型,双亲委派的工作过程是:当一个类加载器收到了类加载的请求后不直接加载,而是将这个请求层级级的委派给父加载器,只有父类确认加载不了自加载器才尝试加载。(职责分明,不会重复加载)
5、简述双亲委派机制
加载器的层级组合关系,双亲委派模型要求除了顶层的类加载器之外,其余的类加载器都应有自己的父类加载器,这里的类加载器之间的父子关系一般不会以继承的关系来实现,通常使用组合的关系来复用父类加载器的代码。
6、双亲委派机制的工作过程
首先会先查找当前ClassLoader是否加载过此类,有就返回;
如果没有,查询父ClassLoader是否已经加载过此类,如果已经加载过,就直接返回Parent加载的类;
如果整个类加载器体系上的ClassLoader都没有加载过,才由当前ClassLoader加载(调用findClass),整个过程类似循环链表一样。
小结:先看当前是否加载过,再看父类是否加载过,再看体系上的加载器是否加载过,如果都没有,由当前的类加载器加载。
7、双亲委派模型的作用(好处)?
好处主要是共享和隔离,共享主要是指可以避免类的重复加载,当父亲已经加载了该类的时候,子类不需要再次加载,一些Framework层级的类一旦被顶层的ClassLoader加载过就缓存在内存里面,以后任何地方用到都不需要重新加载;隔离是指因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的类装载器,保证java/Android核心类库的纯净和安全,防止恶意加载。
四、数据库相关
1、什么是索引?它是由谁实现的?它的结构是什么样的?
索引是为了加速对表中数据行的检索,而创建的一种分散存储的数据结构;索引是有可插拔的存储引擎实现的;正确的创建合适的索引是提升数据库查询性能的基础;索引是以B+树的形式存储的。
2、为什么要使用索引?
索引能极大的减少存储引擎需要扫描的数据量。
索引可以把随机IO变成顺序IO。
索引可以帮助我们在进行分组、排序等操作时,避免使用临时表。
3、为什么要采用B+树作为索引的结构?
B+树是B-树的变种(PLUS版)多路绝对平衡查找树,他拥有B-树的优势;
B+树扫库、 表能力更强;
B+树的磁盘读写能力更强;
B+树的排序能力更强;
B+树的查询效率更加稳定;
4、什么是最左匹配原则?
对索引中关键字进行计算(对比),一定是从左往右依次进行,且不可跳过。
5、什么情况下有索引,但用不上?
如果条件中有or,即使其中有部分条件带索引也不会使用(这也是为什么尽量少用or的原因);
对于多列索引,不是使用的第一部分,则不会使用索引;
like查询是以%开头;
存在索引列的数据类型隐形转换,则用不上索引,比如列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引;
where子句里对索引列上有数学运算,用不上索引;
where子句里对有索引列使用函数,用不上索引;
联合索引中如果不是按照索引最左列开始查找,无法使用索引。
6、如何设计联合索引?
经常用的列优先,放到最左边。【最左匹配原则】
选择性(离散度)高的列优先,放到最左边。【离散度高原则】
宽度小的列优先,放到最左边。【最少空间原则】
7、什么是覆盖索引?
如果查询列可通过索引节点中的关键字直接返回, 则该索引称之为覆盖索引。
7、都有哪些存储引擎?
CSV存储引擎
Archive存储引擎
Memory存储引擎(也叫heap存储引擎)
Myisam存储引擎(5.5版本之前默认)
Innodb(5.5及以后默认)
8、Myisam和Innodb有什么区别?
InnoDB支持事物,而MyISAM不支持事物
InnoDB支持行级锁,而MyISAM支持表级锁
InnoDB支持MVCC, 而MyISAM不支持
InnoDB支持外键,而MyISAM不支持
InnoDB不支持全文索引,而MyISAM支持。
MyISAM内部维护了一个计数器,因此做count(*)更快
9、MySQL查询语句的执行过程
会用解析器将SQL语句解析成解析树;
然后进行预处理,预处理主要是检查解析树的合法性;
接下来查询优化器会对预处理完成的解析树进行优化,找到最优的执行计划;
最后将优化后的解析树交个执行引擎进行查询。
10、如何理解数据库事务?
是一组不可再分割的操作集合(工作逻辑单元),数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作。
11、事务的ACID特性?
原子性(Atomicity),最小的工作单元,整个工作单元要么一起提交成功,要么全部失败回滚。
一致性(Consistency),事务中操作的数据及状态改变是一致的,即写入资料的结果必须完全符合预设的规则,不会因为出现系统意外等原因导致状态的不一致。
隔离性(Isolation),一个事务所操作的数据在提交之前,对其他事务的可见性设定(一般设定为不可见)。
持久性(Durability),事务所做的修改就会永久保存,不会因为系统意外导致数据的丢失。
12、事务的并发带来了哪些问题?
脏读:(读到了回滚前) B事务对一个数值修改后的值被A事务读到了,此时B事务回滚了, 数据库中是修改前的值,A事务中是B事务修改的值;
不可重复读:(读了两次,两次不一致) A事务读取了某条数据,此时B事务对这个数据进行了修改并成功提交, 此时A事务重复对这条数据进行了查询,发现与之前的数据值不一致。
幻读:(增或删除带来的不可重复读) A事务读取某个条件范围下的数据条数,这时B插入或删除了满足这个条件的数据,此时A再次统计的时候数据不一致。
13、事务的四种隔离级别?
Read Uncommitted(未提交读)未解决并发问题,事务未提交对其他事务也是可见的;
Read Committed(提交读)解决脏读问题,一个事务开始之后,只能看到自己提交的事务所做的修改;
Repeatable Read(可重复读)解决不可重复读问题, 在同一个事务中多次读取同样的数据结果是一样的,这种隔离级别未定义解决幻读的问题(InnoDB中解决了);
Serializable(串行化)--解决所有问题,最高的隔离级别,通过强制事务的串行执行;
14、MySQL是如何解决事务问题的?
有两种方案,一种是使用锁,一种是使用MVCC
15、MySQL分别用什么锁解决了脏读、不可重复读、幻读的?
脏读:X锁,行锁中的排他锁,当事务B对一条数据进行修改的时候,给这条数据加上排他锁,此时事务A不能读取事务B修改后的值,即使事务B回滚,A也读的是之前的值。
不可重复读:S锁,行锁中的共享锁,当事务A读取一条数据的时候给这条数据加上了S锁,后续事务B就无法对这条数据进行修改,也就不存在不可重复读的问题了。
幻读:Next-key锁,锁住记录+区间(左开右闭),当事务A查询例如age>15的这种语句,会将负无穷到正无穷范围内的数据都上锁,事务B自然不能对这个区间的数据进行操作。
1.Innodb
聚集索引
所谓聚簇索引,就是指主索引文件和数据文件为同一份文件,聚簇索引主要用在Innodb存储引擎中。在该索引实现方式中B+Tree的叶子节点上的data就是数据本身,key为主键,所以主键索引实际上就是该表所有的数据。如果是普通索引的话,data便会指向对应的主索引,类似select *的查询会有回表查询的操作
查询时间稳定,性能更高
1.非叶子节点只存key,大大滴减少了非叶子节点的大小,那么每个节点就可以存放更多的记录,树更矮了,I/O操作更少了。所以B+Tree拥有更好的性能。
2.普通索引叶子节点是主键索引,b+树的树高是固定的,索引命中都在叶节点,所以每次的查询时间都是变化访问不会很大
区间访问
在B+Tree的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的B+Tree。做这个优化的目的是为了提高区间访问的性能,例如图4中如果要查询key为从18到49的所有数据记录,当找到18后,只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点,极大提到了区间查询效
最左匹配原则 (个人猜测)
联合索引是怎么构建树的,形象的比喻比如把每个字段拼接成一个字段,用单个字段的值,构建树的就跟单个字段索引的一样, 在查找节点的时候只能从最左边开始比较不能跳过字段,后面再研究
网友评论