最新高频Java八股文(全网最全)

1深拷贝和浅拷贝区别是什么?

浅拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。

深拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,无论该字段是值类型的还是引用类型,都复制独立的一份。当你修改其中一个对象的任何内容时,都不会影响另一个对象的内容。

2throw 和 throws 的区别?

throw:

(1)throw 语句用在方法体内,表示抛出异常,由方法体内的语句处理。

(2)throw 是具体向外抛出异常的动作,所以它抛出的是一个异常实例,执行 throw 一定是抛出了某种异常。

throws:

(1)throws 语句是用在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理。

(2)throws 主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道需要捕获的异常的类型。

(3)throws 表示出现异常的一种可能性,并不一定会发生这种异常。

3.final、finally、finalize 有什么区别?

1、final

Final可以用于成员变量(包括方法参数),方法、类。

Final成员

 作为变量

变量一旦被初始化便不可改变(对于基本类型,指的是值不变;对于对象类型,指的是引用不变),初始化只可能在两个地方:定义处和构造函数。

作为方法参数

对于基本类型,定义成final参数没有什么意义,因为基本类型就是传值,不会影响调用语句中的变量;对于对象类型,在方法中如果参数确认不需要改变时,定义成final参数可以防止方法中无意的修改而影响到调用方法。

Final方法

不可覆写

编译器将对此方法的调用转化成行内(inline)调用,即直接把方法主体插入到调用处(方法主体内容过多的时候反而会影响效率)

Final类

不可继承

2、finally

异常处理关键字,finally中的主体总会执行,不管异常发生是否。通常放在try…catch的后面构造最终执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。

2.1、当try中有return时执行顺序

return语句并不是函数的最终出口,如果有finally语句,这在return之后还会执行finally(return的值会暂存在栈里面,等待finally执行后再返回)

2.2、return和异常获取语句的位置

3、finalize

类的Finalize方法,可以告诉垃圾回收器应该执行的操作,该方法从Object类继承而来。在从堆中永久删除对象之前,垃圾回收器调用该对象的Finalize方法。注意,无法确切地保证垃圾回收器何时调用该方法,也无法保证调用不同对象的方法的顺序。即使一个对象包含另一个对象的引用,或者在释放一个对象很久以前就释放了另一个对象,也可能会以任意的顺序调用这两个对象的Finalize方法。如果必须保证采用特定的顺序,则必须提供自己的特有清理方法。

4. Java中CyclicBarrier 和 CountDownLatch有什么不同?

1、CyclicBarrier的某个线程运行到某个点后停止运行,直到所有线程都达到同一个点,所有线程才会重新运行;

 CountDownLatch线程运行到某个点后,计数值-1,该线程继续运行,直到计数值为0,则停止运行;

2、CyclicBarrier只能唤醒一个任务;CountDownLatch可以唤醒多个任务;

3、CyccliBarrier可以重用,CountDownLatch不可重用,当计数值为0时,CountDownLatch就不可再用了。

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处即可-免费获取】​https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho

5.为什么说 Synchronized 是一个悲观锁?乐观锁的实现原理 又是什么?什么是 CAS,它有什么特性?

Synchronized显然是一个悲观锁,因为它的并发策略是悲观的:不管是否会产生竞争,任何的数据操作都必须要加锁、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要被唤醒等操作。

随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略。先进行操作,如果没有其他线程征用数据,那操作就成功了;如果共享数据有征用,产生了冲突,那就再进行其他的补偿措施。这种乐观的并发策略的许多实现不需要线程挂起,所以被称为非阻塞同步。

乐观锁的核心算法是CAS(CompareandSwap,比较并交换),它涉及到三个操作数:内存值、预期值、新值。当且仅当预期值和内存值相等时才将内存值修改为新值。这样处理的逻辑是,首先检查某块内存的值是否跟之前我读取时的一样,如不一样则表示期间此内存值已经被别的线程更改过,舍弃本次操作,否则说明期间没有其他线程对此内存值操作,可以把新值设置给此块内存。

CAS具有原子性,它的原子性由CPU硬件指令实现保证,即使用JNI调用Native方法调用由C++编写的硬件级别指令,JDK中提供了Unsafe类执行这些操作。

3.66 Java中Semaphore是什么?

Java中的Semaphore是一种新的同步类,它是一个计数信号。从概念上讲,从概念上讲,信号量维护了一个许可集合。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采取相应的行动。信号量常常用于多线程的代码中,比如数据库连接池。

3.67现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?

在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。

3.68你如何在Java中获取线程堆栈?

Java虚拟机提供了线程转储(thread dump)的后门,通过这个后门可以把线程堆栈打印出来。通常我们将堆栈信息重定向到一个文件中,便于我们分析,由于信息量太大,很可能超出控制台缓冲区的最大行数限制造成信息丢失。这里介绍一个jdk自带的打印线程堆栈的工具,jstack用于打印出给定的Java进程ID或core file或远程调试服务的Java堆栈信息。

示例:$jstack –l 23561 >> xxx.dump 

