面试题总结1-JVM+数据库

1、进程、线程、协程

进程:进程是程序的一次执行过程,是系统资源分配和独立运行的最小单位;

线程: 线程是进程的一个执行单元,是任务调度和系统执行的最小单位,受内核调度,是同步机制;

协程: 协程是一种用户态的轻量级线程,协程的调度完全由用户控制,不受内核调度,协程是异步机制。

2、死锁

死锁就是两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞现象,若无外力作用,他们都将无法推进下去,此时称系统处于死锁状态。

死锁产生的四个必要条件

1、互斥条件:该资源任意一个时刻只由一个线程占用

2、请求与保持条件:一个线程因请求资源而被阻塞时,对已获得的资源保持不放

3、不可抢占条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源

4、循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系

3、解决死锁

1)预防死锁-针对死锁的必要条件进行预防

2)避免死锁-在资源分配过程中,使用银行家算法避免系统进入不安全的状态,从而避免发生死锁

银行家算法:每当进程提出资源请求且系统的资源能够满足该请求时,系统将判断满足此次资源请求后系统状态是否安全,如果判断结果为安全,则给该进程分配资源,否则不分配资源,申请资源的进程将阻塞。

3)检测死锁-允许死锁的发生,但是通过系统的检测之后,采取一些措施,将死锁清除掉

4)接触死锁-该方法与检测死锁配合使用

4、JVM性能监控工具

a.jsp-主要输出JVM中运行的进程状态信息,一般默认时当前主机或服务器

b.jstack-主要用来查看某个java进程内的线程堆栈信息

c.jmap-jmap用来查看堆内存使用状况,一般结合jhat使用

d.jstat-监视虚拟机各种运行状态信息,可以显示本地或者是远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。

e.jinfo-实时地查看和调整虚拟机各项参数

f.jhat-分析内存转储快照,运行起来比较耗时,不推荐使用

g.JConsole-JMX的可视化管理工具,是一个java GUI监视工具,可以以图表化的形式显示各种数据。并可通过远程连接监视远程的服务器VM。用java写的GUI程序,用来监控VM,并可监控远程的VM,非常易用,而且功能非常强。

h.VisualVM:多合一故障管理工具

5、JVM垃圾回收机制

- 回收堆内存

  1. 如何判定对象已死

    有两种方法

    第一种方法:引用计数法

    给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。

    该方法虽然实现简单,效率高,但是该方法存在一定的缺点就是很难解决对象之间相互循环引用的问题。

    第二种方法:可达性分析算法

    通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。

    在Java技术体系里面,固定可作为GC Roots的对象包括以下几种:

    1、在虚拟机栈中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等;
    2、在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量;
    3、在方法区中常量引用的对象,譬如字符串常量池里的引用;
    4、在本地方法栈中引用的对象;
    5、JVM内部的引用,如基本数据类型对应的Class对象,常驻的异常对象(如NullPointExcepiton),以及系统类加载器;
    6、所有被同步锁(synchronized关键字)持有的对象;

    7、反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等

2.对象的级别-“引用”与对象存活有关

强引用-强引用是最传统的“引用”的定义,是指在程序代码之中普遍存在 的引用赋值,如果一个对象具有强引用,那么垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

软引用-软引用是用来描述一些还有用,但非必须的对象。

如果一个对象只具有软引用,那就类似于可有可无的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。

软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。

弱引用-弱引用也是用来描述那些非必须的对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。

虚引用-虚引用是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。

3.如何判断一个常量是废弃常量?

运行时常量池主要回收的是废弃的常量

假如在字符串常量池中存在字符串 “abc”,如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 “abc” 就是废弃常量,如果这时发生内存回收的话而且有必要的话,“abc” 就会被系统清理出常量池了。

4.如何判断一个类是无用的类?

方法区主要回收的是无用的类,类需要同时满足下面 3 个条件才能算是 “无用的类”

  • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
  • 加载该类的 ClassLoader 已经被回收。
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

5.对象死亡的过程

要宣告对象死亡,至少要经历两次标记过程:

