==
解读
对于基本类型和引用类型 ==
的作用效果是不同的,如下所示:
String x = "string";
String y = "string";
String z = new String("string");
System.out.println(x==y); // true
System.out.println(x==z); // false
System.out.println(x.equals(y)); // true
System.out.println(x.equals(z)); // true
代码解读:因为 x 和 y 指向的是同一个引用,所以 "= ="也是 true,而 new String()方法则重写开辟了内存空间,所以 "= = "结果为 false,而 equals 比较的一直是值,所以结果都为 true。
equals 解读
equals 本质上就是 ==,只不过 String 和 Integer等重写了 equals 方法,把它变成了值比较。看下面的代码就明白了。
首先来看默认情况下 equals 比较一个有相同值的对象,代码如下:
class Cat {
public Cat(String name) {
this.name = name;
}
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Cat c1 = new Cat("王磊");
Cat c2 = new Cat("王磊");
System.out.println(c1.equals(c2)); // false
输出结果出乎我们的意料,竟然是 false?这是怎么回事,看了 equals 源码就知道了,源码如下:
public boolean equals(Object obj) {
return (this == obj);
}
原来 equals 本质上就是 = =。
那问题来了,两个相同值的 String 对象,为什么返回的是 true?代码如下:
String s1 = new String("老王");String s2 = new String("老王");
System.out.println(s1.equals(s2)); // true
同样的,当我们进入 String 的 equals 方法,找到了答案,代码如下:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
原来是 String 重写了 Object 的 equals 方法,把引用比较改成了值比较。
总结 := = 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 本质上就是==,只不过很多类重写了equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。
不对,两个对象的 hashCode() 相同,equals() 不一定 true。
代码示例:
String str1 = "通话";String str2 = "重地";
System. out. println(String. format("str1:%d | str2:%d", str1. hashCode(),str2. hashCode()));
System. out. println(str1. equals(str2));
执行的结果:
str1:1179395 | str2:1179395
false
代码解读:很显然“通话”和“重地”的 hashCode() 相同,然而 equals() 则为 false,因为在散列表中,hashCode() 相等即两个键值对的哈希值相等,然而哈希值相等,并不一定能得出键值对相等。
哈希码并不是完全唯一的,它是一种算法,让同一个类的对象按照自己不同的特征尽量的有不同的哈希码,但不表示不同的对象哈希码完全不同。也有相同的情况,看程序员如何写哈希码的算法。
不能被继承,因为String类有final修饰符,而final修饰的类是不能被继承的。
等于-1,因为在数轴上取值时,中间值(0.5)向右取整,所以正 0.5 是往上取整,负0.5是直接舍弃。
基础类型有 8 种:byte、boolean、char、short、int、long、float、double,
String不属于基础类型,String属于对象/引用数据类型。
不一样,因为内存的分配方式不一样。String str="i"的方式,Java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。
使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。
示例代码:
// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer();
stringBuffer. append("abcdefg");
System. out. println(stringBuffer. reverse()); // gfedcba// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder();
stringBuilder. append("abcdefg");
System. out. println(stringBuilder. reverse()); // gfedcba
不需要,抽象类不一定非要有抽象方法。
示例代码:
abstract class Cat {
public static void sayHi() {
System. out. println("hi~");
}
}
上面代码,抽象类并没有抽象方法但完全可以正常运行。
抽象类可以没有抽象方法
不过如果一个类中有了抽象方法,那么这个类必须声明为抽象类,否则编译通不过。
不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾
按照流的流向分,可以分为输入流和输出流;
按照操作单元划分,可以划分为字节流和字符流;
按照流的角色划分,可以划分为节点流和处理流。
Java Io 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。
InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
try-catch-finally 其中 catch 和 finally 都可以被省略,但是不能同时省略,也就是说有 try 的时候,必须后面跟一个 catch 或者 finally。
finally 一定会执行,如果是 catch 中 return 了,catch 中的 return 会等 finally 中的代码执行完之后,才会执行。
Exception是程序正常运行中,可以预料的意外情况,可以并且应该被捕获,进行相应的处理。
Error是指正常情况下,不大可能出现的情况,绝大部分的Error都会导致程序(比如JVM自身)处于非正常状态,不可恢复状态。既然是非正常情况,所以不便于也不需要捕获。
并行:多个处理器或多核处理器同时执行多个任务。
并发:多个任务在同一个CPU核上,按细分的时间片交替执行,从感觉上来看那些任务是一起执行的
java里线程分2种,
1、用户线程,就是应用程序里自定义的线程。
2、守护线程(即daemon thread),是运行在后台的一种特殊线程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。是个服务其他线程的线程,最典型的就是垃圾回收线程
创建线程有三种方式:
Java多线程有两个重要的接口,Runnable和Callable,分别提供一个run方法和call方法,二者是有较大差异的。
1)Runnable提供run方法,无法通过throws抛出异常,所有CheckedException必须在run方法内部处理。Callable提供call方法,直接抛出Exception异常。
2)Runnable的run方法无返回值,Callable的call方法提供返回值用来表示任务运行的结果
3)Runnable可以作为Thread构造器的参数,通过开启新的线程来执行,也可以通过线程池来执行。而Callable只能通过线程池执行。
线程的状态:
1 NEW 尚未启动
2 RUNNABLE 执行中
3 BLOCKED 阻塞的(被同步锁或者IO锁阻塞)
4 WAITING 等待
5 TIMED_WAITING 等待到指定的时间被唤醒
6 TERMINATED 结束
start() 方法用于启动线程,只能调用一次。
run() 方法用于执行线程的运行时代码,可以重复调用。
线程池创建有七种方式,最核心的是最后一种:
Lock lock = new ReentrantLock();
lock. lock();
try {
System. out. println("获得锁");
} catch (Exception e) {
// TODO: handle exception
} finally {
System. out. println("释放锁");
lock. unlock();
}
synchronized 锁升级原理:
在锁对象的对象头里面有一个 threadid 字段,
在某个时刻,线程A持有独占锁a,并尝试去获取独占锁b,线程B持有独占锁b,并尝试获取独占锁a,那么在这种情况下,AB 两个线程就会发生阻塞现象,我们称为死锁。
synchronized是由一对monitorenter/monitorexit指令实现的,monitor对象是同步的基本实现单元。
在Java 6之前,monitor的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作,性能也很低。
但在Java 6的时候,Java虚拟机对此进行了大刀阔斧地改进,提供了三种不同的monitor实现,也就是常说的三种不同的锁:偏向锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。
另:
执行同步代码块后首先要先执行monitorenter指令,退出的时候monitorexit指令。通过分析之后可以看出,使用Synchronized进行同步,其关键就是必须要对对象的监视器monitor进行获取,当线程获取monitor后才能继续往下执行,否则就只能等待。而这个获取的过程是互斥的,即同一时刻只有一个线程能够获取到monitor。上面的demo中在执行完同步代码块之后紧接着再会去执行一个静态同步方法,而这个方法锁的对象依然就这个类对象,那么这个正在执行的线程还需要获取该锁吗?答案是不必的,从上图中就可以看出来,执行静态同步方法的时候就只有一条monitorexit指令,并没有monitorenter获取锁的指令。这就是锁的重入性,即在同一锁程中,线程不需要再次获取同一把锁。Synchronized先天具有重入性。每个对象拥有一个计数器,当线程获取该对象锁后,计数器就会加一,释放锁后就会将计数器减一。
任意一个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取该对象的监视器才能进入同步块和同步方法,如果没有获取到监视器的线程将会被阻塞在同步块和同步方法的入口处,进入到BLOCKED状态
该图可以看出,任意线程对Object的访问,首先要获得Object的监视器,如果获取失败,该线程就进入同步状态,线程状态变为BLOCKED,当Object的监视器占有者释放后,在同步队列中得线程就会有机会重新获取该监视器。
synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但是在 Java 6 中对 synchronized 进行了非常多的改进。
主要区别如下:
atomic 主要利用 CAS (Compare And Wwap) 和 volatile 和 native方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
Java 容器分为 Collection 和 Map 两大类,其下又有很多子类,如下所示:
List、Set、Map 的区别主要体现在两个方面:元素是否有序、是否允许元素重复。
三者之间的区别,如下表:
对于在 Map 中插入、删除、定位一个元素这类操作,HashMap 是最好的选择,因为相对而言 HashMap 的插入会更快,但如果你要对一个 key 集合进行有序的遍历,那 TreeMap 是更好的选择。
HashMap 基于 Hash 算法实现的,我们通过 put(key,value)存储,get(key)来获取。
当传入 key 时,HashMap 会根据 key. hashCode() 计算出 hash 值,根据 hash 值将 value 保存在 bucket 里。
当计算出的 hash 值相同时,我们称之为 hash 冲突,HashMap 的做法是用链表和红黑树存储相同 hash 值的 value。当hash冲突个数少时使用链表,当hash冲突个数多时使用红黑树。
HashSet 是基于HashMap 实现的,HashSet 底层使用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。
// list to arrayList list = new ArrayList();list. add("王磊");list. add("的博客");list. toArray();// array to list
String[] array = new String[]{"王磊","的博客"};
Arrays. asList(array);
poll()和remove()都是移除并返回第一个元素,但是在poll()在队列为空时返回null,而remove()会抛出NoSuchElementException异常。
代码示例:
Queue<String> queue = new LinkedList<String>();queue. offer("string"); // add
System. out. println(queue. poll());
System. out. println(queue. remove());
System. out. println(queue. size());
Vector、Hashtable、都是线程安全的,
而像 HashMap 则是非线程安全的,不过在JDK 1.5之后随着Java. util. concurrent并发包的出现,它们也有了自己对应的线程安全类,比如HashMap对应的线程安全类就是ConcurrentHashMap
Iterator是一个可以遍历任何Collection的接口。可以用来遍历Collection ;
在 Collection 中使用迭代器方法来获取迭代器实例。然后遍历,在遍历过程中还可以同时移除元素。
Iterator 使用代码如下:
List<String> list = new ArrayList<>();
Iterator<String> it = list. iterator();
while(it. hasNext()){
String obj = it. next();
System. out. println(obj);
}
Iterator 的特点是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。
可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。
示例代码如下:
List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错
System. out. println(list. size());
反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。
当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。
以下情况需要使用 Java 序列化:
想把的内存中的对象状态保存到一个文件中或者数据库中时候;
想用套接字在网络上传送对象的时候;
想通过RMI(远程方法调用)传输对象的时候。
动态代理是运行时动态生成代理类。
动态代理的应用有 spring aop、hibernate 数据查询、测试框架的后端 mock、rpc,Java注解对象获取等。
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和
CGLIB之间转换
如何强制使用CGLIB实现AOP?
(1)添加CGLIB库,SPRING_HOME/cglib/*.jar
(2)在spring配置文件中加入
JDK动态代理和CGLIB字节码生成的区别?
(1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类
(2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法最好不要声明成final
JDK 原生动态代理和 cglib 动态代理。JDK 原生动态代理是基于接口实现的,而 cglib 是基于继承当前类的子类实现的。
克隆的对象可能包含一些已经修改过的属性,而 new 出来的对象的属性都还是初始化时候的值,
所以需要用克隆方法来把当前对象的“状态”保存到一个新的对象中。
1.第一范式(确保每列保持原子性),不可拆分
2.第二范式(确保表中的非主键列都和主键列完全相关,而不是部分相关), 消除部分依赖
3.第三范式(确保表中的非主键列都和主键列直接相关,而不是间接相关),消除间接依赖
1.列的原子性,每列的属性都是最小的单位,不可再分拆
2.主键唯一,每条记录是惟一的
3.不要有冗余数据,不同类型的数据分开做表
https://blog.csdn.net/ffhgjgj6576567/article/details/118657917
https://www.cnblogs.com/linjiqin/archive/2012/04/01/2428695.html
使用 select version() 获取当前 MySQL 数据库版本。
内连接关键字:inner join;左连接:left join;右连接:right join。
内连接是把匹配的关联数据显示出来;左连接是左边的表全部显示出来,右边的表显示出符合条件的数据;右连接正好相反。
索引是满足某种特定查找算法的数据结构,而这些数据结构会以某种方式指向数据,从而实现高效查找数据。
具体来说MySQL中的索引,不同的数据引擎实现有所不同,但目前主流的数据库引擎的索引都是B+树实现的,B+ 树的搜索效率,可以到达二分法的性能,找到数据区域之后就找到了完整的数据结构了,所有索引的性能也是更好的。
使用 explain 查看 SQL 是如何执行查询语句的,从而分析你的索引是否满足需求。
explain 语法:explain select * from table where type=1。
MySQL 的事务隔离是在 MySQL. ini 配置文件里添加的,在文件的最后添加:
transaction-isolation = REPEATABLE-READ
可用的配置值:READ-UNCOMMITTED、READ-COMMITTED、REPEATABLE-READ、SERIALIZABLE。
MyISAM 只支持表锁,InnoDB 支持表锁和行锁,默认为行锁。
一、架构优化
1.分布式缓存
当接收到查询请求后,我们先查询缓存,判断缓存中是否有数据,有数据就直接返回给应用,如若没有再查询数据库,并加载到缓存中,这样就大大减少了对数据库的访问次数,自然而然也提高了数据库性能。更适用于高并发、大数据量大场景。
2.读写分离
一主多从,读写分离,主动同步,是一种常见的数据库架构优化手段。
一般来说当你的应用是读多写少,数据库扛不住读压力的时候,采用读写分离,通过增加从库数量可以线性提升系统读性能。
主库,提供数据库写服务;从库,提供数据库读能力;主从之间,通过binlog同步数据。
主要是用于解决 “数据库读性能问题”
3.水平切分
当你的应用业务数据量很大,单库容量成为性能瓶颈后,采用水平切分,可以降低数据库单库容量,提升数据库写性能。
主要是用于解决“数据库数据量大的问题”
二、硬件优化
我们使用数据库,不管是读操作还是写操作,最终都是要访问磁盘,所以说磁盘的性能决定了数据库的性能。一块PCIE固态硬盘的性能是普通机械硬盘的几十倍不止。我们可以从吞吐率、IOPS两个维度看一下机械硬盘、普通固态硬盘、PCIE固态硬盘之间的性能指标。通过这个指标数据可以很直观的看到不同规格的硬盘之间的性能差距非常大,当然性能更好的硬盘价格会更贵,在资金充足并且迫切需要提升数据库性能时,尝试更换一下数据库的硬盘不失为一个非常好的举措,你之前遇到SQL执行缓慢问题在你更换硬盘后很可能将不再是问题。
三、DB优化
要让一台数据库实例完全发挥其性能,首先我们就得先优化数据库的实例参数。
数据库实例参数优化遵循三句口诀:日志不能小、缓存足够大、连接要够用。
数据库事务提交后需要将事务对数据页的修改刷( fsync)到磁盘上,才能保证数据的持久性。这个刷盘,是一个随机写,性能较低,如果每次事务提交都要刷盘,会极大影响数据库的 性能。数据库在架构设计中都会采用如下两个优化手法:
先将事务写到日志文件RedoLog(WAL),将随机写优化成顺序写
加一层缓存结构Buffer,将单次写优化成顺序写
所以日志跟缓存对数据库实例尤其重要。而连接如果不够用,数据库会直接抛出异常,系统无法访问。
四、表优化
1.建表时选择合适的存储引擎。Innodb:支持事务和外键,擅长更新,删除。myisam:不支持事务和外键,但是擅长插入和查询。
2.创建索引
对于查询占主要的应用来说,索引显得尤为重要。很多时候性能问题很简单的就是因为我们忘了添加索引而造成的,或者说没有添加更为有效的索引导致。如果不加索引的话,那么查找任何哪怕只是一条特定的数据都会进行一次全表扫描,如果一张表的数据量很大而符合条件的结果又很少,那么不加索引会引起致命的性能下降。但是也不是什么情况都非得建索引不可,比如性别可能就只有两个值,建索引不仅没什么优势,还会影响到更新速度,这被称为过度索引
对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
3.水平分表。
4.垂直分表。
5.三范式和逆三范式应用(减少关联查询(多表联查)(部分展示表可加冗余字段))
五、SQL优化
1.避免select * 写法,列出需要查询的字段
执行SQL时优化器需要将 * 转成具体的列;每次查询都要回表,不能走覆盖索引。
2.避免复杂SQL语句
提升可阅读性;避免慢查询的概率;可以转换成多个短查询,用业务端处理
3.关于UNION和UNION ALL
可以使用联合(UNION)来代替手动创建的临时表,但是使用 union 前,尽量保证 union 之前的结果集已是最小,也就是说,要先用 where 过滤出相应的数据,再做 union ;另外 union 和 union all 是有性能差异的,如果确定前后两段的数据没有重复,尽量用 union all 来替代 union ;因为 union all 的执行效率比 union 高,union 执行时需要排重。
4.like语句操作。
一般情况下不鼓励使用like操作,如果非使用不可,如何使用也是一个问题。like “%aaa%” 不会使用索引而like “aaa%”可以使用索引。
5.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
6.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描(id<>3则可使用id>3 or id<3来代替)。
7.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描(可换union all)
8.应尽量避免 in 和 not in,否则会导致全表扫描,用 exists 代替 in,not in 可以用 not exists 代替。
9.应尽量避免在 where 子句中对字段进行表达式/运算操作,这将导致引擎放弃使用索引而进行全表扫描。
10.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。
11.应尽量避免where 1=1写法,被迫进行全表扫描,执行效率不高
12.应尽量避免order by rand()类似写法,这种随机排序需要 Using temporary 和 Using filesort,查询的执行代价往往比较大。
执行计划
要想优化SQL必须要会看执行计划,执行计划会告诉你哪些地方效率低,哪里可以需要优化。通过explain sql 可以查看执行计划
https://blog.csdn.net/qq_42446961/article/details/121283651 数据库优化的八种方式
https://blog.csdn.net/ThinPikachu/article/details/122152689 数据库优化的四大方法
https://blog.csdn.net/jie_liang/article/details/77340905 sql优化的几种方法
注意
https://www.cnblogs.com/houss/p/10587274.html
301:永久重定向。
302:暂时重定向。
它们的区别是,301 对搜索引擎优化(SEO)更加有利;302 有被提示为网络拦截的风险。
tcp 和 udp 是 OSI 模型中的运输层中的协议。
TCP (Transmission Control ProtocoI) 传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议,
而 UDP (User Datagram Protocol) 用户数据报协议 是一种无连接、不可靠、基于数据报的传输层通信协议
两者的区别大致如下:
如果采用两次握手,那么只要服务器发出确认数据包就会建立连接,但由于客户端此时并未响应服务器端的请求,那此时服务器端就会一直在等待客户端,这样服务器端就白白浪费了一定的资源。若采用三次握手,服务器端没有收到来自客户端的再此确认,则就会知道客户端并没有要求建立请求,就不会浪费服务器的资源。
https://baike.baidu.com/item/%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B/5111559?fr=aladdin
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
(1) TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送。
(2) 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
(3) 服务器关闭客户端的连接,发送一个FIN给客户端。
(4) 客户端发回ACK报文确认,并将确认序号设置为收到序号加1。
https://blog.csdn.net/w8y56f/article/details/120653921?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-120653921-blog-81090081.pc_relevant_3mothn_strategy_and_data_recovery&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-120653921-blog-81090081.pc_relevant_3mothn_strategy_and_data_recovery&utm_relevant_index=2
TCP 粘包可能发生在发送端或者接收端,分别来看两端各种产生粘包的原因:
实现跨域有以下几种方案:
它是利用script标签的 src 连接可以访问不同源的特性,加载远程返回的“JS 函数”来执行的。
JSP(全称JavaServer Pages)是由Sun Microsystems公司主导创建的一种动态网页技术标准。JSP部署于网络服务器上,可以响应客户端发送的请求,并根据请求内容动态地生成HTML、XML或其他格式文档的Web网页,然后返回给请求者。JSP技术以Java语言作为脚本语言,为用户的HTTP请求提供服务,并能与服务器上的其它Java程序共同处理复杂的业务需求。
Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态Web内容。
jsp和servlet的区别有以下几点:
JSP 是 servlet 技术的扩展,本质上就是 servlet 的简易方式。使用Jsp只需要完成程序员需要输出到客户端的内容,Jsp中的Java脚本如度何镶嵌到一个类中,由Jsp容器完成。
1、jsp经编译后就变成了Servlet。
2、jsp更擅长表现于页面显示,侧重于视图;servlet类似于一个Controller,更擅长于控制逻辑。
3、Jsp中的内置对象都是必须通过HttpServletResponse对象以及HttpServlet对象得到;而Servlet则没有内置对象,是个完整的Java类,这个类的Service方法用于生成对客户端的响应
5、Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML里分离开来。而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件。
https://blog.csdn.net/lzh_99999/article/details/105929127
JSP 有 9 大内置对象:
session 的工作原理是客户端登录完成之后,服务器会创建对应的 session,session创建完之后会把 session 的 id 发送给客户端,客户端再存储到浏览器中。这样客户端每次访问服务器时,都会带着 sessionid,服务器拿到 sessionid 之后,在内存找到与之对应的 session 这样就可以正常工作了。
一般默认情况下,在会话中,服务器存储 session 的 sessionid 是通过 cookie 存到浏览器里。
如果浏览器禁用了 cookie,浏览器请求服务器无法携带 sessionid,服务器无法识别请求中的用户身份,session失效。
但是可以通过其他方法在禁用 cookie 的情况下,可以继续使用session。
#{}
是预编译处理,是占位符,${}
是字符串替换,是拼接符#{}
的时候会将sql中的#{}
替换成?号,调用PreparedStatement来赋值${}
的时候就是把${}
替换成变量的值,调用Statement来赋值#{}
的变量替换是在DBMS中、变量替换后,#{}
对应的变量自动加上单引号${}
的变量替换是在DBMS外、变量替换后,${}
对应的变量不会加上单引号#{}
可以有效的防止sql注入,提高系统的安全性分页方式:逻辑分页和物理分页。
RowBounds 表面是在“所有”数据中检索数据,其实并非是一次性查询出所有数据,因为 MyBatis 是对 jdbc 的封装,在 jdbc 驱动中有一个 Fetch Size 的配置,它规定了每次最多从数据库查询多少条数据,假如你要查询更多数据,它会在你执行 next()的时候,去查询更多的数据。就好比你去自动取款机取 10000 元,但取款机每次最多能取 2500 元,所以你要取 4 次才能把钱取完。只是对于 jdbc 来说,当你调用 next()的时候会自动帮你完成查询工作。这样做的好处可以有效的防止内存溢出。
Fetch Size 官方相关文档:http://t. cn/EfSE2g3
一级缓存(mybatis默认支持);SqlSession层,线程级别的
二级缓存;SqlSessionFactory层,进程级别的,xml里面加一个标签
MyBatis 有三种基本的Executor执行器:
分页插件的基本原理是使用 MyBatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 SQL,然后重写 SQL,根据 dialect 方言,添加对应的物理分页语句和物理分页参数。
自定义插件实现原理
MyBatis 自定义插件针对 MyBatis 四大对象(Executor、StatementHandler、ParameterHandler、ResultSetHandler)进行拦截:
Executor:拦截内部执行器,它负责调用 StatementHandler 操作数据库,并把结果集通过 ResultSetHandler 进行自动映射,另外它还处理了二级缓存的操作;
StatementHandler:拦截 SQL 语法构建的处理,它是 MyBatis 直接和数据库执行 SQL 脚本的对象,另外它也实现了 MyBatis 的一级缓存;
ParameterHandler:拦截参数的处理;
ResultSetHandler:拦截结果集的处理。
自定义插件实现关键
MyBatis 插件要实现 Interceptor 接口,接口包含的方法,如下:
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
setProperties 方法是在 MyBatis 进行配置插件的时候可以配置自定义相关属性,即:接口实现对象的参数配置;
plugin 方法是插件用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理,可以决定是否要进行拦截进而决定要返回一个什么样的目标对象,官方提供了示例:return Plugin. wrap(target, this);
intercept 方法就是要进行拦截的时候要执行的方法。
自定义插件实现示例
官方插件实现:
@Intercepts({@Signature(type = Executor. class, method = "query",
args = {MappedStatement. class, Object. class, RowBounds. class, ResultHandler. class})})public class TestInterceptor implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation. getTarget(); //被代理对象
Method method = invocation. getMethod(); //代理方法
Object[] args = invocation. getArgs(); //方法参数
// do something . . . . . . 方法拦截前执行代码块
Object result = invocation. proceed();
// do something . . . . . . . 方法拦截后执行代码块
return result;
}
public Object plugin(Object target) {
return Plugin. wrap(target, this);
}
}
aop 是面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
简单来说就是统一处理某一“切面”(类)的问题的编程思想,比如统一处理日志、异常等。
ioc:Inversion of Control(中文:控制反转)是 spring 的核心,对于 spring 框架来说,就是由 spring 来负责控制对象的生命周期和对象间的关系。
简单来说,控制指的是当前对象对内部成员的控制权;控制反转指的是,这种控制权不由当前对象管理了,由其他(类,第三方容器)来管理。
spring中的bean默认是单例模式,但是spring框架并没有对单例bean进行多线程的封装处理。所有线程都共享一个单例实例Bean,因此存在资源的竞争,不能保证绝对安全。
不过实际上大部分时候spring bean是无状态的,所有某种程度上来说bean也是安全的,但如果bean 有状态的话,那就要去解决线程安全问题了。最简单的就是改变bean的作用域,把“singleton”变更为“prototype”,这样请求bean相当于new Bean( ),线程之间就不会再存在Bean共享,就不会有线程安全的问题了
spring支持5种作用域,如下:
实现方式共有两种:声明式事务管理方式;编码方式
声明式事务管理又有两种实现方式:基于XML配置文件的方式和注解方式
编码方式:提供编码的形式管理和维护事务。
当多个事务同时访问相同数据时,如果没有采取必要的隔离机制,就可能发生以下问题:
不可重复读和幻读比较:
两者有些相似,但是前者针对的是update或delete,后者针对的insert。
spring 有五大隔离级别,默认值为 ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和数据库的隔离级别一致:
Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。
https://blog.csdn.net/m0_53067943/article/details/123926050
将 http 请求映射到相应的类/方法上。
@Autowired 它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作,通过@Autowired 的使用来消除 set/get 方法。
配置简单
独立运行
自动装配
无代码生成和 xml 配置
提供应用监控
易上手
提升开发效率
spring boot 核心的两个配置文件:
配置文件有 . properties 格式和 . yml 格式,它们主要的区别是书法风格不同。
. properties 配置如下:
spring. RabbitMQ. port=5672
. yml 配置如下:
spring:
RabbitMQ:
port: 5672
. yml 格式不支持 @PropertySource 注解导入。
jpa 全称 Java Persistence API,是 Java 持久化接口规范,hibernate 属于 jpa 的具体实现。
数据缓存功能
分布式锁的功能
支持数据持久化
支持事务
支持消息队列
1)redis支持持久化
2)redis支持存储类型更多
因为 cpu 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存或者网络带宽。既然单线程容易实现,而且 cpu 又不会成为瓶颈,那就顺理成章地采用单线程的方案了。
关于 Redis 的性能,官方网站也有,普通笔记本轻松处理每秒几十万的请求。
而且单线程并不代表就慢 nginx 和 nodejs 也都是高性能单线程的代表。
缓存穿透:指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
解决方案:最简单粗暴的方法如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们就把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
string(字符串)、list(列表)、set(集合)、zset(有序集合)、hash(字典)
支持的 Java 客户端有 Redisson、jedis、lettuce 等。
Redis 的持久化有两种方式,或者说有两种策略:
Redis 分布式锁其实就是在系统里面占一个“坑”,其他程序也要占“坑”的时候,占用成功了就可以继续执行,失败了就只能放弃或稍后重试。
占坑一般使用 setnx(set if not exists)指令,只允许被一个程序占有,使用完调用 del 释放锁。
Redis 分布式锁不能解决超时的问题,分布式锁有一个超时时间,程序的执行如果超出了锁的超时时间就会出现问题。
尽量使用 Redis 的散列表,把相关的信息放到散列表里面存储,而不是把每个字段单独存储,这样可以有效的减少内存使用。比如将 Web 系统的用户对象,应该放到散列表里面再整体存储到 Redis,而不是把用户的姓名、年龄、密码、邮箱等字段分别设置 key 进行存储。
异步、削峰、解耦
RabbitMQ 中重要的角色有:生产者、消费者和代理:
vhost:每个 RabbitMQ 都能创建很多 vhost,我们称之为虚拟主机,每个虚拟主机其实都是 mini 版的RabbitMQ,它拥有自己的队列,交换器和绑定,拥有自己的权限机制。
首先客户端必须连接到 RabbitMQ 服务器才能发布和消费消息,客户端和 rabbit server 之间会创建一个 tcp 连接,一旦 tcp 打开并通过了认证(认证就是你发送给 rabbit 服务器的用户名和密码),你的客户端和 RabbitMQ 就创建了一条 amqp 信道(channel),信道是创建在“真实” tcp 上的虚拟连接,amqp 命令都是通过信道发送出去的,每个信道都会有一个唯一的 id,不论是发布消息,订阅队列都是通过这个信道完成的。
声明队列必须设置持久化 durable 设置为 true.
消息推送投递模式必须设置持久化,deliveryMode 设置为 2(持久)。
消息已经到达持久化交换器。
消息已经到达持久化队列。
以上四个条件都满足才能保证消息持久化成功。
持久化的缺地就是降低了服务器的吞吐量,因为使用的是磁盘而非内存存储,从而降低了吞吐量。可尽量使用 ssd 硬盘来缓解吞吐量的问题。
direct(默认方式):最基础最简单的模式,发送方把消息发送给订阅方,如果有多个订阅者,默认采取轮询的方式进行消息发送。
fanout:分发模式,把消费分发给所有订阅者。
topic:匹配订阅模式,使用正则匹配到消息队列,能匹配到的都能接收到。
延迟队列的实现有两种方式:
通过消息过期后进入死信交换器,再由交换器转发到延迟消费队列,实现延迟功能;
使用 RabbitMQ-delayed-message-exchange 插件实现延迟功能。
集群主要有以下两个用途:
高可用:某个服务器出现问题,整个 RabbitMQ 还可以继续使用;
高容量:集群可以承载更多的消息量。
磁盘节点:消息会存储到磁盘。
内存节点:消息都存储在内存中,重启服务器消息丢失,性能高于磁盘类型。
各节点之间使用“–link”连接,此属性不能忽略。
各节点使用的 erlang cookie 值必须相同,此值相当于“秘钥”的功能,用于各节点的认证。
整个集群中必须包含一个磁盘节点。
不是,原因有以下两个:
存储空间的考虑:如果每个节点都拥有所有队列的完全拷贝,这样新增节点不但没有新增存储空间,反而增加了更多的冗余数据;
性能的考虑:如果每条消息都需要完整拷贝到每一个集群节点,那新增节点并没有提升处理消息的能力,最多是保持和单节点相同的性能甚至是更糟。
如果唯一磁盘的磁盘节点崩溃了,不能进行以下操作:
不能创建队列
不能创建交换器
不能创建绑定
不能添加用户
不能更改权限
不能添加和删除集群节点
唯一磁盘节点崩溃了,集群是可以保持运行的,但你不能更改任何东西。
RabbitMQ 对集群的停止的顺序是有要求的,应该先关闭内存节点,最后再关闭磁盘节点。如果顺序恰好相反的话,可能会造成消息的丢失。
Elasticsearch是一个基于Lucene的搜索引擎。它提供了具有HTTP Web界面和无架构JSON文档的分布式,多租户能力的全文搜索引擎。
Elasticsearch是用Java开发的,根据Apache许可条款作为开源发布。
ElasticSearch可以有一个架构。架构是描述文档类型以及如何处理文档的不同字段的一个或多个字段的描述。Elasticsearch中的架构是一种映射,它描述了JSON文档中的字段及其数据类型,以及它们应该如何在Lucene索引中进行索引。因此,在Elasticsearch术语中,我们通常将此模式称为“映射”。
Elasticsearch具有架构灵活的能力,这意味着可以在不明确提供架构的情况下索引文档。如果未指定映射,则默认情况下,Elasticsearch会在索引期间检测文档中的新字段时动态生成一个映射。
一个索引被分解成碎片以便于分发和扩展。副本是分片的副本。一个节点是一个属于一个集群的ElasticSearch的运行实例。一个集群由一个或多个共享相同集群名称的节点组成。
在ElasticSearch中索引数据时,数据由为索引定义的Analyzer在内部进行转换。 分析器由一个Tokenizer和零个或多个TokenFilter组成。编译器可以在一个或多个CharFilter之前。分析模块允许您在逻辑名称下注册分析器,然后可以在映射定义或某些API中引用它们。
Elasticsearch附带了许多可以随时使用的预建分析器。或者,您可以组合内置的字符过滤器,编译器和过滤器器来创建自定义分析器。
编译器用于将字符串分解为术语或标记流。一个简单的编译器可能会将字符串拆分为任何遇到空格或标点的地方。Elasticsearch有许多内置标记器,可用于构建自定义分析器。
enabled属性适用于各类ElasticSearch特定/创建领域,如index和size。用户提供的字段没有“已启用”属性。 存储意味着数据由Lucene存储,如果询问,将返回这些数据。
存储字段不一定是可搜索的。默认情况下,字段不存储,但源文件是完整的。因为您希望使用默认值(这是有意义的),所以不要设置store属性 该指数属性用于搜索。
索引属性只能用于搜索。只有索引域可以进行搜索。差异的原因是在分析期间对索引字段进行了转换,因此如果需要的话,您不能检索原始数据。
因为在我们商城中的数据,将来会非常多,所以采用以往的模糊查询,模糊查询前置配置,会放弃索引,导致商品查询是全表扫面,在百万级别的数据库中,效率非常低下,而我们使用ES做一个全文索引,我们将经常查询的商品的某些字段,比如说商品名,描述、价格还有id这些字段我们放入我们索引库里,可以提高查询速度。
在大多数环境中,每个节点都在单独的盒子或虚拟机上运行。
索引 - 在Elasticsearch中,索引是文档的集合。
分片 -因为Elasticsearch是一个分布式搜索引擎,所以索引通常被分割成分布在多个节点上的被称为分片的元素。
spring cloud 是一系列框架的有序集合。它利用 spring boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 spring boot 的开发风格做到一键启动和部署。
在分布式架构中,断路器模式的作用也是类似的,当某个服务单元发生故障之后,通过断路器的故障监控,向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。
类加载器(ClassLoader)
运行时数据区(Runtime Data Area)
执行引擎(Execution Engine)
本地库接口(Native Interface)
首先,通过Java编译器把 Java 代码转换成.class字节码文件;
其次,类加载器(ClassLoader)把字节码文件加载到运行时数据区(Runtime Data Area)即JVM内存中,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行;
于是,需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行;
最后,此过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。
https://blog.csdn.net/drose29/article/details/125296697
https://blog.csdn.net/wjgcl/article/details/124805628
不同虚拟机的运行时数据区可能略微有所不同,但都会遵从 Java 虚拟机规范, Java 虚拟机规范规定的区域分为以下 5 个部分:
程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成;
Java 虚拟机栈(Java Virtual Machine Stacks):用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;
Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;
方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。
功能方面:堆是用来存放对象的,栈是用来执行程序的。
共享性:堆是线程共享的,栈是线程私有的。
空间大小:堆大小远远大于栈。
队列和栈都是被用来预存储数据的。
队列允许先进先出检索元素,但也有例外的情况,Deque 接口允许从两端检索元素。
栈和队列很相似,但它运行对元素进行后进先出进行检索。
在介绍双亲委派模型之前先说下类加载器。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立在 JVM 中的唯一性,每一个类加载器,都有一个独立的类名称空间。类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class 对象。
类加载器分类:
启动类加载器(Bootstrap ClassLoader),是虚拟机自身的一部分,用来加载Java_HOME/lib/目录中的,或者被 -Xbootclasspath 参数所指定的路径中并且被虚拟机识别的类库;
其他类加载器:
扩展类加载器(Extension ClassLoader):负责加载\lib\ext目录或Java. ext. dirs系统变量指定的路径中的所有类库;
应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。
双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。
loadClass方法的主要职责就是实现双亲委派机制:首先检查这个类是不是已经被加载过了,如果加载过了直接返回,否则委派给父加载器加载,这是一个递归调用,一层一层向上委派,最顶层的类加载器(启动类加载器)无法加载该类(它的搜索范围中没找到所需的类)时,再一层一层向下委派给子类加载器加载。
为什么要双亲委派?
双亲委派保证类加载器,自下而上的委派,又自上而下的加载,保证每一个类在各个类加载器中都是同一个类。
一个非常明显的目的就是保证java官方的类库\lib和扩展类库\lib\ext的加载安全性,不会被开发者覆盖。
https://blog.csdn.net/wzwjm123/article/details/124803527?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-124803527-blog-125683873.pc_relevant_aa&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-124803527-blog-125683873.pc_relevant_aa&utm_relevant_index=2
类装载分为以下 5 个步骤:
加载:根据查找路径找到相应的 class 文件然后导入;
检查:检查加载的 class 文件的正确性;
准备:给类中的静态变量分配内存空间;
解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;
初始化:对静态变量和静态代码块执行初始化工作。
一般有两种方法来判断:
引用计数器:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;
可达性分析:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。
强引用:发生 gc 的时候不会被回收。
软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。
弱引用:有用但不是必须的对象,在下一次GC时会被回收。
虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。
标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片。
标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。
复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。
分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。
Serial:最早的单线程串行垃圾回收器。
Serial Old:Serial 垃圾回收器的老年版本,同样也是单线程的,可以作为 CMS 垃圾回收器的备选预案。
ParNew:是 Serial 的多线程版本。
Parallel 和 ParNew 收集器类似是多线程的,但 Parallel 是吞吐量优先的收集器,可以牺牲等待时间换取系统的吞吐量。
Parallel Old 是 Parallel 老生代版本,Parallel 使用的是复制的内存回收算法,Parallel Old 使用的是标记-整理的内存回收算法。
CMS:一种以获得最短停顿时间为目标的收集器,非常适用 B/S 系统。
G1:一种兼顾吞吐量和停顿时间的 GC 实现,是 JDK 9 以后的默认 GC 选项。
CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。
CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。
新生代回收器:Serial、ParNew、Parallel Scavenge
老年代回收器:Serial Old、Parallel Old、CMS
整堆回收器:G1
新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。
分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。
新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:
把 Eden + From Survivor 存活的对象放入 To Survivor 区;
清空 Eden 和 From Survivor 分区;
From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。
每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。
老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。
JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。
jconsole:用于对 JVM 中的内存、线程和类等进行监控;
jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等。
-Xms2g:初始化推大小为 2g;
-Xmx2g:堆最大内存为 2g;
-XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;
-XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;
–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;
-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;
-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
-XX:+PrintGC:开启打印 gc 信息;
-XX:+PrintGCDetails:打印 gc 详细信息。
凡事预则立,不预则废。能读到这里的人,我相信都是这个世界上的“有心人”,星光不问赶路人,时光不负有心人!我相信你的每一步努力,都会收获意想不到的回报。
答案很简单:数据库出现性能瓶颈。用大白话来说就是数据库快扛不住了。
数据库出现性能瓶颈,对外表现有几个方面:
大量请求阻塞在高并发场景下,大量请求都需要操作数据库,导致连接数不够了,请求处于阻塞状态。
SQL 操作变慢如果数据库中存在一张上亿数据量的表,一条 SQL 没有命中索引会全表扫描,这个查询耗时会非常久。
存储出现问题业务量剧增,单库数据量越来越大,给存储造成巨大压力。
从机器的角度看,性能瓶颈无非就是CPU、内存、磁盘、网络这些,要解决性能瓶颈最简单粗暴的办法就是提升机器性能,但是通过这种方法成本和收益投入比往往又太高了,不划算,所以重点还是要从软件角度入手。
kafka 不能脱离 zookeeper 单独使用,因为 kafka 使用 zookeeper 管理和协调 kafka 的节点服务器。
kafka 有两种数据保存策略:按照过期时间保留和按照存储的消息大小保留。
这个时候 kafka 会执行数据清除工作,时间和大小不论那个满足条件,都会清空数据。
cpu 性能瓶颈
磁盘读写瓶颈
网络瓶颈
集群的数量不是越多越好,最好不要超过 7 个,因为节点越多,消息复制需要的时间就越长,整个群组的吞吐量就越低。
集群数量最好是单数,因为超过一半故障集群就不能用了,设置为单数容错率更高。
zookeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 google chubby 的开源实现,是 hadoop 和 hbase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
集群管理:监控节点存活状态、运行请求等。
主节点选举:主节点挂掉了之后可以从备用的节点开始新一轮选主,主节点选举说的就是这个选举的过程,使用 zookeeper 可以协助完成这个过程。
分布式锁:zookeeper 提供两种锁:独占锁、共享锁。独占锁即一次只能有一个线程使用资源,共享锁是读锁共享,读写互斥,即可以有多线线程同时读同一个资源,如果要使用写锁也只能有一个线程使用。zookeeper可以对分布式锁进行控制。
命名服务:在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息。
zookeeper 有三种部署模式:
单机部署:一台集群上运行;
集群部署:多台集群运行;
伪集群部署:一台集群启动多个 zookeeper 实例运行。
zookeeper 的核心是原子广播,这个机制保证了各个 server 之间的同步。实现这个机制的协议叫做 zab 协议。 zab 协议有两种模式,分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,zab 就进入了恢复模式,当领导者被选举出来,且大多数 server 完成了和 leader 的状态同步以后,恢复模式就结束了。状态同步保证了 leader 和 server 具有相同的系统状态。
在分布式环境中,有些业务逻辑只需要集群中的某一台机器进行执行,其他的机器可以共享这个结果,这样可以大大减少重复计算,提高性能,所以就需要主节点。
可以继续使用,单数服务器只要没超过一半的服务器宕机就可以继续使用。
客户端端会对某个 znode 建立一个 watcher 事件,当该 znode 发生变化时,这些客户端会收到 zookeeper 的通知,然后客户端可以根据 znode 变化来做出业务上的改变。
https://blog.csdn.net/weixin_51291483/article/details/109212137/
hibernate 是对 jdbc 的封装,大大简化了数据访问层的繁琐的重复性代码。
hibernate 是一个优秀的 ORM 实现,很多程度上简化了 DAO 层的编码功能。
可以很方便的进行数据库的移植工作。
提供了缓存机制,是程序执行更改的高效。
ORM(Object Relation Mapping)对象关系映射,是把数据库中的关系数据映射成为程序中的对象。
使用 ORM 的优点:提高了开发效率降低了开发成本、开发更简单更对象化、可移植更强。
在 Config 里面把 hibernate. show_SQL 设置为 true 就可以。但不建议开启,开启之后会降低程序的运行效率。
三种:hql、原生 SQL、条件查询 Criteria。
实体类可以定义为 final 类,但这样的话就不能使用 hibernate 代理模式下的延迟关联提供性能了,所以不建议定义实体类为 final。
Integer 类型为对象,它的值允许为 null,而 int 属于基础数据类型,值不能为 null。
读取并解析配置文件。
读取并解析映射文件,创建 SessionFactory。
打开 Session。
创建事务。
进行持久化操作。
提交事务。
关闭 Session。
关闭 SessionFactory。
数据查询时,没有 OID 指定的对象,get() 返回 null;load() 返回一个代理对象。
load()支持延迟加载;get() 不支持延迟加载。
hibernate 常用的缓存有一级缓存和二级缓存:
一级缓存:也叫 Session 缓存,只在 Session 作用范围内有效,不需要用户干涉,由 hibernate 自身维护,可以通过:evict(object)清除 object 的缓存;clear()清除一级缓存中的所有缓存;flush()刷出缓存;
二级缓存:应用级别的缓存,在所有 Session 中都有效,支持配置第三方的缓存,如:EhCache。
临时/瞬时状态:直接 new 出来的对象,该对象还没被持久化(没保存在数据库中),不受 Session 管理。
持久化状态:当调用 Session 的 save/saveOrupdate/get/load/list 等方法的时候,对象就是持久化状态。
游离状态:Session 关闭之后对象就是游离状态。
getCurrentSession 会绑定当前线程,而 openSession 则不会。
getCurrentSession 事务是 Spring 控制的,并且不需要手动关闭,而 openSession 需要我们自己手动开启和提交事务。
hibernate 中每个实体类必须提供一个无参构造函数,因为 hibernate 框架要使用 reflection api,通过调用 ClassnewInstance() 来创建实体类的实例,如果没有无参的构造函数就会抛出异常。