命令 : $jstack [option] pid >> 文件  

>>表示输出到文件尾部,实际运行中,往往一次dump的信息,还不足以确认问题,建议产生三次dump信息,如果每次dump都指向同一个问题,我们才确定问题。

3.69提交任务时线程池队列已满会时发会生什么?

这个问题问得很狡猾,许多程序员会认为该任务会阻塞直到线程池队列有空位。但是最大线程数没有满的话,就会新建一个非核心线程去执行该任务。如果核心线程数、阻塞队列、最大线程数都满了的话,就会执行线程池的拒绝策略,如果一个任务不能被调度执行那么ThreadPoolExecutor’s submit()方法将会抛出一个RejectedExecutionException异常

3.70什么是乐观锁和悲观锁?

乐观锁总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。

乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。

悲观锁顾名思义,就是很悲观,总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起。可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加锁,在Java中,synchronized的思想也是悲观锁。

第4章 JVM

4.1 JVM内存分哪几个区,每个区的作用是什么? 

java虚拟机主要分为以下几个区:

(1)方法区

  1. 有时候也成为永久代,在该区内很少发生垃圾回收,但是并不代表不发生GC,在这里进行的GC主要是对方法区里的常量池和对类型的卸载
  2. 方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。
  3. 该区域是被线程共享的。
  4. 方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说常量并不一定是编译时确定,运行时生成的常量也会存在这个常量池中。

(2)虚拟机栈

  1. 虚拟机栈也就是我们平常所称的栈内存,它为java方法服务,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。
  2. 虚拟机栈是线程私有的,它的生命周期与线程相同。
  3. 局部变量表里存储的是基本数据类型、returnAddress类型(指向一条字节码指令的地址)和对象引用,这个对象引用有可能是指向对象起始地址的一个指针,也有可能是代表对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译器间确定
  4. 操作数栈的作用主要用来存储运算结果以及运算的操作数,它不同于局部变量表通过索引来访问,而是压栈和出栈的方式
  5. 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接.动态链接就是将常量池中的符号引用在运行期转化为直接引用。

(3)本地方法栈
本地方法栈和虚拟机栈类似,只不过本地方法栈为Native方法服务。

(4)堆

java堆是所有线程所共享的一块内存,在虚拟机启动时创建,几乎所有的对象实例都在这里创建,因此该区域经常发生垃圾回收操作。

(5)程序计数器:

内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个java虚拟机规范没有规定任何OOM情况的区域。

4.2 heap 和stack 有什么区别 

(1)申请方式

stack:由系统自动分配。例如,声明在函数中一个局部变量 int b; 系统自动在栈中为 b 开辟空间

heap:需要程序员自己申请,并指明大小,在 c 中 malloc 函数,对于Java 需要手动 new Object()的形式开辟

(2)申请后系统的响应

stack:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

heap:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

(3)申请大小的限制

stack:栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS 下,栈的大小是 2M(也有的说是 1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示 overflow。因此,能从栈获得的空间较小。

heap:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的, 自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见, 堆获得的空间比较灵活,也比较大。

(4)申请效率的比较

stack:由系统自动分配,速度较快。但程序员是无法控制的。

heap:由 new 分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。

(5)heap和stack中的存储内容

stack:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址, 然后是函数的各个参数,在大多数的 C 编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。

当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

heap:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。

4.3 java类加载过程? 

Java类加载需要经历一下几个过程:

(1)加载

加载时类加载的第一个过程,在这个阶段,将完成一下三件事情:

  1. 通过一个类的全限定名获取该类的二进制流。
  2. 将该二进制流中的静态存储结构转化为方法去运行时数据结构。 
  3. 在内存中生成该类的Class对象,作为该类的数据访问入口。

(2)验证

验证的目的是为了确保Class文件的字节流中的信息不回危害到虚拟机.在该阶段主要完成以下四钟验证: 

  1. 文件格式验证:验证字节流是否符合Class文件的规范,如主次版本号是否在当前虚拟机范围内,常量池中的常量是否有不被支持的类型. 
  2. 元数据验证:对字节码描述的信息进行语义分析,如这个类是否有父类,是否集成了不被继承的类等。
  3. 字节码验证:是整个验证过程中最复杂的一个阶段,通过验证数据流和控制流的分析,确定程序语义是否正确,主要针对方法体的验证。如:方法中的类型转换是否正确,跳转指令是否正确等。
  4. 符号引用验证:这个动作在后面的解析过程中发生,主要是为了确保解析动作能正确执行。
  5. 准备

准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中。

(3)解析

该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。

(4)初始化

初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。

4.4 什么是类加载器,类加载器有哪些? 

实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。

主要有一下四种类加载器: 

(1)启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。

(2)扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。

(3)系统类加载器(system class loader)也叫应用类加载器:它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。

(4)用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现。

4.5 java中垃圾收集的方法有哪些? 

1引用计数法   应用于:微软的COM/ActionScrip3/Python等

a) 如果对象没有被引用,就会被回收,缺点:需要维护一个引用计算器

