JAVA
1.hashmap 线程不同步 hashtable 同步
2.arraylist线程不同步,vector线程同步
3.vector增长为100%,arraylist为50%
JDK1.8新特性
1.lambda表达式
//匿名内部类
Comparator<Integer> cpt = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1,o2);
}
};
TreeSet<Integer> set = new TreeSet<>(cpt);
System.out.println("=========================");
//使用lambda表达式
Comparator<Integer> cpt2 = (x,y) -> Integer.compare(x,y);
TreeSet<Integer> set2 = new TreeSet<>(cpt2);
2.Stream流:首先对stream的操作可以分为两类,中间操作(intermediate operations)和结束操作(terminal operations):
中间操作总是会惰式执行,调用中间操作只会生成一个标记了该操作的新stream。
结束操作会触发实际计算,计算发生时会把所有中间操作积攒的操作以pipeline的方式执行,这样可以减少迭代次数。计算完成之后stream就会失效。
就是简化一些业务的逻辑的写法,提升编码效率
List<String> list = Arrays.asList("ab", "cd", "ef");
Stream<String> colStream = list.stream();
// 3、值
Stream<String> stream = Stream.of("ab", "cd", "ef");
list.stream().forEach(user -> System.out.println(user));
集合
ArrayList和linkedList底层实现原理
Arraylist是基于动态数组实现的,所以查找速度快,但是增删操作的速度会比较慢 。当我们使用new来创建一个数组,实际上是在堆上申请了一段连续的大内存,我们知道我们在java中创建数组的时候,会给他一个固定的大小,不能适应数据的动态增删,那么这个时候就有了所谓的动态数组,动态数组的本质就是,当数据超出当前数组的内存范围,会先试着比较数据长度和数组长度的1.5倍的大小,如果1.5倍大于元素长度(小于的话,则创建一个长度为数据长度的数组),那么直接通过Arrays.copyOf得到一个新长度的数组,把原数据拷贝过去,释放掉旧的数组, 这个时候内存有很多是没有使用的,这个时候就可以执行增删操作。
LinkedList是基于双向链表的数据结构实现的,链表是可以占用一段不连续的内存空间的。双向链表有前驱节点和后驱节点,里面存储的是上一个元素和后一个元素所在的位置,中间的黄色部分就是业务数据了,当我们需要执行插入的任务,比如第一个元素和第二个元素之间,只需要改变他们的前驱节点和后驱节点的指向就可以了,不要像动态数组那么麻烦,删除也是同样的操作,但是因为是不连续的内存空间,当需要执行查找,需要从第一个元素开始查找,直到找到我们需要的数据。
3.函数式接口,只定义了一个抽象方法的接口,更方便使用lambda表达式。 @FunctionalInterface
4.接口可以定义默认方法。
5.可以使用::引用构造函数或者静态方法
反射
Java的反射就是利用上面第二步加载到jvm中的.class文件来进行操作的。.class文件中包含java类的所有信息,当你不知道某个类具体信息时,可以使用反射获取class,然后进行各种操作。 在java中,只要给定类的名字, 那么就可以通过反射机制来获得类的所有信息。
反射机制
1.通过一个对象获取完整的包名和类名。
package Reflect;
class Demo{
//other codes...
}
class hello{
public static void main(String[] args) {
Demo demo=new Demo();
System.out.println(demo.getClass().getName());
}
}
//【运行结果】:Reflect.Demo
2.实例化Class类对象
package Reflect;
class Demo{
//other codes...
}
class hello{
public static void main(String[] args) {
Class<?> demo1=null;
Class<?> demo2=null;
Class<?> demo3=null;
try{
//一般尽量采用这种形式
demo1=Class.forName("Reflect.Demo");
}catch(Exception e){
e.printStackTrace();
}
demo2=new Demo().getClass();
demo3=Demo.class;
System.out.println("类名称 "+demo1.getName());
System.out.println("类名称 "+demo2.getName());
System.out.println("类名称 "+demo3.getName());
}
}
//【运行结果】:
//类名称 Reflect.Demo
//类名称 Reflect.Demo
//类名称 Reflect.Demo
3.通过Class实例化其他类的对象
package Reflect;
class Person{
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString(){
return "["+this.name+" "+this.age+"]";
}
private String name;
private int age;
}
class hello{
public static void main(String[] args) {
Class<?> demo=null;
try{
demo=Class.forName("Reflect.Person");
}catch (Exception e) {
e.printStackTrace();
}
Person per=null;
try {
per=(Person)demo.newInstance();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
per.setName("Rollen");
per.setAge(20);
System.out.println(per);
}
}
//【运行结果】:
//[Rollen 20]
但是不能自定义有参构造函数。
4.通过反射调用其他类的中的构造函数
package Reflect;
import java.lang.reflect.Constructor;
class Person{
public Person() {
}
public Person(String name){
this.name=name;
}
public Person(int age){
this.age=age;
}
public Person(String name, int age) {
this.age=age;
this.name=name;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString(){
return "["+this.name+" "+this.age+"]";
}
private String name;
private int age;
}
class hello{
public static void main(String[] args) {
Class<?> demo=null;
try{
demo=Class.forName("Reflect.Person");
}catch (Exception e) {
e.printStackTrace();
}
Person per1=null;
Person per2=null;
Person per3=null;
Person per4=null;
//取得全部的构造函数
Constructor<?> cons[]=demo.getConstructors();
try{
per1=(Person)cons[0].newInstance();
per2=(Person)cons[1].newInstance("Rollen");
per3=(Person)cons[2].newInstance(20);
per4=(Person)cons[3].newInstance("Rollen",20);
}catch(Exception e){
e.printStackTrace();
}
System.out.println(per1);
System.out.println(per2);
System.out.println(per3);
System.out.println(per4);
}
}
//【运行结果】:
//[null 0]
//[Rollen 0]
//[null 20]
//[Rollen 20]
5.可以调用其他类的接口。
6.取得父类(getSuperclass)
7.取得其它类全部属性
class hello {
public static void main(String[] args) {
Class<?> demo = null;
try {
demo = Class.forName("Reflect.Person");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("===============本类属性========================");
// 取得本类的全部属性
Field[] field = demo.getDeclaredFields();
for (int i = 0; i < field.length; i++) {
// 权限修饰符
int mo = field[i].getModifiers();
String priv = Modifier.toString(mo);
// 属性类型
Class<?> type = field[i].getType();
System.out.println(priv + " " + type.getName() + " "
+ field[i].getName() + ";");
}
System.out.println("===============实现的接口或者父类的属性========================");
// 取得实现的接口或者父类的属性
Field[] filed1 = demo.getFields();
for (int j = 0; j < filed1.length; j++) {
// 权限修饰符
int mo = filed1[j].getModifiers();
String priv = Modifier.toString(mo);
// 属性类型
Class<?> type = filed1[j].getType();
System.out.println(priv + " " + type.getName() + " "
+ filed1[j].getName() + ";");
}
}
}
//【运行结果】:
//===============本类属性========================
//private java.lang.String sex;
//===============实现的接口或者父类的属性========================
//public static final java.lang.String name;
//public static final int age;
getName():获得类的完整名字。
getFields():获得类的public类型的属性。
getDeclaredFields():获得类的所有属性。包括private 声明的和继承类
getMethods():获得类的public类型的方法。
getDeclaredMethods():获得类的所有方法。包括private 声明的和继承类
getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
getConstructors():获得类的public类型的构造方法。
getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。
newInstance():通过类的不带参数的构造方法创建这个类的一个对象。
String、StringBuffer、StringBuilder
StringBuffer(很多方法是synchronized)和StringBuilder(线程不安全,速度快)
String所有属性都被final修饰、私有的并且没有提供修改方法。
String不可变:
字符串线程池的需求。
安全性考虑。
hash缓存。保持hash码的唯一性。
引用
强引用 new,不会被GC
软引用 ,新建对象时,如果堆不够啦,就GC。
弱引用,只能活到下次GC前。
虚引用, 虚引用一般会配合 引用队列(ReferenceQueue)
来使用。当某个被虚引用指向的对象被回收时,我们可以在其引用队列中得到这个虚引用的对象作为其所指向的对象被回收的一个通知 。必须和引用队列使用。
拷贝
假设B复制了A,当A修改时,如果B变了就是浅拷贝,如果没变就是深拷贝。
Integer和int
Ingeter是int的包装类,int的初值为0,Ingeter的初值为null。
int是基本类型,Integer是对象
Integer.parseInt(进制转换)
Integer.toBinaryString(转二进制)
Integer.MIN_VALUE
Integer.MAX_VALUE
AtomicInteger
incrementAndGet(); // ++i
getAndIncrement(); // i++
decrementAndGet(); // --i
getAndDecrement(); // i–
原理
其内部存储了volatile
修饰的value
作为具体值,volatile
避免指令重排,保证了该value
的多线程可见性,即修改时其它线程能获取到最新值。
以incrementAndGet()
方法为例
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt) {
int i;
do {
i = getIntVolatile(paramObject, paramLong); // 获取主存的值
} while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));
// CAS操作,从内存中取出偏移量,比较数据与期望值一致,并修改新值。
return i;
}
CAS是乐观锁的一种实现方式,利用CPU自身特性保证原子性,避免类似悲观锁的线程切换,性能较强。
hashcode
hashcode代表对象的地址说的是对象在hash表中的位置,物理地址说的对象存放在内存中的地址
HashCode的存在主要是为了查找的快捷性,HashCode是用来在散列存储结构中确定对象的存储地址的(后半句说的用hashcode来代表对象就是在hash表中的位置)
接口和抽象类
接口
接口是一种特殊的抽象类,只包含抽象方法。不可以定义成员变量。一个类可以实现接口,重写接口所有方法。一个类可以实现多个接口。
一个接口类型的变量可以引用实现了改接口的类的对象,通过该变量可以调用该接口中定义的方法。
接口也可以继承接口、
抽象类和接口的区别
- 定义抽象类的关键字是abstract class,定义接口的关键字interface。
- 继承抽象类的关键字是extends,实现接口的关键字是implements。
- 继承抽象类支持单继承,实现接口可以多实现。
- 抽象类中可以有构造方法,而接口中不可以有构造方法。
- 抽象类中可以有成员变量,接口中只可以有常量。
- 抽象类中可以有成员方法,接口中只可以有抽象方法。
- 抽象类中增加方法可以不影响子类,接口中增加方法通常影响子类。
- 从jdk1.8开始,允许接口中出现非抽象方法,但需要使用default关键字修饰。
锁
死锁
产生死锁的必要条件:
-
互斥条件:进程占用资源的时候不允许其他进程占用。
-
请求和保持条件:阻塞时,不放。
-
不剥夺条件:未使用完成时,不能剥夺。
-
环路等待条件:死锁时,必然存在一个进程-资源的环形链
-
预防死锁:
1.资源一次性分配:
2.只要有一个资源得/到分配,其他资源也不给进程分配
3.分配编号,每一个进程挨个来请求资源
几种锁
重量级锁(阻塞)
自旋锁
线程的阻塞和唤醒需要CPU从用户态转为核心态,频繁的阻塞和唤醒对CPU来说是一件负担很重的工作,势必会给系统的并发性能带来很大的压力。
所谓自旋锁,就是让该线程等待一段时间,不会被立即挂起,看持有锁的线程是否会很快释放锁。怎么等待呢?执行一段无意义的循环即可(自旋)。
轻量级锁
轻量级锁的目标是,减少无实际竞争情况下,使用重量级锁产生的性能消耗。就是使用CAS操作
使用轻量级锁时,不需要申请互斥量,仅仅将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record(Lock Record:JVM检测到当前对象是无锁状态,则会在当前线程的栈帧中创建一个名为LOCKRECOD表空间用于copy Mark word 中的数据),如果更新成功,则轻量级锁获取成功,记录锁状态为轻量级锁;否则,说明已经有线程获得了轻量级锁,目前发生了锁竞争(不适合继续使用轻量级锁),接下来膨胀为重量级锁。
偏向锁
偏向锁假定将来只有第一个申请锁的线程会使用锁(不会有任何线程再来申请锁),因此,只需要在Mark Word中CAS记录owner(本质上也是更新,但初始值为空),如果记录成功,则偏向锁获取成功,记录锁状态为偏向锁,以后当前线程等于owner就可以零成本的直接获得锁;否则,说明有其他线程竞争,膨胀为轻量级锁
锁升级
刚开始判断是否有锁,锁支持偏向锁,获取到锁资源的线程会优先让它再去获取这个锁,如果没有获取到这个锁就升级成轻量级锁(CAS、乐观锁),如果CAS没有设置成功会进行自旋,自旋到一定次数会升级成synchronized重量级锁。
synchronized和volatile
synchronized(JVM中实现)是怎么做到的,synchronized 先锁住 共享的内存变量,然后线程A修改完之后将值返回到主内存,然后线程B获得锁以后才能获取值,所以通过代码层面的锁可以解决这个线程之间通信的 可见性问题。
如何实现synchronized
JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法 。方法执行时候,会检查标志是否设置,设置了就进行如下操作。
Java对象头
在JVM中对象的存储
对象头主要由Mark Word 和Class Metadata Address组成。
Mark Word:存对象的hashcode,锁信息或分代年龄或GC标志。
**Class Metadata Address:**类型指针指向的类元数据,JVM通过这个指针确定该对象是什么类的实例。
底层通过monitorenter和monitorexit实现的。前者指向同步代码块开始位置,后者指向代码块结束位置。当执行monitorenter时,当前线程会尝试获取对象锁所对应的monitor持有权。取得monitor时,计数器就变为1。若之前取过monitor,会使得计数器加1.若不是当前线程拥有monitor,那么该线程会阻塞,等待其他线程使用结束为止。当前线程完成synchronized操作以后会把monitor归置为0.
monitor存在于对象头中。
对于声明了 volatile 的变量,进行写操作的时候, JVM 会向 处理器发送一条 lock 的前缀指令。
volatile写操作时,将这个变量在自己工作内存中的值,写回到主内存,
每个处理器会 嗅探到 总线上的所传播的数据来检测自己缓存中的值是不是过期了, 处理器的缓存对应的内存地址被修改以后,它就会将当前的处理器缓存的值设置为失效状态,然后去读那个最新的值。
当对volatile标记的变量进行修改时,会将其他缓存中存储的修改前的变量清除,然后重新读取。一般来说应该是先在进行修改的缓存A中修改为新值,然后通知其他缓存清除掉此变量,当其他缓存B中的线程读取此变量时,会向总线发送消息,这时存储新值的缓存A获取到消息,将新值穿给B。最后将新值写入内存。当变量需要更新时都是此步骤,volatile的作用是被其修饰的变量,每次更新时,都会刷新上述步骤
volatile可以修饰变量,共享变量 。
synchronized
修饰一个代码块
2.修饰一个方法
3.修饰一个类
4.修饰一个静态的方法
ThreadLocal
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定的线程中可以获取到存储的数据,对于其他线程来说则无法取到数据。
多线程时候static一个对象 就不行啦。
原理
set方法,获取线程的 ThreadLocalMap ,这个是hashmap, key是TheadLocal,value是对应存储的值。
get方法,getMap获取ThreadLocalMap ,根据key==当前threadlocal来获取对应的value值。
从ThreadLocal的set和get方法可以看出,它们所操作的都是当前线程的ThreadLocalMap对象。
因此在不同线程中,访问同一个ThreadLocal的set和get方法,它们对ThreadLocalde的读、写操作仅限于各自线程的内部,从而使ThreadLocal可以在多个线程中互不干扰地存储和修改数据。
ReentrantLock
主要利用CAS+AQS队列来实现。支持公平锁和非公平锁。
ReentrantLock的基本实现可以概括为:先通过CAS尝试获取锁。如果此时已经有线程占据了锁,那就加入AQS队列并且被挂起。当锁被释放之后,排在CLH队列队首的线程会被唤醒,然后CAS再次尝试获取锁。在这个时候,如果:
非公平锁:如果同时还有另一个线程进来尝试获取,那么有可能会让这个线程抢先获取;
公平锁:如果同时还有另一个线程进来尝试获取,当它发现自己不是在队首的话,就会排到队尾,由队首的线程获取到锁。
可重入锁和可中断锁
可重入锁。可重入锁是指同一个线程可以多次获取同一把锁。ReentrantLock和synchronized都是可重入锁。
可中断锁。可中断锁是指线程尝试获取锁的过程中,是否可以响应中断。synchronized是不可中断锁,而ReentrantLock则提供了中断功能。
手写Lock锁三部曲
任何锁的实现,都是这么实现的。
1.实现Lock接口。
2.多线程场景–安全性。
syn锁的底层是CAS。
Hashmap
1.7使用头插法,在扩容的过程,resize方法调用transfer方法把entry进行了一个rehash,过程中可能会造成链表的循环,出现死循环。同时多个线程并发情况下,不能保证线程安全。
1.8变成了数组+链表+红黑树这么一个结构,尾插法不会改变数据插入的顺序。Entry结点变成Node结点,整个put过程也做了优化,
不用数组
扩容很不方便。
ArrayList的源码,看add和remove size会变
LinkedList
查询很慢。可以插入删除简单
hashmap
abstractmap 抽象骨架类
Hash是什么?
散列:输出是固定长度
· 目的寻找数组下标
1.求余hash%16得到下标 hash&(n-1)
max=1111=16-1
min=0
扩容
默认数组大小16,元素超过12个,就要扩容一倍。
换一个大的数组。
一致性哈希算法
hash算法缺陷
分布式架构缓存处理:(如果有4台缓存服务器)
Hash算法分散数据存储hash(n)%4=
同时可以快速查找数据而不用遍历所有的服务器。
如果加一台缓存服务器 就得重新计算了。于是得使用一致性hash算法。
hash环
一致性hash算法是对232取模,得到一个值K1,hash环上顺时针找到服务器节点。因为一致性hash是对服务器的负载均衡问题的,服务器IP是32位,所以对232取模。
**优势:**如果B失效了,可以迁移至C。
通过减少影响范围的方式解决了增减服务器导致的数据散列问题,从而解决分布式环境下的负载均衡问题。
为什么使用红黑树?
红黑树插入修改数据时会需要最多两次旋转使其达到平衡
平衡AVL树可能需要O(log n)旋转、
AVL树是更加严格的平衡,因此可以提供更快的查找速度,一般读取查找密集型任务,适用AVL树。
HashTable
synchronized多线程共享安全问题
HashTable线程安全。多线程不用HashTable,效率低,锁太重了。
ConcurrentHashMap
结构–HashMap一样 --数组+链表+红黑树
线程安全
多线程,有多个栈.
CAS机制
(只能确保一个共享变量的原子操作,循环时间长,ABA问题)
ThreadLocal 内存泄漏问题
ThreadLocalMap生命周期和线程一样长
短生命周期持 长的生命周期对象, 内存泄漏。
弱引用。
对象new=强引用。方法没有运行完。JVM 垃圾回收机制。对象不回收。
弱引用—对象。JVM GC垃圾回收。垃圾可以回收。
分段锁
Collections.synchronizedMap
对象排斥锁mutex
Tomcat
类加载机制,
启动类加载器
系统类加载器
通用类加载器
webapp加载器。WEB-INF/classes
webapp加载器。WEB-INF/lib
tomcat8可以配置不打破双亲委派机制
加载顺序
web加载器
不到再向上Common,System
不加载到启动类加载器是因为 主要是为了防止一些基础类会被web中的类覆盖 。
线程池
什么是线程池
是装有线程的池子,我们可以把要执行的多线程交给线程池来处理,和连接池的概念一样,通过维护一定数量的线程池来达到多个线程的复用。
线程池的好处。
,每个线程都要通过new Thread(xxRunnable).start()的方式来创建并运行一个线程,线程少的话这不会是问题,而真实环境可能会开启多个线程让系统和程序达到最佳效率,当线程数达到一定数量就会耗尽系统的CPU和内存资源,也会造成GC频繁收集和停顿,因为每次创建和销毁一个线程都是要消耗系统资源的,如果为每个任务都创建线程这无疑是一个很大的性能瓶颈。所以,线程池中的线程复用极大节省了系统资源,当线程一段时间不再有任务处理时它也会自动销毁,而不会长驻内存 。
线程池工作流程
1、如果线程池中的线程小于corePoolSize时就会创建新线程直接执行任务。
2、如果线程池中的线程大于corePoolSize时就会暂时把任务存储到工作队列workQueue中等待执行。
3、如果工作队列workQueue也满时,当线程数小于最大线程池数maximumPoolSize时就会创建新线程来处理,而线程数大于等于最大线程池数maximumPoolSize时就会执行拒绝策略。
拒绝策略
第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满
第二种DisCardPolicy:不执行新任务,也不抛出异常
第三种DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行
第四种CallerRunsPolicy:直接调用execute来执行当前任务
线程池分类
Executors是jdk里面提供的创建线程池的工厂类,它默认提供了4种常用的线程池应用,也可以直接调用 ThreadPoolExecutor的构造函数来自己创建线程池 。或者使用 guava提供的ThreadFactoryBuilder来创建线程池。
**newFixedThreadPool **:固定线程池。
**newCachedThreadPool **:带缓冲的线程池。
**newSingleThreadExecutor **:单线程线程池。
newScheduledThreadPool :调度线程池,按一定周期执行任务,定时执行任务。
提交线程
es.submit(xxRunnble);(返回一个Future对象,就是结果)
es.execute(xxRunnble);(无返回值){
- 获取当前线程池的状态。
- 当前线程数量小于coreSize时创建一个新的线程运行。
- 如果当前线程处于运行状态,并且写入阻塞队列成功。
- 双重检查,再次获取线程状态;如果线程状态变了(非运行状态)就需要从阻塞队列移除任务,并尝试判断线程是否全部执行完毕,同时执行拒绝策略。
- 如果当前线程池为空就新创建一个线程并执行。
- 如果在第三步的判断为非运行状态,尝试新建线程,如果失败则执行拒绝策略。
}
计算机网络
1.基于TCP的应用层协议有:SMTP、TELNET、HTTP、FTP
基于UDP的应用层协议:DNS、TFTP(简单文件传输协议)、
RIP(路由选择协议)、DHCP、BOOTP(是DHCP的前身)、IGMP(Internet组管理协议)
2.53号端口为DNS,22号为ssh
HTML
1.CSS中的伪类:
:link 用这个可以设置未被访问的链接的样式
:visited 用这个设置已经被访问的链接的样式
:hover 用于设置将鼠标悬浮在链接上的样式
:active 用于设置鼠标点击链接时到鼠标松开时的样式
:focus 用于设置用键盘将焦点放在链接上时的样式(如用tab键或者上下键来移动页面焦点时)
2.Media (媒体查询器)的用法
width = device-width:宽度等于当前设备的宽度
initial-scale:初始的缩放比例(默认设置为1.0)
minimum-scale:允许用户缩放到的最小比例(默认设置为1.0)
maximum-scale:允许用户缩放到的最大比例(默认设置为1.0)
user-scalable:用户是否可以手动缩放(默认设置为no,因为我们不希望用户放大缩小页面)
1.用户文档测试应考虑用户文档的完整性、正确性、一致性、易理解 性和易浏览性
2.计算机软件由程序和文档组成
3.集成测试在单元测试之后
4.逻辑覆盖方法
操作系统
一、内核态和用户态
用户态:当一个进程在执行自己的用户空间代码块时,处于用户态
内核态:当一个进程因为某些原因陷入内核空间,执行内核代码块时,处于内核态. 此时处理器处于特权级最高的(0级)内核代码中执行 ,执行的内核代码会使用当前进程的内核栈 即此时处理器在特权级最低的(3级)用户代码中运行。
二、操作系统的状态:就绪(差cpu)、运行、阻塞。
就绪是等待处理机,阻塞可能是等待输入输出。
三、进程间的通信方式有什么?
-
进程同步:控制多个进程按一定顺序执行;
-
进程通信:进程间传输信息。
1.管道:
半双工,即不能同时在两个方向上传输数据。有的系统可能 支持全双工。 只能在父子进程间
-
FIFO:命名管道
去除了管道只能在父子进程中使用的限制。
3.消息队列
消息队列可以独立于读写进程存在,从而避免了 FIFO 中 同步管道的打开和关闭时可能产生的困难
避免FIFO同步阻塞问题,不需要进程提供同步方法
可以有选择性接受消息。
4.信号量
它是一个计数器,用于为多个进程提供对共享数据对象的访问 提供了一个不同进程或者进程的不同线程之间访问同步的手段。
5.共享存储
允许多个进程共享一个给定的存储区 需要使用信号量用来同步对共享存储的访问。
6.套接字
与其它通信机制不同的是,它可用于不同机器间的进程通信。
-
五:进程同步的几种方式
临界区: 对临界资源进行访问的那段代码称为临界区。
同步与互斥: 多个进程按一定顺序执行; 多个进程在同一时刻只有一个进程能进入临界区。
信号量: 如果信号量的取值只能为 0 或者 1,那么就成为了 互斥量(Mutex) ,0 表示临界区已经加锁,1 表示临界区解锁。
事件:用来通知线程有一些事件发生,推动后续任务。
线程进程
对操作系统来说,线程是最小的执行单元,进程是最小的资源管理单元。
四:**线程的状态:
new(新建)
runnable(就绪状态)
running(运行状态)
blocked(阻塞状态)
waiting(等待阻塞)
Dead(死亡状态)
改变状态是通过操作系统内核的TCB(Thread Control Block)模块
进程的状态:
创建态、活动就绪、执行态、终止态、静止就绪、静止阻塞、活动阻塞。
协程
比线程更轻量级的存在。
协程由程序控制。
协程的暂停完全由程序控制,线程的阻塞状态是由操作系统内核来进行切换
计算机网络
层
五、物理层(网卡、中继器)、数据链路层(交换机、网桥)、网络层(路由器)、传输层(TCP和UDP)、会话层、表示层、应用层(http)
TCP\IP 链路层、网络层、传输层、应用层
链路层:以太网帧的格式
MTU的概念
ARP\RARP协议
ARP 为 IP 地址到对应的硬件地址提供动态映射。
ARP的报文格式
ARP查询原理
ARP缓存
网络层:IP首部格式
IP分片
IP选路
ICMP协议
报文格式
报文分类
传输层:UDP协议(特点+各个字段)
TCP协议(特点、首部、)
TCP连接控制(三、四、同时打开、同时关闭、半关闭)
TCP流量控制机制(滑动窗口、慢启动、拥塞避免、快速重 传、快速恢复)
TCP超时重传机制:定时器
TCP\UDP为包头
应用层:DNS协议
DNS名字空间、指针查询基本原理、DNS缓存
FTP协议(两条连接、两种工作模式、各种响应码)
FTP断点续传、匿名FTP
HTTP协议
报文格式:请求报文、响应报文、请求头各种字 段、 响应头各种字段、http状态码
HTTPS协议:详细握手过程
从输入一个url到页面都经历了什么
①缓存解析:先去缓存看看有没有: 浏览器缓存-系统缓存-路由器缓存
②DNS解析: 网址到IP地址的转换,这个过程就是DNS解析
③TCP连接
④发送HTTP请求: 浏览器开始向服务器发送http请求,请求数据包。请求信息包含一个头部和一个请求体
⑤服务器处理请求并返回HTTP报文: 服务器收到浏览器发送的请求信息,返回一个响应头和一个响应体。
⑥浏览器解析渲染页面: 浏览器收到服务器发送的响应头和响应体,进行客户端渲染,生成Dom树、解析css样式、js交互。
连接结束
服务器的501、502、503、504
500:服务器内部错误。 你的用户权限的问题导致,或者是数据库连接出现了错误
501: 服务器还是不具有请求功能的
502: WEB服务器故障, 可能是由于程序进程不够 , 请求的php-fpm已经执行 ,但没有执行完成
可能原因:
1、Nginx服务器,php-cgi进程数不够用;
2、PHP执行时间过长;
3、php-cgi进程死掉;
503: 服务器目前无法使用,系统维护服务器暂时的无法处理客户端的请求,这只是暂时状态
504: 错误表示超时,是指客户端所发出的请求没有到达网关,
505: 服务器不支持请求中所用的HTTP协议版本,
http和https的区别
- HTTPS协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
- HTTP是超文本传输协议,信息是明文传输,HTTPS则是具有安全性的SSL加密传输协议。
- HTTP的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输,身份认证的网络协议,比HTTP协议安全。
什么是Nginx?
是一个http服务器 及反向代理服务器
location?
作用:基于Nginx服务器接收到的请求字符串,虚拟主机名称(ip,域名)、url匹配,对特定请求进行处理
负载均衡
TCP( Transmission Control Protocol,传输控制协议 )
TCP是一种面向连接(连接导向)的、可靠的基于字节流的传输层通信协议。TCP将用户数据打包成报文段,它发送后启动一个定时器,另一端收到的数据进行确认、对失序的数据重新排序、丢弃重复数据。
特点
- TCP是面向连接的运输层协议
- 每一条TCP连接只能有两个端点,每一条TCP连接只能是点对点的
- TCP提供可靠交付的服务
- TCP提供全双工通信。数据在两个方向上独立的进行传输。因此,连接的每一端必须保持每个方向上的传输数据序号。
- 面向字节流。面向字节流的含义:虽然应用程序和TCP交互是一次一个数据块,但TCP把应用程序交下来的数据仅仅是一连串的无结构的字节流。
tcp三次握手
seq是报文序列号, SYN=1是同步,ACK是确认位=1是有效的,ack是确认位字段的值。
为什么是三次呢?
客户端发送请求报文,但是网络延迟滞留了,有个定时器发现没有接收到确认报文,故重新发,这次接收到了。但如果这个滞留的又恢复了,这样第三次的意义就是告诉服务器我不需要了。
tcp四次挥手
FIN=1表示要关闭 ,seq序列号
如果有数据还要传送,则需要再来一次。
最后加2秒是为了防止服务器没有发送成功。
TCP和UDP
UDP:面向非连接。随时可以发。不可靠。报文模式
TCP:连接。可靠。(顺序是一致的。)流量控制和拥塞控制。流模式。
TCP 可靠性保证
1.确认应答机制。(三次握手)
2.检验和。
3.序列号。每个字节的数据进行编号,可以去重。
4.超时重传。
5.连接管理机制。
6.流量控制和拥塞控制。
TCP 流量控制机制和拥塞控制机制
流量控制:
一种固定大小的窗口,一种滑动窗口。 流量控制根本目的是防止分组丢失,它是构成TCP可靠性的一方面。
发送方在发送过程中始终保持着一个发送窗口,只有落在发送窗口内的帧才允许被发送;同时接收方也维持着一个接收窗口,只有落在接收窗口内的帧才允许接收。这样通过调整发送方窗口和接收方窗口的大小可以实现流量控制。 每个TCP/IP主机支持全双工数据传输,因此TCP有两个滑动窗口:一个用于接收数据,另一个用于发送数据。
流量控制引发的死锁?怎么避免死锁的发生?
当发送者收到了一个窗口为0的应答,发送者便停止发送,等待接收者的下一个应答。但是如果这个窗口不为0的应答在传输过程丢失,发送者一直等待下去,而接收者以为发送者已经收到该应答,等待接收新数据,这样双方就相互等待,从而产生死锁。
为了避免流量控制引发的死锁,TCP使用了持续计时器。每当发送者收到一个零窗口的应答后就启动该计时器。时间一到便主动发送报文询问接收者的窗口大小。若接收者仍然返回零窗口,则重置该计时器继续等待;若窗口不为0,则表示应答报文丢失了,此时重置发送窗口后开始发送,这样就避免了死锁的产生。
拥塞控制: 拥塞控制是作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况
慢开始和拥塞避免
发送方维持一个拥塞窗口 cwnd ( congestion window )的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞。
**慢开始:**先探测一下,即由小到大逐渐增大发送窗口。每经过一个传输轮次,拥塞窗口 cwnd 就加倍。慢开始的“慢”并不是指cwnd的增长速率慢,而是指在TCP开始发送报文段时先设置cwnd=1,使得发送方在开始时只发送一个报文段(目的是试探一下网络的拥塞情况),然后再逐渐增大cwnd。慢开始算法只是在TCP连接建立时和网络出现超时时才使用。
**拥塞避免算法:**让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。这样拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。
快重传和快恢复
快重传算法:首先要求接收方每收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时才进行捎带确认。 然后接收到三个确认以后就重传没成功的报文。
快恢复算法: 当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把ssthresh门限减半(为了预防网络发生拥塞)。但是接下去并不执行慢开始算法
HTTP(HyperText Transfer Protocol,超文本传输协议)
应用层协议。
特点
- 支持客户/服务器模式。
- 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
- 灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
- 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
- 无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。为了解决这个问题, Web程序引入了Cookie机制来维护状态。
工作原理
HTTP协议定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据。
状态码
服务器的回应被定义在几个状态码之间:5开头表示服务器错误,4开头表示客户端错误,3开头表示需要做进一步处理,2开头表示成功,1开头表示在请求被接受处理的同时提供的额外信息。
get和post的区别
【1】GET请求可被缓存,POST请求不能被缓存。
【2】GET请求被保留着浏览器历史记录中,POST请求不会被保留。
【3】GET请求能被收藏至书签中,POST请求不能被收藏至书签。
【4】GET请求不应在处理敏感数据时使用,POST可以用户处理敏感数据。
【5】 GET请求有长度限制,POST请求没有长度限制。
【6】POST不限制提交的数据类型,所以POST可以提交文件到服务器。
Cookies和Session
Cookies是一小段文本信息,客户端请求服务器后,给客户端颁发一个cookies然后保存下来。保存在客户端。
session也是记录客户状态的机制,保存在服务器上。
区别:
1.存储位置不同。
- session更安全。
- 单个cookies不能超过4k,一个网站最多20个。
- session会占用大量服务器性能。
缓存
强制缓存: 向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程,强制缓存的情况主要有三种(暂不分析协商缓存过程)
协商缓存:
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:
协商缓存生效,返回304,如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TRxxwW2L-1586440460233)(C:\Users\56495\AppData\Roaming\Typora\typora-user-images\1584412497426.png)]
Mybatis
概念
三层架构:
表现层:展示数据的
业务层:处理业务的
持久层:和数据库进行交互
持久层技术解决方案
JDBC技术:
Connection
PreparedStatement
ResultSet
Spring中的jdbctemplate
对jdbc的简单封装
Apache中的DBUtils:
简单封装
以上不是框架。上述两个都是工具类。
持久层框架,通过xml或者注解方式配置,屏蔽jdbc api底层,使用ORM思想(对象关系映射),结果集封装。
环境搭建
配置的文件,主配置文件
映射文件的路径和dao包结构相同。
实现了三四五,就不用写dao的实现类。
测试过程
1.读取配置文件。
2.创建sqlSessionFactory工厂
3.使用工厂生产SqlSession对象
4.使用SqlSession创建Dao接口的代理对象。
5.使用代理对象执行方法。
6.释放资源。
1、使用类加载器,只能读取类路径的配置文件。
使用ServletContext对象的getRealPath();
2、创建工厂Mybatis使用了构建者模式(把对象的创建细节隐藏)
3、生产SqlSession使用了工厂模式(降低类之间的依赖关系)
4、getMapper:创建Dao接口实现类使用了代理模式。(不修改源码,对原有方法进行增强)
自定义Mybatis的分析(mybatis在使用代理Dao的方式实现增删改查时做什么事呢?)
第一:创建代理对象
第二:代理对象中调用selectList
执行查询的分析
创建代理对象的分析
Spring
解耦思路:
1.用反射创建对象,而不是使用new关键字
Class.forname()
2.通过读取配置文件来获取要创建的对象全限定类名
web应用入口在tomcat。。初始化web.xml
解耦思路:
1.用反射创建对象,而不是使用new关键字
Class.forname()
2.通过读取配置文件来获取要创建的对象全限定类名
事务隔离级别
1) DEFAULT (默认)
这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应。
2) READ_UNCOMMITTED (读未提交)
这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
3) READ_COMMITTED (读已提交)
保证一个事务修改的数据提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。
4) REPEATABLE_READ (可重复读)
这种事务隔离级别可以防止脏读、不可重复读,但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了不可重复读。
5) SERIALIZABLE(串行化)
这是花费最高代价但是最可靠的事务隔离级别,事务被处理为顺序执行。除了防止脏读、不可重复读外,还避免了幻像读。
事务传播机制
1) REQUIRED(默认属性)
如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
被设置成这个级别时,会为每一个被调用的方法创建一个逻辑事务域。如果前面的方法已经创建了事务,那么后面的方法支持当前的事务,如果当前没有事务会重新建立事务。
2) MANDATORY
支持当前事务,如果当前没有事务,就抛出异常。
3) NEVER
以非事务方式执行,如果当前存在事务,则抛出异常。
4) NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
5) REQUIRES_NEW
新建事务,如果当前存在事务,把当前事务挂起。
6) SUPPORTS
支持当前事务,如果当前没有事务,就以非事务方式执行。
7) NESTED
支持当前事务,新增Savepoint点,与当前事务同步提交或回滚。
嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。
PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:
它们非常类似,都像一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务。
使用PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它需要JTA 事务管理器的支持。
使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。
IOC
applicationcontext:
他在构建核心容器时,创建对象采取的是立即加载的方式。
单例对象适用。
beanfactory:
他在构建核心容器时,创建对象采取的是延迟加载的方式,什么时候获取对象了,什么时候创建对象。
多例对象适用。
bean
bean的加载过程
javabean 和springbean区别
java对象:使用new或其他方式直接创建,由JVM统一管理,一个java对象就只有生成这个对象的类中申明的所有属性和性能。
springbean:通过反射创建,由spring容器统一管理,拥有自己类中的申明的方法和属性还有spring给其增加、修改的属性。
java对象加载流程:
spring流程:
Class类型的对象—BeanDefinition-----beanDefinitionmap(“@Component() springbean对象”)----BeanFactoryPostProcessor----检查检验(类名是否合法,是否单例)-----beanpostprocessor—singletonObjects
创建bean的三种方式
1.使用bean标签,配以id和class,切没有其他属性和标签,
采用的是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
2.使用普通工厂中的方法创造对象。(使用某个类的方法创建对象,并存入spring容器)
3.使用静态工厂中的静态方法创造对象,(使用某个类中静态方法创建对象)
bean的作用范围
bean标签的scope属性:
作用:指定bean的作用范围。
取值:
singleton:单例的(默认值)
prototype:多例的
request:作用于web应用的请求范围
session:作用于web应用的会话范围
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
bean对象的生命周期
单例对象
出生:当容器创建时对象出生
活着:只要容器还在,对象一直活着
死亡:容器销毁,对象消亡
总结:单例对象的生命周期和容器相同
多例对象
出生:当我们使用对象时spring框架为我们创建
活着:对象只要是在使用过程中就一直活着。
死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
beanfactory和applicationContext的区别
applicationContext:(单例模式适用)
他在构建核心容器时,创建对象采取的策略是采取立即加载的方式。
beanfactory:(多例对象适用)
构建核心容器是,创建对象采取的策略是延迟加载的方式也就是说什么时候根据id获取对象了,什么时候创建对象。
beanfactory和factorybean
factorybean在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰者模式
BeanFactory是个Factory,也就是IOC容器或对象工厂,FactoryBean是个Bean
所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似
不是用反射创建, getobject来获取对象,然后放入spring容器中。
BeanPostProcessor和BeanFactoryPostProcessor的区别
初始化方法之前和之后执行,两个方法。增强bean。
增强工厂,改变功能。
创建对象之前,可以将类修改。
DI
spring中的依赖注入
依赖注入: Dependency Injection
IOC的作用: 降低程序间的耦合(依赖关系)
依赖关系的管理: 以后都交给spring来维护 在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明 依赖关系的维护: 就称之为依赖注入。
依赖注入:
能注入的数据:有三类
基本类型和String
其他bean类型(在配置文件中或者注解配置过的bean) 复杂类型/集合类型
注入的方式:有三种
第一种:使用构造函数提供
第二种:使用set方法提供
第三种:使用注解提供(明天的内容)
使用的标签:constructor-arg
标签出现的位置:bean标签的内部标签中的属性
type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始
name:用于指定给构造函数中指定名称的参数赋值 常用的 =============以上三个用于指定给构造函数中哪个参数赋值===============================
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
优势: 在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
弊端: 改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。
set方法注入
更常用的方式
涉及的标签:property
出现的位置:bean标签的内部
标签的属性
name:用于指定注入时所调用的set方法名称
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
优势: 创建对象时没有明确的限制,可以直接使用默认构造函数
弊端: 如果有某个成员必须有值,则获取对象是有可能set方法没有执行。
注解
用于创建对象的
用于注入数据的
用于改变作用范围的、生命周期
配置xml
配置类
动态代理
AOP
面向切面编程,是OOP的延伸
使用动态代理
通过配置的方式实现
连接点:业务层中所有方法都是连接点。
切入点:业务层中支持事务的方法是切入点。
spring基于xml配置AOP的步骤
切入点表达式写法
四种常用的通知类型
环绕通知
SpringMVC
servlet的生命周期
init()、service()、destroy()
1、浏览器像servlet发送请求
2、tomcat收到请求后,创建Request和Response两个对象的生命周期,并且将浏览器请求的参数传递给Servlet
3、Servlet接收到请求后,调用doget或者dopost方法。处理浏览器的请求信息,然后通过Response返回信息
4、tomcat接收到返回的信息,返回给浏览器。
5、浏览器接收到返回消息后,tomcat销毁Request和Response两个对象,同时销毁这两个对象所获得的信息。
Mysql
事务的特性(ACID)
原子性:要同步成功或失败。
一致性:事务从一致状态到另一个一致状态,执行前和执行后都必须处于一致状态。
隔离性:事物之间是隔离的。
持久性:事务提交以后是持久的。
事务的隔离等级
脏读: 在处理数据的过程中,读取到另一个未提交事务的数据。
不可重复读: 不可重复读读取到的是前一个事务提交的数据 。(内容不一样)
幻读:和不可重复读类似(数量不一样)
解决不可重复读的方法是 锁行,解决幻读的方式是 锁表。
读未提交:会脏读。
读已提交:避免脏读。
可重复读:避免脏读、不可重复读。
串行化:避免脏读、不可重复读、幻读。
MVCC
Multi-Version Concurrency Control,多版本并发控制。
MVCC的实现,通过保存数据在某个时间点的快照来实现的。这意味着一个事务无论运行多长时间,在同一个事务里能够看到数据一致的视图。根据事务开始的时间不同,同时也意味着在同一个时刻不同事务看到的相同表里的数据可能是不同的。
实现的是读-写,写-读的并发进行。
就是操作时不能修改新增时间之后的操作,查找时过期时间只要在事务id之后就可以查找到。
行锁和表锁
行锁:共享锁。 lock in share mode,无法修改(读锁)
排它锁:其他人无法操作。
意向锁
表锁、无法手动创建。
间隙锁和临键锁
插入是1、5、9、11 区间为(-,1】(1,5】(5,9】(9,11】(11,+)
就是锁一个区间,然后不能修改啦。
自增锁
级别0、1、2 (binlog) 严格递减。
乐观锁、悲观锁
乐观锁:数据库进行操作时,认为这次操作不会有冲突,操作时不加锁,更新以后再去判断是否冲突。
悲观锁:操作数据时认为会冲突,每次操作都要加锁。
保持高并发一致性:添加版本号,A操作完 version从1到2,B操作完发现version=2,则操作回滚。
实现乐观锁:
1.使用数据版本(Version)记录机制实现, 当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
- 同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp ) 也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。
乐观锁适用的场景
像乐观锁适用于写比较少的情况下(多读场景) 。
乐观锁实现
package priv.nanjing.testCasClass;
import java.util.concurrent.atomic.AtomicInteger;
/*
* @Author : darrenqiao
* */
//多线程争用的数据类
class Counter {
//int count = 0;
//使用AtomicInteger代替基本数据类型
AtomicInteger count = new AtomicInteger(0);
public int getCount() {
//return count;
return count.get();
}
public void add() {
//count += 1;
count.addAndGet(1);
}
public void dec() {
//count -= 1;
count.decrementAndGet();
}
}
//争用数据做加操作的线程
class AddDataThread extends Thread {
Counter counter;
public AddDataThread(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
for (int i = 0; i < CasClass.LOOP; ++i) {
counter.add();
}
}
}
//争用数据做减法操作的线程
class DecDataThread extends Thread {
Counter counter;
public DecDataThread(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
for (int j = 0; j < CasClass.LOOP; j++) {
counter.dec();
}
}
}
public class CasClass {
final static int LOOP = 10000;
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread addThread = new AddDataThread(counter);
Thread decThread = new DecDataThread(counter);
addThread.start();
decThread.start();
addThread.join();
decThread.join();
System.out.println(counter.getCount());
}
}
索引
索引是数据结构,空间换时间。索引是在磁盘中。
联合索引
满足最左前缀原则,比如建立id,name索引,其实建立了id索引和id,name索引。如果查找的少了一列,则只使用前面列的索引。
覆盖索引+explain
通过索引项的信息直接返回所查询的列,该索引就叫查询sql的覆盖索引,支持索引下推的过程。可以防止回表操作。
这个可以在explain中看它执行计划的时候,extra字段里面有using index condition可以看到。
id:执行顺序,sql从大到小的执行。如果是 explain select * from (select * from ( select * from t3 where id=3952602) a) b; 则id=3;
select_type:就是select的类型。
SIMPLE:简单select查询;
PRIMARY:最外层的select查询。就是如果有嵌套的话 最外层的执行就是primary;
.UNION: UNION中的第二个或后面的SELECT语句 ;
**.UNION RESULT** :UNION的结果;
**.SUBQUERY **:子查询的第一个select;
**.DERIVED **:派生表的select。
table:是哪张表的数据;
type:显示连接使用了哪种类别,有没有使用索引。
possible_keys: 指出MySQL能使用哪个索引在该表中找到行 。
key: 显示MySQL实际决定使用的键(索引) 。
key_len: 显示MySQL实际决定使用的键(索引) 的长度。
ref:使用哪个列或者常数和key一起从表中选择行。
row:执行查询时必须用到多少行。
extra:包含mysql解决查询的详细信息:
(1).Distinct
一旦MYSQL找到了与行相联合匹配的行,就不再搜索了
(2).Not exists
MYSQL优化了LEFT JOIN,一旦它找到了匹配LEFT JOIN标准的行,
就不再搜索了
(3).Range checked for each
Record(index map:#)
没有找到理想的索引,因此对于从前面表中来的每一个行组合,MYSQL检查使用哪个索引,并用它来从表 中返回行。这是使用索引的最慢的连接之一
(4).Using filesort
看到这个的时候,查询就需要优化了。MYSQL需要进行额外的步骤来发现如何对返回的行排序。它根据连 接类型以及存储排序键值和匹配条件的全部行的行指针来排序全部行
(5).Using index
列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是 同一个索引的部分的时候
(6).Using temporary
看到这个的时候,查询需要优化了。这里,MYSQL需要创建一个临时表来存储结果,这通常发生在对不同 的列集进行ORDER BY上,而不是GROUP BY上
(7).Using where
使用了WHERE从句来限制哪些行将与下一张表匹配或者是返回给用户。如果不想返回表中的全部行,并且 连接类型ALL或index,这就会发生,或者是查询有问题
优化
索引还在
联合索引;
覆盖索引;也可以避免排序用到的一些临时文件;利用最左原则匹配和覆盖,减少一些索引的维护
硬盘怕随即读写,有查找的开销;我们可以把MRR打开,就是multi-range-read,他可以在回表之前把我们的id读到buffer里面,进行一个排序,把原来随机操作变成一个顺序操作。
如果是写多读少的服务,并且这个服务的唯一性要求没有那么高,或者我们的业务代码可以保证唯一性的时候,可以用普通索引,因为普通索引可以用到change buffer的,它可以把一些写操作给缓存下来,在我们读的时候进行merge操作,可以提高写入的速度和内存的命中率。
索引走不上
1.可能sql写的问题。对索引字段进行了一些函数操作,连接查询时候两个表的编码不一样,或者两个字段的类型不一样;
2.索引统计信息是不是有问题?可以analyze table重新统计所有的信息。因为索引信息不是准确值,是一个随机采样的一个过程,所以可能出现问题。
3.因为业务表分太多,内存的空洞也比较多。
explain分析的索引问题
因为它可能会选错索引,可能会涉及到回表操作,还有一些排序操作,可能会报错。
如果索引建的不好
1.采用force index进行强制索引。不太好的是迁移到别的数据库就不支持了。而且还需要代码的重新发布;
2.考虑用覆盖索引和最左原则
B+树
为什么使用B+树
1.B+树只有叶子节点上有data,可以减少索引占用的内存,同时索引会存储在磁盘上。B树都有data 磁盘IO次数就多啦。
2.B+树所有叶子结点有指针穿起来,可以遍历起来方便。
3.不用红黑树是因为红黑树深度比较大,
myisam和innodb区别
1.innodb支持事务,myisam不支持。
2.InnoDB支持外键,而MyISAM不支持。
3.InnoDB是聚集索引,使用B+Tree作为索引结构,数据文件是和(主键)索引绑在一起的(表数据文件本身就是按B+Tree组织的一个索引结构),必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。
MyISAM是非聚集索引,也是使用B+Tree作为索引结构,索引和数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
4.InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数
5.InnoDB表必须有主键
如何选择
-
是否要支持事务,如果要请选择innodb,如果不需要可以考虑MyISAM;
- 如果表中绝大多数都只是读查询,可以考虑MyISAM,如果既有读也有写,请使用InnoDB。
-
系统奔溃后,MyISAM恢复起来更困难,能否接受;
4. MySQL5.5版本开始Innodb已经成为Mysql的默认引擎(之前是MyISAM), -
说明其优势是有目共睹的,如果你不知道用什么,那就用InnoDB,至少不会差。
什么情况下设置了索引但无法使用
① 以“%”开头的LIKE语句,模糊匹配
② OR语句前后没有同时使用索引
③ 数据类型出现隐式转化(如varchar不加单引号的话可能会自动转换为int型)
主键索引和非主键索引
主键索引叶子结点存储一行值,非主键索引叶子结点存储主键的值。
大量热点数据更新问题
可以把它写进一个内存的临时表中,innodb会维护一个bufferpool的,如果把大量数据全部读进去,会造成flush的操作(把脏页刷回mysql,造成阻塞)。
如果redis带宽打满了,可以使用本地缓存。有三种session、localStorage、sessionStorage。
Cookie:一般用来存储用户信息,每次请求的时候内容都会自动被传递给服务器。
LocalStorage:localstorage会把内容一直存在浏览器,直到清除浏览器的缓存 。
sessionStorage:跟localStorage一样,只不过sessionStorage的生命周期跟同源窗口有关,就是说当 前同一个源下面的只要有一个窗口没关或者跳到另外的窗口,sessionStorage都会存在。
Redis
键值对、缓存数据库、基于内存、单线程
特性
使用场景
String
setnx (not exist)不存在才可以设置
应用场景
Hash
应用场景
先找到key应该怎么设计?1.
优缺点
List
阻塞队列Blocking-queue-brpop(平时和rpop一样,但是没有数据时会一直等待)
List :lpush(rpush) key xxx
栈:lpush和lpop 队列lpush和rpop
重要:如何处理一对多关系的设计。
Set
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis 中集合是通过hashtable实现的,所以添加,删除,查找的复杂度都是 O(1)。
Zset
有序集合
为每个元素增加一个double类型的分数。
应用场景
排行榜、
底层实现
跳跃表
为什么不使用红黑树:因为是单线程,跳跃表更简洁。
其中每一个数据的层数是随机的。
用score来寻找数据,
持久化
持久化功能有效地避免因进程退出造成的数据丢失问题,当下次重启时,利用之前持久化的文件即可实现数据恢复。
RDB和AOF
RDB
把当前进程数据生成快照保存到硬盘的过程,手动触发和自动触发。
- Redis父进程首先判断:当前是否在执行save,或bgsave/bgrewriteaof(后面会详细介绍该命令)的子进程,如果在执行则bgsave命令直接返回。bgsave/bgrewriteaof 的子进程不能同时执行,主要是基于性能方面的考虑:两个并发的子进程同时执行大量的磁盘写操作,可能引起严重的性能问题。
- 父进程执行fork操作创建子进程,这个过程中父进程是阻塞的,Redis不能执行来自客户端的任何命令
- 父进程fork后,bgsave命令返回”Background saving started”信息并不再阻塞父进程,并可以响应其他命令
- 子进程创建RDB文件,根据父进程内存快照生成临时快照文件,完成后对原有文件进行原子替换
- 子进程发送信号给父进程表示完成,父进程更新统计信息
保存:RDB文件保存在dir配置指定的目录下,文件名通过dbfilename配置
指定。可以通过执行conf set dir{newDir}和config set dbfilename {newFileName}运行期动态执行,下次运行时RDB文件会保存在新目录
压缩:Redis默认采用LZF算法对生成的RDB文件做压缩处理,压缩后的文件远远小于内存大小,默认开启,可以通过参数config set rdbcompression {yes|no}动态修改
校验:如果Redis加载损坏的RDB文件时拒绝启动
优缺点
优点:
RDB恢复速度远大于AOF。
他代表了Redis在某个时间点的数据快照,适合用于备份和全量复制。
缺点;
不能实时持久化,因为我得fork父进程,重量级操作,需要时间。
RDB文件是特定的二进制文件格式,存在兼容问题。
AOF*(主流)
独立日志形式记录每次写命令,重启后再执行AOF文件中的命令,作用是解决实时性。
开启AOF功能需要设置配置:appendonly yes,默认不开启。AOF文件名通过appendfilename配置设置,默认文件名是appendonly.aof。
1.所有的写入命令追加到aof_buf(缓存区)中
2.AOF缓存区根据对应的策略向磁盘做同步操作
3.随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的
4.当Redis服务器重启时,可以加载AOF文件进行数据恢复。
命令写入是文本协议格式。
- 文本协议格式具有很好的兼容性
- 开启AOF后,所有写入命令都包含追加操作,直接采用协议格式,避免了二次处理开销
- 文本协议具有可读性,方便直接修改和处理
为什么要追加到aof_buf中?
- Redis是单线程响应命令,如果每次写AOF文件命令都直接追加到磁盘,那么性能完全取决于当前硬盘负载。
- 先写入缓冲区aof_buf中,还有一个好处,Redis可以提供多种缓冲区同步硬盘策略,在性能和安全性上做出平衡
AOF文件同步
no:快,持久化没有保障。
always:慢,安全。 fsync针对单个文件操作,做强制硬盘操作,fsync将阻塞直到写入磁盘完成后返回,保证了数据持久化
eyerysec:可能丢失一秒内的数据。 write操作会触发延迟写机制。 当缓冲区被填满或超过了指定时限后,才真正将缓冲区的数据写入到硬盘里
重写机制(手动和自动)
首先从数据库中读取键现在的值,然后用一条命令去记录键值对,代替之前记录该键值对的多个命令;
AOF文件重写是把Redis进程内的数据转化为写命令同步到新的AOF文件的过程 。
重写可以降低文件占用空间,可以更快地被Redis加载。
-
执行AOF重写请求
如果当前进程正在执行AOF重写,请求不执行
如果当前进程正在执行bgsave操作,重写命令延迟到bgsave完成后再执行 -
父进程执行fork创建子进程,开销等同于bgsave过程
-
父进程fork操作完成后,继续响应其他命令。所有修改命令依然写到AOF缓冲区并根据appendfsync策略同步到磁盘上,保证原有AOF机制正确性.
由于fork操作运用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然响应命令,Redis使用"AOF重写缓冲区"保存这部分新数据,防止新AOF文件生成期间丢失这部分数据
-
子进程根据内存快照,按照命令合并规则写入到新的AOF文件。
-
新AOF文件写入完成后,子进程发送信号给父进程,父进程更新统计信息
父进程把AOF重写缓冲区中的数据写入到新的AOF文件
-
分布式锁的实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C2xlTYrz-1586440460305)(![C:\Users\56495\AppData\Roaming\Typora\typora-user-images](https://img-blog.csdnimg.cn/20200410094531465.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0MzU0NTg0,size_16,color_FFFFFF,t_70)
这样写,第一个锁释放的可能是第二个锁。释放了不属于自己的锁。
哨兵模式
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
用文字描述一下故障切换(failover)的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。
缓存
缓存穿透
是指查询一个数据库一定不存在的数据。正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存。
缓存雪崩
缓存雪崩,是指在某一个时间段,缓存集中过期失效。 而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。
比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网 。
缓存击穿
缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。 类似爆款,让其永不过期就好。
JVM
谈谈对jvm的理解
JVM就是java虚拟机,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。
1.类加载器
启动类加载器、扩展类加载器、应用类加载器、自定义类加载器
2.在类加载检查通过后,接下来虚拟机将为新生对象分配内存。
java8虚拟机和之前的变化
什么是OOM?
什么是栈溢出StackOverFlowError?怎么分析
JVM的常用调优参数有哪些?
内存快照如何抓取,怎么分析Dump文件?
谈谈JVM中,类加载器你的认识?
类加载器和类加载机制
JVM的位置
JVM的体系结构
类加载器
类加载器作用
加载Class文件~
1.虚拟机自带的加载器
2.启动类(根)加载器
3.扩展类加载器
4.应用程序加载器
双亲委派机制
APP–>EXC–BOOT(最终执行)
1.类加载器收到类加载的请求
2.将这个请求向上委托给父类加载器去完成,一直向上委托,知道启动类加载器
3.启动类加载器检查是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则跑出异常,通知子类进行加载。
4.重复步骤3
Class Not Found
null:java调用不到~ C\C++
打破双亲委派机制
- 自定义类加载器,重写loadClass方法;
- 使用线程上下文类加载器;
沙箱安全机制
包括:
字节码校验器:确保遵守java语言规范
类装载器:对java沙箱起作用
它防止恶意代码干涉善意代码;双亲委派记至
它守护了被信任的类库边界
它将代码归入保护域,确定了代码可以进行哪些操作
存取控制器:控制核心API对操作系统的存取权限
安全管理器:核心API和操作系统之间的主要接口。比存取控制器优先级更高。
安全软件包:java.security下的类和扩展包下的类。允许用户新增
Native
private native void start0()
带了native,java作用范围达不到了,回去调用底层c语言的
调用本地方法接口JNI
JNI作用:扩展java的使用,融合不同的编程语言为java所用
他在内存区域中,专门开辟了一块表及区域:本地方法栈
执行中,加载本地方法库中的方法通过JNI
调用其他接口:Socker,WebService http
PC寄存器
方法区
static final Class 常量池在方法区
栈
数据结构
先进后出、后进先出:桶
队列:先进先出(FIFO:First Input First Output)
栈:栈内存,主管程序的运行,生命周期和线程同步;
线程结束,栈内存也就是释放,对于栈来说,不存在垃圾回收问题
一旦线程结束,栈就over
栈:8大基本类型+对象引用+实例的方法
栈运行原理:栈帧
栈满了:StackOverflowError
栈+堆+方法区:交互关系
三种JVM
堆
Heap,一个JVM只有一个堆内存,堆内存大小是可以调节的。
类加载器读取了类文件后,一般把什么东西放到堆中?类,方法,常量,变量,保存我们所有引用类型的真实对象
堆内存中还要细分为三个区域:
新生区(伊甸园区)、幸存者区
养老区 old
永久区 Perm
GC垃圾回收主要在伊甸园区和养老区~
假设内存满了,OOM,堆内存不够
在JDK8以后,永久存储区改了名字(元空间)
真理:有99%的对象都是临时对象!new
新生区
类:诞生和成长的地方,甚至死亡;
伊甸园区,所有的对象都是在伊甸园区new出来的
幸存者区:0、1
老年区
永久区
这个区域常驻内存的。用来存放JDK自身携带的Class对象,Interface元数据,存储的是java运行时的一些环境或类信息~,这个区域不存在垃圾回收!关闭虚拟机就会释放这个区域的内存
一个启动类,加载了大量的第三方jar包。Tomcat部署了大量应用,大量动态生成的反射类。不断地被加载。直到内存满,就会出现OOM;
jdk1.6之前:永久代,常量池是在方法区中;
jdk1.7:永久代,但是慢慢退化了,去永久代,常量池在堆中
jdk1.8之后:无永久代,
常量池在元空间。
元空间:逻辑上存在,物理上不存在。
堆内存调优
堆内存空间扩大
-Xms1024m -Xmx1024m -XX:printGCDetails
在一个项目中,突然出现OOM故障,如何排除
能够看到代码第几行出错:内存快照分析工具,MAT,Jprofiler
Debug,一行行分析代码!
MAT,Jprofiler作用:
分析Dump内存文件,快速定位内存泄漏;
获取堆中的数据
获得大的对象~
GC垃圾回收
JVM在进行GC时,并不是对三个区域统一回收,大部分时候,回收都是新生代~
新生代
幸存区(from,to)
老年区
GC两种类型:轻GC,重GC(全局GC)
题目:
JVM的内存模型 分区~详细到每个区放什么?
堆里面的分区有哪些?Eden,from,to,老年区,特点!
常用算法
标记清除法、标记压缩、复制算法、引用计数器
轻GC和重GC分别在什么时候发生?
引用计数法
可达性分析
复制算法
谁空谁是to
一旦Eden被GC,就空了
经历15次GC还没死,就进养老区
好处:没有内存的碎片
坏处:浪费了内存空间,多了一半空间永远是空to,
复制算法最佳使用场景:
对象存活度较低,新生区~
标记压缩清除法(老年代)
优点:不需要额外的空间!
缺点:两次扫描严重浪费时间,会产生内存碎片。
标记压缩
再优化
JMM Java Memory Model
1.什么是JMM?
2.它干嘛的?
作用:缓存一致性协议,用于定义数据读写的规则(遵守,找到这个规则)。
JMM和线程工作内存和主内存之间的抽象关系,线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存。
解决共享对象可见性这个问题:voliate
JMM:抽象的概念,
voliate解决一致性
总结
内存效率:复制》标记清除》标记压缩(时间复杂度)
内存整齐度:复制算法=标记压缩》标记清除
内存利用率:标记压缩=标记清除》复制算法
GC:分代收集算法
年轻代:
存活率低
复制算法!
老年代:
区域大:存活率
标记清除(内存碎片不是太多 )+标记压缩混合 实现
JVM的垃圾收集器
串行垃圾回收器
而且它进行垃圾回收的时候,必须暂停其他所有的工作线程(Stop The World,STW),直到它收集完成。
有两种Serial与Serial Old,一般两者搭配使用。新生代采用Serial,是利用复制算法;老年代使用Serial Old采用标记-整理算法。
并行垃圾回收器
整体来说,并行垃圾回收相对于串行,是通过多线程运行垃圾收集的。也会stop-the-world。 和CMS搭配使用。
ParNew:Serial收集器的多线程版本,默认开启的收集线程数和cpu数量一样,运行数量可以通过修改ParallelGCThreads设定。用于新生代收集,复制算法 。
Parallel Scavenge: 关注吞吐量,吞吐量优先,吞吐量=代码运行时间/(代码运行时间+垃圾收集时间),也就是高效率利用cpu时间,尽快完成程序的运算任务可以设置最大停顿时间MaxGCPauseMillis以及,吞吐量大小GCTimeRatio。如果设置了-XX:+UseAdaptiveSizePolicy参数,则随着GC,会动态调整新生代的大小,Eden,Survivor比例等,以提供最合适的停顿时间或者最大的吞吐量。用于新生代收集,复制算法。
Parllel Old:Parallel Scavenge的老年代版本 。
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获得最短回收停顿时间为目标的收集器 。
初始标记:标记一下GC Roots能直接关联到的对象,会“Stop The World”。
并发标记:GC Roots Tracing,可以和用户线程并发执行。
重新标记:标记期间产生的对象存活的再次判断,修正对这些对象的标记,执行时间相对并发标记短,会“Stop The World”。
并发清除:清除对象,可以和用户线程并发执行。
.CMS只会回收老年代和永久代 , 年轻带只能配合Parallel New或Serial回收器 。
CMS是一种预处理垃圾回收器,它不能等到old内存用尽时回收,需要在内存用尽前,完成回收操作,否则会导致并发回收失败 ,阈值是老年代或永久代的92%
G1收集器
G1从整体看还是基于标记-清除算法的,但是局部上是基于复制算法的 。 因为G1中的垃圾收集区域是“分区”(Region)的。
其中初始标记阶段仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Set)的值,让下一个阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这个阶段需要STW,但耗时很短
并发标记阶段是从GC Roots开始对堆中对象进行可达性分析,找到存活的对象,这阶段耗时较长,但是可以和用户线程并发运行。
最终标记阶段则是为了修正在并发标记期间因用户程序继续运行而导致标记产生变化的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记需要把Remembered Set Logs的数据合并到Remembered Sets中,这阶段需要暂停线程,但是可并行执行。
筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来确定回收计划
G1会选择可能回收最多垃圾的Region进行回收。与此同时,G1回收器会维护一个空间Region的链表。每次回收之后的Region都会被加入到这个链表中。 在HotSpot的实现中,整个堆被划分成2048左右个Region
Spring boot
boot内嵌容器原理
tomcat、jetty