2022 Java 核心概念知识点
- Java Development
-
- Test Practice (软件测试)
-
- 什么是黑盒测试?
- 什么是白盒测试?
- 什么是灰盒测试?
- String
-
- String、StringBuffer、StringBuilder
- String 和 char 的区别
- StringBuffer、StringBuilder 的区别?
- 常用字符集
- Lock in Java (Java里的锁)
-
- 什么是 CAS ?
- 介绍一下 synchronized ?
- 介绍一下锁的升级?
- synchronized 和 volatile 的区别?
- 什么是死锁? 造成死锁的原因有哪些?
- 介绍一下什么是 AQS ?
- JVM
-
- Debug JVM
- Garbage Collector
- 如何选择合适的收集算法?
- 新生代怎么转化成老年代?
- GC的触发条件是什么?
- JVM 中确定垃圾的集中算法?
- 常用的垃圾回收算法?
- 介绍一下垃圾回收器?
- 类加载
- 什么是双亲委派模型?为什么要使用双亲委派?
- OOP (面向对象编程)
-
- 对象(Object)
- 类(Class)
- Inheritance (继承)
- Polymorphism (多态)
-
- Overload (重载)
- Override (重写)
- Abstraction (抽象)
-
- Abstract class (抽象类)
- Interface (接口)
- 抽象类跟接口的区别
- 封装
- 五个面向对象设计原则 - SOLID原则
- Generics (泛型)
-
- 桥方法 (Bridge Method)
- 堆污染 (Heap pollution)
- PECS 原则: Producer Extends, Consumer Super
- Reflection (反射)
- Serialization (序列化)
- Collections (集合)
- Multi-thread (多线程)
- Annotations (注解)
- Network (计算机网络)
- String (字符串)
- Nested Class (Java 内部类)
- Final, static, abstract
- NIO and IO Stream (Java IO流、Stream 流)
- Logging
- Exceptions (异常)
- Packaging (Java 包)
- OWASP Top 10 (十大安全漏洞列表)
- XML Parsers (XML解析器)
- Regex (正则表达式)
- Spring
- Redis
- MySQL
- 分布式架构
-
- 微服务
-
- 微服务的几大组件
-
- 注册中心
- 配置中心
- 服务网关
- 负载均衡
- 熔断
-
- 什么是服务雪崩?
- 什么是服务限流?
- 什么是服务降级?
- 什么是服务熔断?
- 服务熔断和降级的区别?
- 服务调用
Java Development
Test Practice (软件测试)
什么是黑盒测试?
黑盒测试(也被称为功能测试)把软件当作一个 “黑盒”,不涉及程序内部的逻辑结构,而是通过规格说明对程序功能进行检查,从中获取测试数据。测试人员只知道软件应该做什么,而不知道它具体是如何做的。
什么是白盒测试?
白盒测试(也被称为透明盒测试、玻璃盒测试、透明盒测试和结构测试)验证程序的内部结构或工作原理,通过程序的源代码进行测试而不使用用户界面。这种类型的测试需要从代码句法发现内部代码在算法,溢出,路径,条件等等中的缺点或者错误,进而加以修正。
什么是灰盒测试?
灰盒测试是介于白盒测试与黑盒测试之间的一种测试方式。包括对内部数据结构和算法的了解,以便设计测试,同时在用户或黑盒水平上执行这些测试。
String
String、StringBuffer、StringBuilder
- String:不可变字符串,
- 保存字符串的数组用final和private修饰,并且不提供修改此字符串的方法。
- String类也被final修饰,使得它不能被继承,保证了String的不变性。
- String类从字符串生成器和其他构造字符串使用的是内部的副本
- StringBuffer:可变字符串,效率低,线程安全
- StringBuilder:可变字符序列,效率高,线程不安全
String 和 char 的区别
- 字符是基本数据类型,而字符串是 Java 中的类。
- char 表示单个字符,而字符串可以包含零个或多个字符。字符串是一个字符的数组。
- 在Java程序中使用单引号(')定义char,使用双引号(“)定义字符串。由于 String 是一个特殊的类,因此可以选择使用双引号定义字符串之外,还可以使用 new 关键字创建 String。
StringBuffer、StringBuilder 的区别?
No. |
StringBuffer |
StringBuilder |
1 |
字符串缓冲区是同步的,即线程安全 |
字符串生成器是非同步的,即线程不安全 |
2 |
字符串缓冲区比字符串生成器低效 |
字符串生成器比字符串缓冲区更有效率 |
3 |
字符串缓冲区在 Java 1.0 中引入 |
字符串生成器在 Java 1.5 中引入 |
常用字符集
- Single-Byte Encoding(单字节编码)
- ASCII 的 128 个字符集涵盖小写和大写字母、数字以及一些特殊字符和控制字符的英语字母表。原始 ASCII 未使用每个字节中最重要的位。其中一个更流行的 ASCII 扩展是 ISO-8859-1,也称为 “ISO Latin 1”。
- Multi-Byte Encoding(多字节编码)
- Unicode 作为标准,可以为世界上每个可能的字符定义码位。
- UTF-32 是 Unicode 的一种编码方案,它使用四个字节来表示由 Unicode 定义的每个码位。显然,为每个字符使用四个字节是空间效率低下的
- UTF-16 也是一种可变长度字符编码。这种编码方法是特殊的。它将字符编码为 2 个字节或 4 个字节。
- UTF-8 是 Unicode 的另一种编码方案,它采用可变长度的字节进行编码。由于其空间效率,是网络上使用的最常见的编码
- GB 2312使用两个字节来表示任何图形字符,并用于对中文进行编码
Lock in Java (Java里的锁)
什么是 CAS ?
CAS全称 Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。
CAS 算法涉及到三个操作:
- 需要读写的内存值 V
- 进行比较的值 A
- 要写入的新值
当且仅当 V 的值等于 A 时,CAS通过原子方式用新值B来更新V的值(“比较+更新”整体是一个原子操作),否则不会执行任何操作。一般情况下,“更新”是一个不断重试的操作
介绍一下 synchronized ?
synchronized内置锁是一种对象锁(锁的是对象而非引用),作用粒度是对象,可以用来实现对临界资源的同步互斥访问,是可重入的,重量级锁,性能较低。其可重入最大的作用是避免死锁。
介绍一下锁的升级?
锁状态存在四种分别是: 无状态, 偏向锁, 轻量级锁和重量级锁。 同时锁有一个特性只会升级不会降级。
- 首先对象处于无锁状态,接着thread-1访问对象的时候,通过cas操作去获取偏向锁并将锁的偏向位更改为1;(对象会记录下偏向线程thread-1的 id),此时线程1转为偏向锁
- 当另外一个thread-2到达的时候会比较自身线程id和对象头中的id是否一致,发现不一致就会去检测对象头中的线程thread-1是否存活, 如果thread-1还是存活的就升级为轻量级锁。
- 如果thread-2获取失败则说明存在竞争关系,这时将偏向锁升级为轻量级锁;升级为轻量级锁之后会在thread-2线程的栈帧中开辟一块锁记录空间叫做displaced Mark Word,并将锁对象的markword拷贝到线程本身的displaced Mark Word空间中,然后通过cas的方式去设置锁对像中线程id指针,并将锁的标志设置为00;
- 当其中一个线程的自旋次数超过阈值(默认是10)的时候为了防止cpu空转,会将自旋锁升级为重量级锁,将对象监视器的指针存储在对象头之中。
synchronized 和 volatile 的区别?
- volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
- volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
- volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞
- synchronized保证了原子性、可见性和有序性;而volatile只保证了可见性和有序性,没有保证原子性。
- volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
什么是死锁? 造成死锁的原因有哪些?
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。
死锁的几个条件:
- 互斥条件:针对资源来说,该资源任意一个时刻只由一个线程占用。
- 请求与保持条件:针对A来说,线程申请资源时,保持对原有资源的占有。
- 剥夺条件:针对拿到资源的线程A来说,资源只能由占有者释放,申请者不能强制剥夺。
- 循环等待条件:线程1等待2占有的资源,2等待3占有的资源,3等待1占有的资源。若干进程之间形成一种头尾相接的循环等待资源关系。
如何避免死锁:
- 设置超时时间, 使用JUC包中的Lock接口提供的tryLock方法, 如果超过了这个时间没拿到锁, 可以做其他的事情, synchronized如果没有拿到锁会一直等待下去。
- 多使用JUC包提供的并发类,而不是自己设计锁.例如 ConcurrentHashMap ConcurrentLinkedQueue AtomicBoolean 等等
- 尽量降低锁的使用粒度,锁的使用范围, 只要能满足业务要求, 范围越小越好
- 尽量使用同步方法 而不是同步代码块
- 避免锁的嵌套
- 正确的顺序获得锁
- 专锁专用,量不要几个功能用同一把锁。来避免锁的冲突
介绍一下什么是 AQS ?
AQS的全称为(AbstractQueuedSynchronizer),是一种抽象的队列式同步器。AQS的核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
注意:AQS是自旋锁:在等待唤醒的时候,经常会使用自旋(while(!cas()))的方式,不停地尝试获取锁,直到被其他线程获取成功
实现了AQS的锁有:自旋锁、互斥锁、读锁写锁
JVM
Debug JVM
Garbage Collector
Java 垃圾回收是 Java 程序执行自动内存管理的过程。是通过销毁不再使用的对象来回收运行时未使用内存的过程。
如何选择合适的收集算法?
- 新生代中,每次垃圾收集时都发现大批对象死去,只有少量对象存活,便采用了复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
- 老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须采用“标记-清理”或者“标记-整理”算法。
新生代怎么转化成老年代?
- Eden区满时,进行Minor GC:当Eden和一个Survivor区中依然存活的对象无法放入到Survivor中,则通过分配担保机制提前转移到老年代中
- 对象体积太大, 新生代无法容纳:-XX:PretenureSizeThreshold参数意思是如果对象的大小超过这个值的时候,对象直接在old区分配内存,默认值是0,即不管多大都是先在eden中分配内存。此参数只对Serial及ParNew两款收集器有效。
- Long-lived objects will enter the old age(长期存活的对象将进入老年代):虚拟机对每个对象定义了一个对象年龄(Age)计数器。当年龄增加到一定的临界值时,就会晋升到老年代中,该临界值由参数:-XX:MaxTenuringThreshold来设置。如果对象在Eden出生并在第一次发生MinorGC时仍然存活,并且能够被Survivor中所容纳的话,则该对象会被移动到Survivor中,并且设Age=1;以后每经历一次Minor GC,该对象还存活的话Age=Age+1。
- 动态对象年龄判定:虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升到老年代,如果在Survivor区中相同年龄(设年龄为age)的对象的所有大小之和超过Survivor空间的一半,年龄大于或等于该年龄(age)的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。
GC的触发条件是什么?
- 年轻代GC触发机制
When the young generation space is insufficient, Minor GC will be triggered. The young generation full here refers to the fullness of the Eden area, and the fullness of the survivor area will not trigger GC.
当年轻代空间不足时,就会触发Minor Gc,这里的年轻代满指的是Eden区满,Survivor区满不会引发GC。
Because most of the Java objects have the characteristics of life and death, Minor GC is very frequent, and the recovery speed is generally faster.
因为Java对象大多都具备朝生夕灭的特性,所以 Minor GC非常频繁,一般回收速度也比较快。
Minor GC will trigger STW, suspend other user’s threads, and wait until the end of garbage collection before user threads resume running.
Minor GC会引发STW,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行。
- 老年代GC触发机制
1)Refers to the GC that occurs in the old generation. When the object disappears from the old generation, the Major GC occurs.
指发生在老年代的Gc,对象从老年代消失时,发生Major GC。
2)Major GC appears, which is often accompanied by at least one Minor GC (but not absolutely, in the collection strategy of the Parallel Scavenge collector, there is a strategy selection process for directly conducting Major GC).
出现了Major GC,经常会伴随至少一次的Minor Gc(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。
That is, when the space in the old age is insufficient, the Minor Gc is generally tried to be triggered first. If there is not enough space afterward, a Major GC is triggered.
也就是在老年代空间不足时,一般会先尝试触发Minor Gc。如果之后空间还不足,则触发Major GC。
3)The speed of Major GC is generally more than 10 times slower than that of Minor GC, and the time of STW is longer.
Major GC的速度一般会比Minor GC慢10倍以上,STW的时间更长。
4)If the memory is not enough after the Major GC, it will report OOM.
如果Major GC后,内存还不足,就报OOM了。
- Full GC 触发机制 (Full GC trigger mechanism):
- System.gc()方法的调用
- 老年代空间不足
- 方法区【永久代】空间不足
- CMS GC时出现promotion failed和concurrent mode failure
- 堆中分配很大的对象
JVM 中确定垃圾的集中算法?
- 引用计数法:给对象中添加⼀个引用计数法,每当有⼀个地方引用它,计数器就加1;当引用失效,计数器就减1;任何时候计数器为0的对象就是不可能再被使用的
- 可达性分析算法:VM默认使用可达性分析算法。这个算法的基本思想就是通过⼀系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当⼀个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。
常用的垃圾回收算法?
- 标记-清除算法 (Mark-Sweep):算法分为“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统⼀回收所有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。
- 复制算法 (Coping):它可以将内存分为大小相同的两块,每次使用其中的⼀块。当这⼀块的内存使用完后,就将还存活的对象复制到另⼀块去,然后再把使用的空间⼀次清理掉。这样就使每次的内存回收都是对内存区间的⼀半进行回收。
- 标记-整理算法 (Mark-Compact):据老年代的特点特出的⼀种标记算法,标记过程仍然与“标记-清除”算法⼀样,但后续步骤不是直接对可回收对象回收,⽽是让所有存活的对象向⼀端移动,然后直接清理掉端边界以外的内存。
- 分代收集算法 (Generational Collection):当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。⼀般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进⾏分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。
介绍一下垃圾回收器?
- Serial收集器 (Serial Collector):Serial 是最基本垃圾收集器,使用复制算法,曾经是JDK1.3.1 之前新生代唯一的垃圾收集器。Serial 是一个单线程的收集器,它不仅只会使用一个 CPU 或一条线程去完成垃圾收集工作,而且在进行垃圾收集的同时,必须暂停其他所有的工作线程( “Stop The World”),直到垃圾收集结束。
- ParNew 收集器(ParNew Collector):ParNew 垃圾收集器其实是 Serial 收集器的多线程版本,也使用复制算法,除了使用多线程进⾏垃圾收集外,其余行为(控制、参数、收集算法、回收策略等等)和Serial收集器完全⼀样。它是许多运行在Server模式下的虚拟机的首要选择,除了Serial收集器外,只有它能与CMS收集器(真正意义上的并发收集器,后面会介绍到)配合工作。
- Parallel Scavenge收集器(Parallel Scavenge):Parallel Scavenge 收集器类似于ParNew 收集器。Parallel Scavenge 收集器也是一个新生代垃圾收集器,同样使用复制算法,也是一个多线程的垃圾收集器。它重点关注的是程序达到一个可控制的吞吐量(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),高吞吐量可以最高效率地利用 CPU 时间,尽快地完成程序的运算任务,主要适用于在后台运算而不需要太多交互的任务。
- Serial Old 收集器(Serial Old Collector):Serial Old 是 Serial 垃圾收集器的老年代版本,它同样是个单线程的收集器,使用标记-整理算法。这个收集器也主要是运行在 Client 默认的 java 虚拟机默认的老年代垃圾收集器。
- Parallel Old 收集器(Parallel Old Collector ):Parallel Old 收集器是Parallel Scavenge的老年代版本,使用多线程的标记-整理算法,在 JDK1.6才开始提供。在 JDK1.6 之前,新生代使用 Parallel Scavenge 收集器只能搭配老年代的 Serial Old 收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量,Parallel Old 正是为了在老年代同样提供 吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,可以优先考虑新生代 Parallel Scavenge和老年代 Parallel Old 收集器的搭配策略。
- CMS 收集器(Concurrent Mark Sweep Collector):CMS收集器是一种老年代垃圾收集器,其最主要目标是获取最短垃圾回收停顿时间,从而为交互比较高的程序提高用户体验。和其他老年代使用标记-整理算法不同,它使用多线程的标记-清除算法。
- G1 收集器(Garbage First Collector):G1通过把Java堆分成大小相等的多个独立区域,回收时计算出每个区域回收所获得的空间以及所需时间的经验值,根据记录两个值来判断哪个区域最具有回收价值,所以叫Garbage First(垃圾优先)。
G1 和 CMS 的对比 (Comparison of G1 and CMS):
- 优点
- G1 有整理内存过程,不会产生内存碎片
- G1 的 STW 更可控,在停顿时间上添加了预测机制,用户可以指定期望停顿时间
- 缺点
- 在用户程序运行过程中,G1无论是为了垃圾收集产生的内存占用(Footprint)还是程序运行时的额外执行负载(Overload)都要比CMS要高。
- 不同
- CMS基于增量更新,G1基于原始快照。
- CMS和G1都用写后屏障更新卡表,G1还需要写前屏障来实现STAB。
类加载
代码被编译之后会生成字节码文件,类的加载指的是将字节码文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的 Class对象, Class对象封装了类在方法区内的数据结构,并且向程序员提供了访问方法区内的数据结构的接口。
JVM 中类的加载流程:
- 根据类名或者包名来获取二进制字节流;
- 将字节流所代表的静态数据结构转化为方法区的运行时的数据结构;
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口;
什么是双亲委派模型?为什么要使用双亲委派?
双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
双亲委派保证类加载器,自下而上的委派,又自上而下的加载,保证每一个类在各个类加载器中都是同一个类。
OOP (面向对象编程)
Object Oriented Programming(OOP),中文为面向对象编程,它是一种使用类和对象设计程序的方法或范例。它通过提供一些概念简化了软件开发和维护:对象、类、继承、多态性、抽象和封装。
对象(Object)
一个有着状态和行为的实体被看成一个对象(比如:椅子,桌子,汽车等)。它可以是物理上的或者逻辑上的。一个对象就是一个类的实例,对象有这三种特性:
- 状态:表示对象的数据
- 行为:表示对象的行为(功能),比如:存款,取款等
- 标识:对象标识通常通过唯一的ID实现。外部用户看不到该ID的值,但是,JVM内部使用它来唯一的标识每个对象。
类(Class)
类是一组具有公共属性的对象。它是创建对象的模板或蓝图。它是一个逻辑实体。这不可以是物理的。Java中的类可以包含:字段、方法、构造函数、块、嵌套类和接口
创建Class对象的三种方法:
- Class.forName(“XXX”)
Class c = Class.forName(String className)
- Myclass.class
A a = new A();
Class c = A.class;
- obj.getClass()
A a = new A();
Class c = a.getClass();
Inheritance (继承)
Java中的继承是一种机制,其中一个对象获取父对象的所有属性和行为。它是OOPs的重要组成部分。Java继承背后的思想是,你可以创建基于现有类的新类。从现有类继承时,可以重用父类的方法和字段。此外,你还可以在当前类中添加新方法和字段。
Polymorphism (多态)
多态是同一个行为具有多个不同表现形式或形态的能力。
满足多态的三个条件:继承、重写、父类引用指向子类对象。
Overload (重载)
如果一个类有多个同名但参数不同的方法,则称为方法重载。
** java中重载方法有两种方式:**
Override (重写)
如果子类(子类)与父类中声明的方法相同,则在 Java 中称为方法覆盖。
Abstraction (抽象)
抽象是隐藏实现细节并仅向用户显示功能的过程。 另一种方式是,它只向用户显示基本信息并隐藏内部细节。
Abstract class (抽象类)
声明为abstract的类称为抽象类。它可以有抽象方法和非抽象方法,它需要扩展并实现其方法,它不能被实例化,它可以有构造方法和静态方法;它也可以有final方法,这将强制子类不改变方法的主体。
Interface (接口)
Java中的接口是一种实现抽象的机制,它具有静态常量和抽象方法。Java接口中只能有抽象方法,不能有方法体。它用于在Java中实现抽象和多重继承。
抽象类跟接口的区别
- 抽象类可以有抽象和非抽象方法。接口只能有抽象方法。从 Java 8 开始,它也可以有默认和静态方法。
- 抽象类不支持多重继承。接口支持多重继承
- 抽象类可以有final、非final、static和非static变量。接口只有静态常量。
- 抽象类可以提供接口的实现。接口不能提供抽象类的实现。
- abstract 关键字用于声明抽象类。 interface 关键字用于声明接口。
- 一个抽象类可以扩展另一个 Java 类并实现多个 Java 接口。一个接口只能扩展另一个 Java 接口。
- 可以使用关键字“extends”扩展抽象类。可以使用关键字“implements”来实现接口。
- Java 抽象类可以具有私有、受保护等类成员。Java 接口的成员默认情况下是公共的。
简单来说,抽象类实现了部分抽象(0 到 100%),而接口实现了完全抽象(100%)。
封装
Java 中的封装是将代码和数据一起包装成一个单元的过程。
五个面向对象设计原则 - SOLID原则
-
S - Single-responsiblity Principle (单一职责原则)
一个类应该只有一个改变的理由,这意味着一个类应该只有一个职责。
-
O - Open-closed Principle (开闭原则)
对象或实体应该对扩展开放但对修改关闭。这意味着一个类应该是可扩展的,而不需要修改类本身。
-
L - Liskov Substitution Principle (里式替换原则)
里氏替换原则是一个非常有用的一个概念。他的定义:
如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有对象o1都替换成o2的时候,程序P的行为都没有发生变化,那么类型T2是类型T1的子类型。
通俗的去讲就是:子类可以去扩展父类的功能,但是不能改变父类原有的功能。
-
I - Interface Segregation Principle (接口隔离原则)
永远不应该强迫客户端实现它不使用的接口,或者不应该强迫客户端依赖他们不使用的方法。
-
D - Dependency Inversion Principle (依赖倒置原则)
依赖倒置原则指的是一种特殊的解耦方式,它指出高级模块不能依赖于低级模块,但它们应该依赖于抽象,依赖模块被颠倒了。实体必须依赖于抽象,而不是实体。
Generics (泛型)
Java 泛型是 JDK 5 中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的数据类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数,然后在使用时再指定此参数具体的值,这样这个类型就可以在使用时决定了。这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
泛型的作用:
泛型参数可以增强代码的可读性以及稳定性。编译器可以对泛型参数进行检测,并且通过泛型参数可以指定传入的对象类型。
- 保证了类型的安全性:泛型约束了变量的类型,保证了类型的安全性。
- 提高程序的性能:泛型变量固定了类型,使用的时候就已经知道是值类型还是引用类型,避免了不必要的装箱、拆箱操作。
- 提高方法、算法的重用性
桥方法 (Bridge Method)
如果一个类继承了一个泛型类或者实现了一个泛型接口, 那么编译器在编译这个类的时候就会生成一个叫做桥接方法的混合方法(混合方法简单的说就是由编译器生成的方法, 方法上有synthetic修饰符), 这个方法用于泛型的类型安全处理, 用户一般不需要关心桥接方法。
堆污染 (Heap pollution)
当一个不带泛型的对象赋值给一个带泛型的变量时,就有可能发生堆污染。
原因:
在定义泛型对象(泛型类的对象)时,是否显示指定泛型类型不是强制的(可以在后面的运行中确定),这就造成了ClassCastException隐患,这个异常隐患不会在编译时抛出,而是在运行时抛出。
PECS 原则: Producer Extends, Consumer Super
Producer extends: 如果我们需要一个 List 提供类型为 T 的数据(即希望从 List 中读取 T 类型的数据), 那么我们需要使用 ? extends T, 例如 List extends Integer>. 但是我们不能向这个 List 添加数据。
Consumer Super: 如果我们需要一个 List 来消费 T 类型的数据(即希望将 T 类型的数据写入 List 中), 那么我们需要使用 ? super T, 例如 List super Integer>. 但是这个 List 不能保证从它读取的数据的类型。
如果我们既希望读取, 也希望写入, 那么我们就必须明确地声明泛型参数的类型, 例如 List
Reflection (反射)
反射是一种API,用于在运行时检查或修改方法、类和接口的行为。反射所需的类是在java.lang.reflect包中提供的。反射为我们提供了有关对象所属的类的信息,以及可以使用该对象执行的该类的方法。
Serialization (序列化)
Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程。
序列化:序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。核心作用是对象状态的保存与重建。我们都知道,Java对象是保存在JVM的堆内存中的,也就是说,如果JVM堆不存在了,那么对象也就跟着消失了。而序列化提供了一种方案,可以让你在即使JVM停机的情况下也能把对象保存下来的方案。就像我们平时用的U盘一样。把Java对象序列化成可存储或传输的形式(如二进制流),比如保存在文件中。这样,当再次需要这个对象的时候,从文件中读取出二进制流,再从二进制流中反序列化出对象。
反序列化:客户端从文件中或网络上获得序列化后的对象字节流,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。
Collections (集合)
Java中的集合是一个框架,它提供了一个架构来存储和操作一组对象。它可以实现对数据执行的全部操作,例如搜索、排序、更新、插入和删除。
Multi-thread (多线程)
Annotations (注解)
Network (计算机网络)
String (字符串)
Nested Class (Java 内部类)
Final, static, abstract
NIO and IO Stream (Java IO流、Stream 流)
Logging
Exceptions (异常)
Packaging (Java 包)
OWASP Top 10 (十大安全漏洞列表)
XML Parsers (XML解析器)
Regex (正则表达式)
Spring
Redis
MySQL
分布式架构
CAP理论
- 一致性C (Consistency)
- 可用性A (Availability)
- 分区容错性P (Partition tolerance)
根据CAP原理,将NoSQL数据库分成了满足CA原则,满足CP原则和满足AP原则三大类:
- CA:单点集群,满足一致性,可用性的系统,通常可扩展性较差
- CP:满足一致性,分区容错的系统,通常性能不是特别高
- AP:满足可用性,分区容错的系统,通常可能对一致性要求低一些
微服务
微服务架构是一种架构模式,它提倡将单一的应用程序划分成一组组小的服务,每个服务运行在其独立的进程内,服务之间互相协调,采用轻量级的通信机制(HTTP)互相沟通。各个服务能够被独立的部署到生产环境中,应尽量避免统一的,集中式的服务管理机制,但可以有一个非常轻量级的集中式管理来协调这些服务。
简单来说就是将传统的一站式应用,根据业务拆分成一个一个的服务,彻底的去耦合,每一个微服务提供单个业务功能的服务,一个服务做一件事情,从技术角度看就是一种小而独立的处理过程,类似进程的概念,能够自行单独启动或销毁,拥有自己独立的数据库
微服务的几大组件
注册中心
注册中心它记录了服务和服务地址的映射关系,在分布式架构中,服务会注册到这里,当服务需要调用其他服务时,就在这里找到服务的地址,进行调用。注册中心是整个服务调用的核心部分,如果服务在注册中心中不存在,那么通过网关会调用不到,导致失败。
常见的注册中心:
功能对比 |
eureka |
consul |
nacos |
配置中心功能 |
不支持 |
支持,但是用起来偏麻烦,不太符合spring Boot框架的命名风格,支持动态刷新 |
支持,用起来简单,符合springBoot的命名风格,支持动态刷新 |
依赖 |
依赖zookeeper |
不依赖其他组件 |
不依赖其他组件 |
应用内/外 |
直接集成到应用中,依赖于应用自身完成服务的注册与发现 |
属于外部应用,侵入性小 |
属于外部应用,侵入性小 |
ACP原则 |
遵循AP(可用性+分离容忍)原则,有较强的可用性,服务注册块,单牺牲了一定的一致性 |
遵循CP原则(一致性+分离容忍)服务注册稍慢,由于其一致性导致了在leader挂掉重新选举期间整个consul不可用 |
通知遵循CP原则(一致性+分离容忍)和AP原则(可用性+分离容忍) |
版本迭代 |
目前已经不进行升级 |
目前仍然进行版本迭代 |
目前仍然进行版本迭代 |
集成支持 |
只支持SpringCloud集成 |
支持SpringCloud K8S集成 |
支持Dubbo,SpringCloud,K8S集成 |
访问协议 |
HTPP |
HTTP/DNS |
HTTP/动态DNS/UDP |
雪崩保护 |
支持 |
不支持 |
支持 |
界面 |
英文界面 |
英文界面,不符合国人习惯 |
中文界面 |
上手 |
容易 |
复杂一点 |
极易,中文文档,案例,社区活跃 |
配置中心
管理各个环境的配置文件参数,比如说数据库,缓存,存储,业务应用并且支持管理每个不同的环境的配置。
配置中心作用:
- 本地配置在服务启动加载,修改配置不需要重启服务
- 多个环境(dev,prod,sit,uat)容易混淆,会产生错误,导致服务运行异常
- 出现配置错误时,不容易回滚到指定的版本
常用配置中心对比:
功能 |
Spring Cloud Config |
Apollo |
Nacos |
实效性 |
SpringCloudBus |
HTTP长轮询 |
HTTP长轮询 |
权限控制 |
支持 |
支持 |
支持 |
灰度发布 |
支持 |
支持 |
不支持 |
版本管理 |
支持 |
支持 |
支持 |
版本回滚 |
支持 |
支持 |
支持 |
多环境支持 |
支持 |
支持 |
支持 |
服务网关
网关是微服务架构中提供路由转发与鉴权等功能,首先,它会提供最基本的路由服务,将客户端请求转发后台业务服务;其次,作为一个入口,它还可以进行认证,鉴权,限流等操作。
网关的作用:
- 客户端访问的统一对接接口
- 防止内部接口直接暴露给外部客户端(隐藏内部服务)
- API网关通过提供一个额外的保护层来防止恶意攻击,例如SQL注入,XML解析器漏洞和拒绝服务
- 服务网关的前置过滤器中,所有请求过来进行权限校验
- 日志访问与审计
常用的网关组件:
- KONG是一个通过lua-nginx-module实现的在nginx中运行的lua应用程序
- Spring Cloud Gateway是由Spring官方基于Spring5.0等技术开发的网关,目的是代替原先版本中的Zuul
- Zuul
负载均衡
服务提供方一般以多实例的形式提供服务,负载均衡功能能够让服务调用方连接到合适的服务节点。并且,节点选择的工作对服务调用方来说是透明的。
熔断
在微服务架构中,一个服务作为调用方调用另一个服务时,为了防止被调用服务出现问题进而导致调用服务出现问题,所以调用服务需要进行自我保护,而保护的常用手段就是熔断。
熔断,降级,限流:
- 限流:限制并发的请求访问量,超过阈值则拒绝;
- 降级:服务分优先级,牺牲非核心服务(不可用),保证核心服务稳定;从整体负荷考虑;
- 熔断:依赖的下游服务故障触发熔断,避免引发本系统崩溃;系统自动执行和恢复
什么是服务雪崩?
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的“扇出”,如果扇出的链路上某个微服务的调用响应时间过长,或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,这就是所谓的“雪崩效应”。
什么是服务限流?
服务限流指当系统资源不够的情况下,不足以应对大量的用户与数据接口请求时,为了保证优先的资源能够正常服务,因此对系统按照预设的规则进行流量限制或功能限制的一种方法
什么是服务降级?
服务降级是指当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理,或换种简单的方式处理,从而释放服务器资源以保证核心业务正常运作或高效运作。说白了,就是尽可能的把系统资源让给优先级高的服务。
服务降级的分类:
- 超时降级:主要配置好超时时间和超时重试次数和机制,并使用异步机制探测回复情况
- 失败次数降级:主要是一些不稳定的api,当失败调用次数达到一定阀值自动降级,同样要使用异步机制探测回复情况
- 故障降级:比如要调用的远程服务挂掉了(网络故障、DNS故障、http服务返回错误的状态码、rpc服务抛出异常),则可以直接降级。降级后的处理方案有:默认值(比如库存服务挂了,返回默认现货)、兜底数据(比如广告挂了,返回提前准备好的一些静态页面)、缓存(之前暂存的一些缓存数据)
- 限流降级:秒杀或者抢购一些限购商品时,此时可能会因为访问量太大而导致系统崩溃,此时会使用限流来进行限制访问量,当达到限流阀值,后续请求会被降级;降级后的处理方案可以是:排队页面(将用户导流到排队页面等一会重试)、无货(直接告知用户没货了)、错误页(如活动太火爆了,稍后重试)
什么是服务熔断?
在多个微服务之间调用的时候,当某个服务单元发生故障之后,通过断路器的故障监控 (类似熔断保险丝) ,向调用方返回一个服务预期的,可处理的备选响应 (FallBack) ,而不是长时间的等待或者抛出调用方法无法处理的异常,这样就可以保证了服务调用方的线程不会被长时间,不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。这就是服务熔断。
服务熔断和降级的区别?
- 服务熔断—>服务端:某个服务超时或异常,引起熔断~,类似于保险丝(自我熔断)
- 服务降级—>客户端:从整体网站请求负载考虑,当某个服务熔断或者关闭之后,服务将不再被调用,此时在客户端,我们可以准备一个 FallBackFactory ,返回一个默认的值(缺省值)。会导致整体的服务下降,但是好歹能用,比直接挂掉强。
- 触发原因不太一样,服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;管理目标的层次不太一样,熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务开始)
- 实现方式不太一样,服务降级具有代码侵入性(由控制器完成/或自动降级),熔断一般称为自我熔断。
服务调用
服务调用,即一个服务调用另一个服务,此过程可以分为服务调用者、服务提供者。基本上都会使用注册中心来作为中间件。