2复制算法  年轻代中使用的是Minor GC,这种GC算法采用的是复制算法(Copying)

a) 效率高,缺点:需要内存容量大,比较耗内存

b) 使用在占空间比较小、刷新次数多的新生区

3标记清除  老年代一般是由标记清除或者是标记清除与标记整理的混合实现

a) 效率比较低,会差生碎片。

4标记压缩  老年代一般是由标记清除或者是标记清除与标记整理的混合实现

a) 效率低速度慢,需要移动对象,但不会产生碎片。

5标记清除压缩标记清除-标记压缩的集合,多次GC后才Compact

a) 使用于占空间大刷新次数少的养老区,是3 4的集合体

4.6 如何判断一个对象是否存活?(或者GC对象的判定方法) 

判断一个对象是否存活有两种方法: 

(1)引用计数法

所谓引用计数法就是给每一个对象设置一个引用计数器,每当有一个地方引用这个对象时,就将计数器加一,引用失效时,计数器就减一。当一个对象的引用计数器为零时,说明此对象没有被引用,也就是“死对象”,将会被垃圾回收. 

引用计数法有一个缺陷就是无法解决循环引用问题,也就是说当对象A引用对象B,对象B又引用者对象A,那么此时A,B对象的引用计数器都不为零,也就造成无法完成垃圾回收,所以主流的虚拟机都没有采用这种算法。

(2)可达性算法(引用链法) 

该算法的基本思路就是通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到GC Roots没有任何引用链相连时(即从GC Roots节点到该节点不可达),则证明该对象是不可用的。

在java中可以作为GC Roots的对象有以下几种:虚拟机栈中引用的对象、方法区类静态属性引用的对象、方法区常量池引用的对象、本地方法栈JNI引用的对象。

4.7 简述java内存分配与回收策略以及Minor GC和Major GC(full GC

内存分配:

(1)栈区:栈分为java虚拟机栈和本地方法栈

(2)堆区:堆被所有线程共享区域,在虚拟机启动时创建,唯一目的存放对象实例。堆区是gc的主要区域,通常情况下分为两个区块年轻代和年老代。更细一点年轻代又分为Eden区,主要放新创建对象,From survivor 和 To survivor 保存gc后幸存下的对象,默认情况下各自占比 8:1:1。

(3)方法区:被所有线程共享区域,用于存放已被虚拟机加载的类信息,常量,静态变量等数据。被Java虚拟机描述为堆的一个逻辑部分。习惯是也叫它永久代(permanment generation)

(4)程序计数器:当前线程所执行的行号指示器。通过改变计数器的值来确定下一条指令,比如循环,分支,跳转,异常处理,线程恢复等都是依赖计数器来完成。线程私有的。

回收策略以及Minor GC和Major GC:

(1)对象优先在堆的Eden区分配。

(2)大对象直接进入老年代。

(3)长期存活的对象将直接进入老年代。

当Eden区没有足够的空间进行分配时,虚拟机会执行一次Minor GC.Minor GC通常发生在新生代的Eden区,在这个区的对象生存期短,往往发生GC的频率较高,回收速度比较快;Full Gc/Major GC 发生在老年代,一般情况下,触发老年代GC的时候不会触发Minor GC,但是通过配置,可以在Full GC之前进行一次Minor GC这样可以加快老年代的回收速度。

4.8什么情况下会发生栈内存溢出。

栈是线程私有的,他的生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口等信息。局部变量表又包含基本数据类型,对象引用类型

如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常,方法递归调用产生这种结果。

如果Java虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是无法申请到足够的内存去完成扩展,或者在新建立线程的时候没有足够的内存去创建对应的虚拟机栈,那么Java虚拟机将抛出一个OutOfMemory 异常。(线程启动过多)

参数 -Xss 去调整JVM栈的大小

4.9 JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代

对象诞生即新生代->eden,在进行minor gc过程中,如果依旧存活,移动到from,变成Survivor,进行标记。当一个对象存活默认超过15次都没有被回收掉,就会进入老年代。

4.10你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms和G1,包括原理,流程,优缺点。

垃圾收集器包括Serial、parNew、ParallelScavenge、SerialOld、ParallelOld、CMS、G1

CMS:

一、初始标记:此时标记需要用户线程停下来;

二、并发标记:此时标记可以和用户线程一起运行;

三、重新标记:此时标记需要用户线程停下来,主要母的是为了对并发标记的垃圾进行审核;

四、并发清除:与用户线程一起与运行进行垃圾清除;

缺点:

     1、CMS收集器对cpu资源非常敏感;

     2、CMS收集器无法清除浮动垃圾;

     3、cms基于标记清除的算法实现的,所以内存碎片会产生过多。

G1收集器:

    1、初始标记:标记GC Root能直接关联的对象,并且修改TAMS的值,让下一阶段的用户进行并发运行是,能够正确运用Region创建新对象,这阶段需要停顿,但停顿时间很短

   2、并发标记:从GC Root开始对堆进行可达性分析,找出存活的对象,这段耗时较长,但可以与用户线程并发执行。

    3、最终标记是为了修正在并发标记阶段因用户程序继续运作导致标记产生变动的那一部分的标记记录,虚拟机将这部分标记记录在线程Remembered Set中,这阶段需要停顿线程,但是可并行执行。

   4、筛选回收:首先对各个Region的回收价值和成本进行排序,根据用户所期待的GC停顿时间来制定回收计划,这个阶段也可以与用户线程并行执行,但由于只回收一部分的Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。

4.11 JVM内存模型的相关知识了解多少,比如重排序,内存屏障,happen-before,主内存,工作内存。

重排序:jvm虚拟机允许在不影响代码最终结果的情况下,可以乱序执行。

内存屏障:可以阻挡编译器的优化,也可以阻挡处理器的优化

happens-before原则:

1:一个线程的A操作总是在B之前,那多线程的A操作肯定实在B之前。

2:monitor 再加锁的情况下,持有锁的肯定先执行。

3:volatile修饰的情况下,写先于读发生

4:线程启动在一起之前 strat

5:线程死亡在一切之后 end

6:线程操作在一切线程中断之前

7:一个对象构造函数的结束都该对象的finalizer的开始之前

8:传递性,如果A肯定在B之前,B肯定在C之前,那A肯定是在C之前。

主内存:所有线程共享的内存空间

工作内存:每个线程特有的内存空间

4.12简单说说你了解的类加载器,可以打破双亲委派么,怎么打破。

1) 什么是类加载器?