第一次标记:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记,随后进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。假如对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,那么虚拟机将这两种情况都视为“没有必要执行”。反之,该对象将会被放置在一个名为F-Queue的队列之中,并在稍后由一条由虚拟机自动建立的、低调度优先级的Finalizer线程去执行它们的==finalize()方法。
第二次标记:稍后,收集器将对F-Queue中的对象进行第二次小规模的标记。如果对象要在
finalize()==中成功拯救自己,只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移出“即将回收”的集合。如果对象这时候还没有逃脱,那基本上它就真的要被回收了。

- 垃圾回收算法

1.标记清除算法

算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。它的主要缺点有两个:(1)执行效率不稳定,进行大量标记和清除的动作(2)内存空间碎片化
面试题总结1-JVM+数据库_第1张图片

2.标记复制算法

将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。对于多数对象都是可回收的情况,算法需要复制的就是占少数的存活对象,而且每次都是针对整个半区进行内存回收,分配内存时也就不用考虑有空间碎片的复杂情况,只要移动堆顶指针,按顺序分配即可。

这种复制回收算法的代价是将可用内存缩小为了原来的一半,空间浪费未免太多了一点。另外,如果内存中多数对象都是存活的,这种算法将会产生大量的内存间复制的开销。所以,现在的商用Java虚拟机大多都优先采用了这种收集算法去回收新生代

面试题总结1-JVM+数据库_第2张图片

改进:

Appel式回收的具体做法是把新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次分配内存只使用Eden和其中一块Survivor。发生垃圾搜集时,将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1:1,

3.标记整理算法

其中的标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
面试题总结1-JVM+数据库_第3张图片

4.分代收集器

当前虚拟机的垃圾收集都采用分代收集算法,根据对象存活周期的不同将内存分为几块。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。

比如在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择**“标记-清除”或“标记-整理”**算法进行垃圾收集。

—为什么分新生代和老年代?

提升GC效率。

- 垃圾回收器

1.Serial(串行)收集器

串行收集器是单线程收集器。它的"单线程"的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( “Stop The World” ),直到它收集结束。serial收集器的优点是没有线程交互的开销,简单且高效。Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。

新生代采用标记-复制算法,老年代采用标记-整理算法。

面试题总结1-JVM+数据库_第4张图片

2.ParNew收集器

ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。

面试题总结1-JVM+数据库_第5张图片
它是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器 外,只有它能与 CMS 收集器配合工作。

3.Paraller Scavenge收集器

Parallel Scavenge 收集器也是使用标记-复制算法的多线程收集器,Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。

4.Serial Old 收集器

Serial 收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。

5.Parallel Old 收集器

Parallel Scavenge 收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。

6.CMS 收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。

CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。

CMS 收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:

  • 初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;
  • 并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
  • 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
  • 并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。

面试题总结1-JVM+数据库_第6张图片

从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:并发收集、低停顿。但是它有下面三个明显的缺点:

  • 对 CPU 资源敏感;
  • 无法处理浮动垃圾;
  • 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。

7. G1 收集器

G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.

被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备以下特点:

  • 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。

  • 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。

  • 空间整合:与 CMS 的“标记-清理”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于“标记-复制”算法实现的。

  • 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。

    G1 收集器的运作大致分为以下几个步骤:

    • 初始标记:暂停所有的其他线程,并记录下gc roots直接能引用的对象,速度很快
    • 并发标记:从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行
    • 最终标记:对用户线程做另一个短暂的暂停,修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。
    • 筛选回收:负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。

G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来) 。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。

6、设计模式

** 1.七大原则**

  1. 单一职责原则:一个类只负责一项职责。

  2. 接口隔离原则:客户端不应该依赖它不需要的接口即一个类对另一类的依赖应该建立在最小的接口上。

  3. 依赖倒转(倒置)原则:中心思想就是面向接口编程,高层模块不应该依赖低层模块,二者都应该依赖其抽象,抽象不应该依赖细节,细节应该依赖抽象。—求对抽象进行编程,不要对实现进行编程(使用接口)

  4. 里氏替换原则:减少继承,或者在继承中子类尽量不要重写父类的方法

  5. 开闭原则:模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节。

  6. 迪米特法则:只与直接朋友通信,一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。

  7. 合成复用原则:尽量先使用组合或者聚合等关联关系来实现

2.设计模式

单例模式—只有一个实例对象

工厂模式—定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。

7、数据库优化

7.1软优化

#### 7.1.1查询语句的优化,用EXPLAIN分析一条查询语句