类加载器 就是根据指定全限定名称将class文件加载到JVM内存,转为Class对象。

启动类加载器(Bootstrap ClassLoader):由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中。

其他类加载器:由Java语言实现,继承自抽象类ClassLoader。如:

扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库。

应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

2)双亲委派模型

 篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处即可-免费获取】​https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho

双亲委派模型工作过程是:

如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。

双亲委派模型图:

3)为什么需要双亲委派模型?

在这里,先想一下,如果没有双亲委派,那么用户是不是可以自己定义一个java.lang.Object的同名类,java.lang.String的同名类,并把它放到ClassPath中,那么类之间的比较结果及类的唯一性将无法保证,因此,为什么需要双亲委派模型?防止内存中出现多份同样的字节码

4)怎么打破双亲委派模型?

打破双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass和findClass方法。

4.13说说你知道的几种主要的JVM参数

1)堆栈配置相关

java -Xmx3550m -Xms3550m -Xmn2g -Xss128k

-XX:MaxPermSize=16m -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxTenuringThreshold=0

-Xmx3550m: 最大堆大小为3550m。

-Xms3550m: 设置初始堆大小为3550m。

-Xmn2g: 设置年轻代大小为2g。

-Xss128k: 每个线程的堆栈大小为128k。

-XX:MaxPermSize: 设置持久代大小为16m

-XX:NewRatio=4: 设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。

-XX:SurvivorRatio=4: 设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6

-XX:MaxTenuringThreshold=0: 设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。

2)垃圾收集器相关

-XX:+UseParallelGC

-XX:ParallelGCThreads=20

-XX:+UseConcMarkSweepGC

-XX:CMSFullGCsBeforeCompaction=5

-XX:+UseCMSCompactAtFullCollection:

-XX:+UseParallelGC: 选择垃圾收集器为并行收集器。

-XX:ParallelGCThreads=20: 配置并行收集器的线程数

-XX:+UseConcMarkSweepGC: 设置年老代为并发收集。

-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。

-XX:+UseCMSCompactAtFullCollection: 打开对年老代的压缩。可能会影响性能,但是可以消除碎片

3)辅助信息相关

-XX:+PrintGC

-XX:+PrintGCDetails

-XX:+PrintGC 输出形式:

[GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs]

-XX:+PrintGCDetails 输出形式:

[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs

4.15垃圾收集算法

jvm的垃圾回收算法有3种,列举如下:

1,标记清除算法。(即把标注的可回收对象直接清理,这样会带来内存碎片化的问题,而且效率不高);

2,标记整理算法。(即把标注的可回收对象清理,在清理的过程中整理内存,解决了内存的碎片化问题);

3,标记复制算法。(把标注的对象清理,没有清理的对象复制到to区,然后互换引用,解决了内存碎片化的问题,但是需要维护对象关系带来一定代价)

4.16调优工具用过哪些

常用调优工具分为两类,jdk自带监控工具:jconsole和jvisualvm,第三方有:MAT(Memory Analyzer Tool)、GChisto。

jconsole,Java Monitoring and Management Console是从java5开始,在JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和类等的监控jvisualvm,jdk自带全能工具,可以分析内存快照、线程快照;监控内存变化、GC变化等。MAT,Memory Analyzer Tool,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗

GChisto,一款专业分析gc日志的工具

4.17你知道哪些JVM性能调优

首先需要注意的是在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。

  对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数,过多的GC和Full GC是会占用很多的系统资源(主要是CPU),影响系统的吞吐量。特别要关注Full GC,因为它会对整个堆进行整理,导致Full GC一般由于以下几种情况:

旧生代空间不足

  调优时尽量让对象在新生代GC时被回收、让对象在新生代多存活一段时间和不要创建过大的对象及数组避免直接在旧生代创建对象

Pemanet Generation空间不足

  增大Perm Gen空间,避免太多静态对象

  统计得到的GC后晋升到旧生代的平均大小大于旧生代剩余空间

  控制好新生代和旧生代的比例

System.gc()被显示调用

  垃圾回收不要手动触发,尽量依靠JVM自身的机制

调优手段主要是通过控制堆内存的各个部分的比例和GC策略来实现,下面来看看各部分比例不良设置会导致什么后果

1). 新生代设置过小

  一是新生代GC次数非常频繁,增大系统消耗;二是导致大对象直接进入旧生代,占据了旧生代剩余空间,诱发Full GC

2). 新生代设置过大

  一是新生代设置过大会导致旧生代过小(堆总量一定),从而诱发Full GC;二是新生代GC耗时大幅度增加

  一般说来新生代占整个堆1/3比较合适

3). Survivor设置过小

  导致对象从eden直接到达旧生代,降低了在新生代的存活时间

4). Survivor设置过大

  导致eden过小,增加了GC频率

  另外,通过-XX:MaxTenuringThreshold=n来控制新生代存活时间,尽量让对象在新生代被回收

由内存管理和垃圾回收可知新生代和旧生代都有多种GC策略和组合搭配,选择这些策略对于我们这些开发人员是个难题,JVM提供两种较为简单的GC策略的设置方式

1). 吞吐量优先

  JVM以吞吐量为指标,自行选择相应的GC策略及控制新生代与旧生代的大小比例,来达到吞吐量指标。这个值可由-XX:GCTimeRatio=n来设置

2). 暂停时间优先

  JVM以暂停时间为指标,自行选择相应的GC策略及控制新生代与旧生代的大小比例,尽量保证每次GC造成的应用停止时间都在指定的数值范围内完成。这个值可由-XX:MaxGCPauseRatio=n来设置

4.18 Eden和Survivor的比例分配等

默认比例8:1。部分对象都是朝生夕死。 复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。

4.19说一说你对环境变量classpath的理解?如果一个类不在classpath下,为什么会抛出ClassNotFoundException异常,如果在不改变这个类路径的前期下,怎样才能正确加载这个类?

classpath是javac编译器的一个环境变量。它的作用与import、package关键字有关。package的所在位置,就是设置CLASSPATH当编译器面对import packag这个语句时,它先会查找CLASSPATH所指定的目录,并检视子目录java/util是否存在,然后找出名称吻合的已编译文件(.class文件)。如果没有找到就会报错!

动态加载包

  1.  设计模式

5.1你所知道的设计模式有哪些 

Java 中一般认为有 23 种设计模式,我们不需要所有的都会,但是其中常用的几种设计模式应该去掌握。下面列出了所有的设计模式。需要掌握的设计模式我单独列出来了,当然能掌握的越多越好。

总体来说设计模式分为三大类:

创建型模式,共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共7种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

5.2单例设计模式 

见【第2章 手写代码 2.5】

5.3工厂设计模式(Factory) 

5.3.1什么是工厂设计模式? 

工厂设计模式,顾名思义,就是用来生产对象的,在java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则,如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦

5.3.2简单工厂(Simple Factory) 

定义:

一个工厂方法,依据传入的参数,生成对应的产品对象;
角色:
1、抽象产品
2、具体产品
3、具体工厂
4、产品使用者
使用说明:

先将产品类抽象出来,比如,苹果和梨都属于水果,抽象出来一个水果类Fruit,苹果和梨就是具体的产品类,然后创建一个水果工厂,分别用来创建苹果和梨。代码如下:

水果接口

  1. public interface Fruit {  
  2.     void whatIm();  
  3. }  

苹果类:

  1. public class Apple implements Fruit {  
  2.     @Override  
  3.     public void whatIm() {  
  4.         System.out.println("苹果");  
  5.     }  
  6. }  

类:

  1. public class Pear implements Fruit {  
  2.     @Override  
  3.     public void whatIm() {  
  4.         System.out.println("梨");  
  5.     }  
  6. }  

水果工厂

  1. public class FruitFactory {  
  2.   
  3.     public Fruit createFruit(String type) {  
  4.   
  5.         if (type.equals("apple")) {//生产苹果  
  6.             return new Apple();  
  7.         } else if (type.equals("pear")) {//生产梨  
  8.             return new Pear();  
  9.         }  
  10.   
  11.         return null;  
  12.     }  
  13. }  

使用工厂生产产品:

  1. public class FruitApp {  
  2.   
  3.     public static void main(String[] args) {  
  4.         FruitFactory mFactory = new FruitFactory();  
  5.         Apple apple = (Apple) mFactory.createFruit("apple");//获得苹果  
  6.         Pear pear = (Pear) mFactory.createFruit("pear");//获得梨  
  7.         apple.whatIm();  
  8.         pear.whatIm();  
  9.     }  
  10. }  