- 1.避免索引失效导致的全表扫描
- 2.[SQL语句](https://so.csdn.net/so/search?q=SQL语句&spm=1001.2101.3001.7020)的规范
- 3.不要在列上进行运算
- 4.不使用NOT IN和<>操作

#### 7.1.2优化子查询-使用连接(JOIN)来代替子查询(Sub-Queries)

尽量使用JOIN来代替子查询.因为子查询需要[嵌套查询](https://www.zhihu.com/search?q=嵌套查询&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType":"answer","sourceId":1898580870}),嵌套查询时会建立一张临时表,临时表的建立和删除都会有较大的系统开销,而[连接查询](https://www.zhihu.com/search?q=连接查询&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType":"answer","sourceId":1898580870})不会创建临时表,因此效率比[嵌套子查询](https://www.zhihu.com/search?q=嵌套子查询&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType":"answer","sourceId":1898580870})高.

#### 7.1.3**添加适当索引(index) [四种: 普通索引、主键索引、唯一索引unique、全文索引]**
  - 1.使用短索引
  - 2.在建有索引的字段上尽量不要使用函数进行操作
  - 3.应该注意避免在查询中让MySQL进行自动类型转换,因为转换过程也会使索引变得不起作用
  - 4.避免索引失效:
      1. 前导模糊查询不能利用索引(like '%XX'或者like '%XX%')
      2. 如果是组合索引的话,如果不按照索引的顺序进行查找,比如直接使用第三个位置上的索引而忽略第一二个位置上的索引时,则会进行全表查询,组合索引,多列索引必须满足最左匹配.
      3. 条件中有or,OR关键字的两个字段必须都是用了索引,该查询才会使用索引
      4. 索引无法存储null值,所以where的判断条件如果对字段进行了null值判断,将导致数据库放弃索引而进行全表查询.

          为什么索引列无法存储Null值?

          a.索引是有序的。NULL值进入索引时,无法确定其应该放在哪里。
      1. 应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
      2. in和 not in 也要慎用,否则会导致全表扫描
      3. 应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。
      4. 应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。

#### 7.1.4**表的设计合理化 符合三大范式(3NF)**

  1NF、列不可分;

  强调的是列的原子性,即列不能够再分成其他几列。

  2NF、非主键列完全依赖主键,不存在部分依赖;

  一是表必须有一个主键;

  二是没有包含在主键中的列必须完全依赖于主键,而不能只依赖于主键的一部分。

  3NF、非主键列必须直接依赖主键,不存在传递依赖。

  不能存在:非主键列 A 依赖于非主键列 B,非主键列 B 依赖于主键的情况。

#### 7.1.5 表字段设置合理

  应该尽量把字段设置为NOTNULL,这样在将来执行查询的时候,数据库不用去比较NULL值。

  数据字典不存汉字查询

7.2 硬优化

**7.2.1、硬件三件套(cpu,内存,磁盘)*

**7.2.2、优化数据库参数**

**7.2.3、数据库的分库分表**

8、索引失效

  • 避免索引失效:
    1. 前导模糊查询不能利用索引(like '%XX’或者like ‘%XX%’)

    2. 如果是组合索引的话,如果不按照索引的顺序进行查找,比如直接使用第三个位置上的索引而忽略第一二个位置上的索引时,则会进行全表查询,组合索引,多列索引必须满足最左匹配.

    3. 条件中有or,OR关键字的两个字段必须都是用了索引,该查询才会使用索引

    4. 索引无法存储null值,所以where的判断条件如果对字段进行了null值判断,将导致数据库放弃索引而进行全表查询.

      为什么索引列无法存储Null值?

      a.索引是有序的。NULL值进入索引时,无法确定其应该放在哪里。

    5. 应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。

    6. in和 not in 也要慎用,否则会导致全表扫描

    7. 应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。

    8. 应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。

9、慢查询

当 Mysql 性能下降时,通过开启慢查询来获得哪条 SQL 语句造成的响应过慢,进行分析处理。当然开启慢查询会带来 CPU 损耗与日志记录的 IO 开销,所以我们要间断性的打开慢查询日志来查看 Mysql 运行状态。

参考:
1、https://blog.csdn.net/dingjianmin/article/details/121718822
2、https://javaguide.cn

你可能感兴趣的:(面试题,JVM,数据库,数据库,java,面试)