以上的这种方式,每当添加一种水果,就必然要修改工厂类,违反了开闭原则;

所以简单工厂只适合于产品对象较少,且产品固定的需求,对于产品变化无常的需求来说显然不合适。

5.3.3工厂方法(Factory Method) 

定义:

将工厂提取成一个接口或抽象类,具体生产什么产品由子类决定;
角色:
1、抽象产品
2、具体产品
3、抽象工厂
4、具体工厂
使用说明:

和上例中一样,产品类抽象出来,这次我们把工厂类也抽象出来,生产什么样的产品由子类来决定。代码如下:
水果接口苹果类和梨类

代码和上例一样

抽象工厂接口

  1. public interface FruitFactory {  
  2.     Fruit createFruit();//生产水果  
  3. }  

苹果工厂

  1. public class AppleFactory implements FruitFactory {  
  2.     @Override  
  3.     public Apple createFruit() {  
  4.         return new Apple();  
  5.     }  
  6. }  

梨工厂

  1. public class PearFactory implements FruitFactory {  
  2.     @Override  
  3.     public Pear createFruit() {  
  4.         return new Pear();  
  5.     }  
  6. }  

使用工厂生产产品:

  1. public class FruitApp {  
  2.   
  3.     public static void main(String[] args){  
  4.         AppleFactory appleFactory = new AppleFactory();  
  5.         PearFactory pearFactory = new PearFactory();  
  6.         Apple apple = appleFactory.createFruit();//获得苹果  
  7.         Pear pear = pearFactory.createFruit();//获得梨  
  8.         apple.whatIm();  
  9.         pear.whatIm();  
  10.     }  
  11. }  

以上这种方式,虽然解耦了,也遵循了开闭原则,但是如果我需要的产品很多的话,需要创建非常多的工厂,所以这种方式的缺点也很明显。

5.3.4抽象工厂(Abstract Factory) 

定义:

为创建一组相关或者是相互依赖的对象提供的一个接口,而不需要指定它们的具体类。
角色:

1、抽象产品

2、具体产品

3、抽象工厂

4、具体工厂

使用说明:

抽象工厂和工厂方法的模式基本一样,区别在于,工厂方法是生产一个具体的产品,而抽象工厂可以用来生产一组相同,有相对关系的产品;重点在于一组,一批,一系列;举个例子,假如生产小米手机,小米手机有很多系列,小米note、红米note等;假如小米note生产需要的配件有825的处理器,6英寸屏幕,而红米只需要650的处理器和5寸的屏幕就可以了。用抽象工厂来实现:

 篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处即可-免费获取】​https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho

cpu接口和实现类

  1. public interface Cpu {  
  2.     void run();  
  3.   
  4.     class Cpu650 implements Cpu {  
  5.         @Override  
  6.         public void run() {  
  7.             System.out.println("650 也厉害");  
  8.         }  
  9.     }  
  10.   
  11.     class Cpu825 implements Cpu {  
  12.         @Override  
  13.         public void run() {  
  14.             System.out.println("825 更强劲");  
  15.         }  
  16.     }  
  17. }  

屏幕接口和实现类

  1. public interface Screen {  
  2.   
  3.     void size();  
  4.   
  5.     class Screen5 implements Screen {  
  6.   
  7.         @Override  
  8.         public void size() {  
  9.             System.out.println("" +  
  10.                     "5寸");  
  11.         }  
  12.     }  
  13.   
  14.     class Screen6 implements Screen {  
  15.   
  16.         @Override  
  17.         public void size() {  
  18.             System.out.println("6寸");  
  19.         }  
  20.     }  
  21. }  

抽象工厂接口

  1. public interface PhoneFactory {  
  2.   
  3.     Cpu getCpu();//使用的cpu  
  4.   
  5.     Screen getScreen();//使用的屏幕  
  6. }  

小米手机工厂

  1. public class XiaoMiFactory implements PhoneFactory {  
  2.     @Override  
  3.     public Cpu.Cpu825 getCpu() {  
  4.         return new Cpu.Cpu825();//高性能处理器  
  5.     }  
  6.   
  7.     @Override  
  8.     public Screen.Screen6 getScreen() {  
  9.         return new Screen.Screen6();//6寸大屏  
  10.     }  
  11. }  

红米手机工厂

  1. public class HongMiFactory implements PhoneFactory {  
  2.   
  3.     @Override  
  4.     public Cpu.Cpu650 getCpu() {  
  5.         return new Cpu.Cpu650();//高效处理器  
  6.     }  
  7.   
  8.     @Override  
  9.     public Screen.Screen5 getScreen() {  
  10.         return new Screen.Screen5();//小屏手机  
  11.     }  
  12. }  

使用工厂生产产品:

  1. public class PhoneApp {  
  2.     public static void main(String[] args){  
  3.         HongMiFactory hongMiFactory = new HongMiFactory();  
  4.         XiaoMiFactory xiaoMiFactory = new XiaoMiFactory();  
  5.         Cpu.Cpu650 cpu650 = hongMiFactory.getCpu();  
  6.         Cpu.Cpu825 cpu825 = xiaoMiFactory.getCpu();  
  7.         cpu650.run();  
  8.         cpu825.run();  
  9.   
  10.         Screen.Screen5 screen5 = hongMiFactory.getScreen();  
  11.         Screen.Screen6 screen6 = xiaoMiFactory.getScreen();  
  12.         screen5.size();  
  13.         screen6.size();  
  14.     }  
  15. }  

以上例子可以看出,抽象工厂可以解决一系列的产品生产的需求,对于大批量,多系列的产品,用抽象工厂可以更好的管理和扩展。

5.3.5三种工厂方式总结 

1、对于简单工厂和工厂方法来说,两者的使用方式实际上是一样的,如果对于产品的分类和名称是确定的,数量是相对固定的,推荐使用简单工厂模式;

2、抽象工厂用来解决相对复杂的问题,适用于一系列、大批量的对象生产。

5.4代理模式(Proxy) 

5.4.1什么是代理模式?

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。

举个例子来说明:假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了。用图表示如下:

5.4.2为什么要用代理模式?

中介隔离作用:

在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。

开闭原则,增加功能:

代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要修改已经封装好的委托类。

5.4.3有哪几种代理模式?

我们有多种不同的方式来实现代理。

如果按照代理创建的时期来进行分类的话,可以分为两种:静态代理、动态代理。

  1. 静态代理是由程序员创建或特定工具自动生成源代码,再对其编译。在程序员运行之前,代理类.class文件就已经被创建了。
  2. 动态代理是在程序运行时通过反射机制动态创建的。

5.4.4静态代理(Static Proxy)

第一步:创建服务类接口

  1. public interface BuyHouse {  
  2.     void buyHouse();  
  3. }  

第二步:实现服务接口

  1. public class BuyHouseImpl implements BuyHouse {  
  2.   
  3.     @Override  
  4.     public void buyHouse() {  
  5.         System.out.println("我要买房");  
  6.     }  
  7. }  

第三步:创建代理类

  1.  public class BuyHouseProxy implements BuyHouse {  
  2.   
  3.     private BuyHouse buyHouse;  
  4.   
  5.     public BuyHouseProxy(final BuyHouse buyHouse) {  
  6.         this.buyHouse = buyHouse;  
  7.     }  
  8.   
  9.     @Override  
  10.     public void buyHouse() {  
  11.         System.out.println("买房前准备");  
  12.         buyHouse.buyHouse();  
  13.         System.out.println("买房后装修");  
  14.   
  15.     }  
  16. }  

第四步:编写测试类

  1. public class HouseApp {  
  2.   
  3.     public static void main(String[] args) {  
  4.         BuyHouse buyHouse = new BuyHouseImpl();  
  5.         BuyHouseProxy buyHouseProxy = new BuyHouseProxy(buyHouse);  
  6.         buyHouseProxy.buyHouse();  
  7.     }  
  8. }  

静态代理总结:

优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。

缺点:我们得为每一个服务创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。    

5.4.5 JDK动态代理(Dynamic Proxy)

在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK在运行时为我们动态的来创建。

第一步:创建服务类接口

代码和上例一样

第二步:实现服务接口

代码和上例一样

步:编写动态处理器

  1. public class DynamicProxyHandler implements InvocationHandler {  
  2.   
  3.     private Object object;  
  4.   
  5.     public DynamicProxyHandler(final Object object) {  
  6.         this.object = object;  
  7.     }  
  8.   
  9.     @Override  
  10.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  11.         System.out.println("买房前准备");  
  12.         Object result = method.invoke(object, args);  
  13.         System.out.println("买房后装修");  
  14.         return result;  
  15.     }  
  16. }  

步:编写测试类

  1. public class HouseApp {  
  2.   
  3.     public static void main(String[] args) {  
  4.         BuyHouse buyHouse = new BuyHouseImpl();  
  5.         BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(  
  6.                 BuyHouse.class.getClassLoader(),  
  7.                 new Class[]{BuyHouse.class},  
  8.                 new DynamicProxyHandler(buyHouse));  
  9.         proxyBuyHouse.buyHouse();  
  10.     }  
  11. }  

 Proxy是所有动态生成的代理的共同的父类,这个类有一个静态方法Proxy.newProxyInstance(),接收三个参数:

  1. ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的
  2. Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型
  3. InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法

JDK动态代理总结:

优点:相对于静态代理,动态代理大大减少了开发任务,同时减少了对业务接口的依赖,降低了耦合度。

缺点:Proxy是所有动态生成的代理的共同的父类,因此服务类必须是接口的形式,不能是普通类的形式,因为Java无法实现多继承。

5.4.6 CGLib动态代理(CGLib Proxy)

JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

Cglib子类代理实现方法:

(1)引入cglib的jar文件,asm的jar文件

(2)代理的类不能为final

(3)目标业务对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法

第一步:创建服务类

  1. public class BuyHouse2 {  
  2.   
  3.     public void buyHouse() {  
  4.         System.out.println("我要买房");  
  5.     }  
  6. }  

步:创建CGLIB代理类

  1. public class CglibProxy implements MethodInterceptor {  
  2.   
  3.     private Object target;  
  4.   
  5.     public CglibProxy(Object target) {  
  6.         this.target = target;  
  7.     }  
  8.   
  9.     /** 
  10.      *  给目标对象创建一个代理对象 
  11.      * @return 代理对象 
  12.      */  
  13.     public Object getProxyInstance() {  
  14.         //1.工具类  
  15.         Enhancer enhancer = new Enhancer();  
  16.         //2.设置父类  
  17.         enhancer.setSuperclass(target.getClass());  
  18.         //3.设置回调函数  
  19.         enhancer.setCallback(this);  
  20.         //4.创建子类(代理对象)  
  21.         return enhancer.create();  
  22.   
  23.     }  
  24.   
  25.     public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {  
  26.         System.out.println("买房前准备");  
  27.         //执行目标对象的方法  
  28.         Object result = method.invoke(target, args);  
  29.         System.out.println("买房后装修");  
  30.         return result;  
  31.     }  
  32. }  

步:创建测试类

  1. public class HouseApp {  
  2.   
  3.     public static void main(String[] args) {  
  4.   
  5.         BuyHouse2 target = new BuyHouse2();  
  6.         CglibProxy cglibProxy = new CglibProxy(target);  
  7.         BuyHouse2 buyHouseCglibProxy = (BuyHouse2) cglibProxy.getProxyInstance();  
  8.         buyHouseCglibProxy.buyHouse();  
  9.     }  
  10. }  

CGLib代理总结:

CGLib创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

 篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处即可-免费获取】​https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho

5.4.7 简述动态代理的原理, 常用的动态代理的实现方式

动态代理的原理: 使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。

代理对象决定是否以及何时将方法调   用转到原始对象上

动态代理的方式

基于接口实现动态代理: JDK动态代理

基于继承实现动态代理: Cglib、Javassist动态代理

第6章 MySql

6.1 jdbc 操作数据库流程

第一步:Class.forName()加载数据库连接驱动;

第二步:DriverManager.getConnection()获取数据连接对象;

第三步:根据SQL 获取 sql 会话对象,有 2 种方式 Statement、PreparedStatement ;

第四步:执行SQL 处理结果集,执行 SQL 前如果有参数值就设置参数值 setXXX();

第五步:关闭结果集、关闭会话、关闭连接。

6.2关系数据库中连接池的机制是什么? 

前提:为数据库连接建立一个缓冲池。

(1)从连接池获取或创建可用连接

(2)使用完毕之后,把连接返回给连接池

(3)在系统关闭前,断开所有连接并释放连接占用的系统资源

(4)能够处理无效连接,限制连接池中的连接总数不低于或者不超过某个限定值。

其中有几个概念需要大家理解:

最小连接数是连接池一直保持的数据连接。如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费掉。

最大连接数是连接池能申请的最大连接数。如果数据连接请求超过此数,后面的数据连接请求将被加入到等待队列中,这会影响之后的数据库操作。

如果最小连接数与最大连接数相差太大,那么,最先的连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接。不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,它将被放到连接池中等待重复使用或是空闲超时后被释放。

上面的解释,可以这样理解:数据库池连接数量一直保持一个不少于最小连接数的数量,当数量不够时,数据库会创建一些连接,直到一个最大连接数,之后连接数据库就会等待。

6.3 SQL 的select 语句完整的执行顺序 

SQL Select 语句完整的执行顺序:

(1)from 子句组装来自不同数据源的数据;

(2)where 子句基于指定的条件对记录行进行筛选;

(3)group by 子句将数据划分为多个分组;

(4)使用聚集函数进行计算;

(5)使用 having 子句筛选分组;

(6)计算所有的表达式;

(7)select 的字段;

(8)使用order by 对结果集进行排序。

6.3 MySQL的事务 

事务的基本要素(ACID):

  1. 原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位
  2. 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
  3. 隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
  4. 持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

事务的并发问题:

  1. 脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
  2. 不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致
  3. 幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

小结:

不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

MySQL事务隔离级别:

事务隔离级别 脏读 不可重复读 幻读

读未提交(read-uncommitted) 是 是 是

不可重复读(read-committed) 否 是 是

可重复读(repeatable-read) 否 否 是

串行化(serializable) 否 否 否

6.4行锁,表锁

MyISAM

InnoDB

行表锁

表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作

行锁,操作时只锁某一行,不对其它行有影响,适合高并发的操作

6.5索引

数据结构:B+Tree

一般来说能够达到range就可以算是优化了

口诀:

全值匹配我最爱,最左前缀要遵守;

带头大哥不能死,中间兄弟不能断;

索引列上少计算,范围之后全失效;

LIKE百分写最右,覆盖索引不写*;

不等空值还有OR,索引影响要注意;

VAR引号不可丢,SQL优化有诀窍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值