1.springboot原理
![(C:\Users\bitka\AppData\Roaming\Typora\typora-user-images\image-20191101203033564.png)
pom.xml:
https://www.cnblogs.com/jstarseven/p/11087157.html
https://www.jianshu.com/p/ef6f0c0de38f
2.原生mybatis
3.java集合框架
注:边框是细小点的是接口,长点的是抽象类,实线的是具体类。
https://blog.csdn.net/zknxx/article/details/53728768
3.1 哪些集合类提供对元素的随机访问?
ArrayList、HashMap、TreeMap和HashTable类提供对元素的随机访问。
4.设计模式、设计原则
- 单一职责原则
单一职责原则(SRP:Single responsibility principle)又称单一功能原则,它规定一个类应该只有一个发生变化的原因。所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于一个的职责。而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因。
-
开放封闭原则
对扩展开放,对修改封闭。
这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。
如何才能实现耦合度和灵活性兼得呢?
那就是抽象,将业务功能抽象为接口,当业务员依赖于固定的抽象时,对修改就是封闭的,而通过继承和多态继承,从抽象体中扩展出新的实现,就是对扩展的开放。
这样当业务变更时,只需要修改对应的业务实现类就可以,其他不相干的业务就不必修改。当业务增加,只需要增加业务的实现就可以了。
-
里氏替换原则
如果对每一个类型为S的对象o1,都有类型为T的对象o2, 使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。
通俗点讲,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。 但是,反过来就不行了,有子类出现的地方,父类未必就能适应。
有时候父类有多个子类,但在这些子类中有一个特例。要想满足里氏替换原则,又想满足这个子类的功能时,有的伙伴可能会修改父类的方法。但是,修改了父类的方法又会对其他的子类造成影响,产生更多的错误。这是怎么办呢?我们可以为这个特例创建一个新的父类,这个新的父类拥有原父类的部分功能,又有不同的功能。这样既满足了里氏替换原则,又满足了这个特例的需求。
-
迪米特法则(最少知识原则)
一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。一个软件实体应当尽可能少的与其他实体发生相互作用。每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
迪米特法则的初衷是降低类之间的耦合,由于每个类都减少了不必要的依赖,因此的确可以降低耦合关系。过分的使用迪米特原则,会产生大量这样的中介和传递类,导致系统的复杂度变大。所以在采用迪米特原则的时间,要反复权衡,既做到结构清晰,又要高内聚低耦合。
-
接口隔离原则
客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。不要在一个接口里面放很多的方法,这样会显得这个类很臃肿不堪。接口应该尽量细化,一个接口对应一个功能模块,同时接口里面的方法应该尽可能的少,使接口更加轻便灵活。或许看到接口隔离原则这样的定义很多人会觉得和单一职责原则很像,但是这两个原则还是有着很鲜明的区别。接口隔离原则和单一职责原则的审视角度是不同的,单一职责原则要求类和接口职责单一,注重的是职责,是业务逻辑上的划分,而接口隔离原则要求方法要尽可能的少,是在接口设计上的考虑。
当细粒度减小之后,复用性就提高了;类也不需要实现不合适的接口而造成承担不需要承担的行为,也不存在违反LSP或者SRP。
-
依赖倒置原则
依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
抽象是对实现的约束,是对依赖者的一种契约,不仅仅约束自己,还同时约束自己与外部的关系,其目的就是保证所有的细节不脱离契约的范畴,确保约束双方按照规定好的契约(抽象)共同发展,只要抽象这条线还在,细节就脱离不了这个圈圈。
参考:https://blog.csdn.net/ztchun/article/details/93521119
5.SpringMVC的xml替代方式
使用以下三个注解之一或使用SPI:
@WebServlet()
@WebListener()
@WebFilter
或使用SPI
6.tomcat容器
范围大小:
7.MySQL的存储引擎InnoDB与MyISAM的区别:
存储结构
MyISAM:每个MyISAM在磁盘上存储成三个文件。第一个文件的名字以表的名字开始,扩展名指出文件类型。
> .frm(文件存储表定义)
> MYD(MYData,存储数据文件)
> MYI(MYIndex,存储索引文件)
InnoDB:所有的表都保存在同一个数据文件中(也可能是多个文件,或者是独立的表空间文件,这个具体要看独立表空间是否默认开启,如果默认开始(5.7已经默认),那便是多个文件),InnoDB表的大小只受限于操作系统文件的大小,一般为2GB。
参考: https://www.cnblogs.com/kaleidoscope/p/9791116.html
最主要的区别是:
- InnoDB支持事务,而MyISAM不支持;
- InnoDB支持行级锁,而MyISAM不支持;
- InnoDB偏向于高并发复杂事务,MyISAM偏向于快速读
7.1 使用or的索引失效情况:
8.callable与Runnable的区别
9.什么时候用接口
接口指定标准:一流的企业做标准、二流的企业做品牌、三流的企业做产品。
-
有扩展型需求时:可扩展设计主要利用面向对象的多态性。
-
要给外界提供API的时候:规范的内容都是抽象的,对外发布的形式都是接口,它不提供实现,最多会知道实现
9.1接口有什么好处
1.制定标准
标准规范的指定都离不开接口,指定标准的目的就是为了让定义和实现分开。而接口作为完全的抽象,是指定标准的不二之选。
2.提供抽象
抽象得以让接口的调用者和实现着可以完全解耦。解耦的好处是调用者不需要依赖具体的实现,这样也就不需要关心实现的细节。
10.MySQL隔离级别
- 脏读:指一个线程中的事务读取到了另外一个线程中未提交的数据。
- 不可重复读(虚读):指一个线程中的事务读取到了另外一个线程中提交的update的数据。
- 幻读:指一个线程中的事务读取到了另外一个线程中提交的insert或delete的数据。
隔离级别:
隔离级别 | 脏读(Dirty Read) | 不可重复读(NonRepeatable Read) | 幻读(Phantom Read) |
---|---|---|---|
未提交读(Read uncommitted) | 可能 | 可能 | 可能 |
已提交读(Read committed) | 不可能 | 可能 | 可能 |
可重复读(Repeatable read) | 不可能 | 不可能 | 可能/不可能 |
可串行化(Serializable ) | 不可能 | 不可能 | 不可能 |
不可重复读的重点是修改: 同样的条件的select, 你读取过的数据, 再次读取出来发现值不一样了
幻读的重点在于新增或者删除: 同样的条件的select, 第1次和第2次读出来的记录数不一样
不可重复读(虚读)和幻读的差别: 从总的结果来看, 似乎两者都表现为两次读取的结果不一致. 但如果你从控制的角度来看, 两者的区别就比较大: 对于前者, 只需要锁住满足条件的记录 对于后者, 要锁住满足条件及其相近的记录
参考: https://www.cnblogs.com/lz0925/articles/8988922.html
10.1怎么解决幻读
一般的数据库避免幻读需要在串行化的事务隔离级别下,而InnoDB在可重复读RR的事务隔离级别下消除幻读;这样能够有效提高数据库的并发度。
很明显可重复读的隔离级别没有办法彻底的解决幻读的问题,如果需要解决幻读的话也有两个办法:
- 使用串行化读的隔离级别
- MVCC+next-key locks:next-key locks由record locks(索引加锁) 和 gap locks(间隙锁,每次锁住的不光是需要使用的数据,还会锁住这些数据附近的数据)
MVCC 不能解决幻读的问题,Next-Key Locks 就是为了解决这个问题而存在的。在可重复读(REPEATABLE READ)隔离级别下,使用 MVCC + Next-Key Locks 可以解决幻读问题。
InnoDB存储引擎处理幻读的方式Next-Key-locks。
MySQL RR下,如果没有locking read(select for update之类),它不会产生幻读,这种情形下是通过MVCC的快照保证的。如果存在locking read,那么可能会产生幻读。这种幻读一般会在一个locking read跟着一个non-locking read的时候发生,想避免这种情形,要么对所有的读都加锁,要么就是增加隔离级别到 serializable。这种情形,避免幻读是由锁保证的。
MySQL在RR级别下“解决”幻读的方式:
- 快照读(Snapshort Read / Consistent Read)之间通过MVCC实现。
- 当前读(Current Read / Locking Read)之间由Next-Key Lock实现。
- 快照读与当前读之间仍然有幻读。
https://www.zhihu.com/question/372905832
间隙锁和排它锁可解决mysql隔离级别中的幻读问题。
默认情况下,InnoDB工作在RR可重复读隔离级别下,并且会以Next-Key Lock的方式对数据行进行加锁,这样可以有效防止幻读的发生。Next-Key Lock是行锁和间隙锁的组合,当InnoDB扫描索引记录的时候,会首先对索引记录加上行锁(Record Lock),再对索引记录两边的间隙加上间隙锁(Gap Lock)。加上间隙锁之后,其他事务就不能在这个间隙修改或者插入记录,从而避免了幻读。
Gap Lock在InnoDB的唯一作用就是防止其他事务的插入操作,以此防止幻读的发生。
可以认为MVCC是行级锁一个变种,但是他很多情况下避免了加锁操作,开销更低。虽然不同数据库的实现机制有所不同,但大都实现了非阻塞的读操作(读不用加锁,且能避免出现不可重复读和幻读),写操作也只锁定必要的行(写必须加锁,否则不同事务并发写会导致数据不一致)。
https://blog.csdn.net/weixin_33888907/article/details/91398775
11.间隙锁、排它锁
MySQL InnoDB支持三种行锁定方式:
l 行锁(Record Lock):锁直接加在索引记录上面,锁住的是key。
l 间隙锁(Gap Lock):锁定索引记录间隙,确保索引记录的间隙不变。间隙锁是针对事务隔离级别为可重复读或以上级别而已的。
l Next-Key Lock :行锁和间隙锁组合起来就叫Next-Key Lock = Record Lock + Gap Lock。
12.MySQL行锁、表锁的特点
13.并发编程
14.Redis持久化机制
缓存与数据库数据一致性 :延时双删(不保证100%解决),串行化(100%解决)
15.spring源码
16.@Import注解的4种用法
17.动态代理
1.CGLIB
2.jdk
17.1 JDK动态代理与CGLIB实现区别
JDK动态代理底层实现: JDK的动态代理使用Java的反射技术生成动态代理类,只能代理实现了接口的类, 没有实现接口的类不能实现动态代理。 CGLIB动态代理底层实现: 运行时动态的生成一个被代理类的子类(通过ASM字节码处理框架实现),子类重写了被代理类中所有非final的方法,在子类中采用方法拦截的技术拦截所有父类方法的调用,不需要被代理类对象实现接口,从而CGLIB动态代理效率比Jdk动态代理反射技术效率要高。其底层实现是通过ASM字节码处理框架来转换字节码并生成新的类。大部分功能实际上是ASM所提供的,Cglib只是封装了ASM,简化了ASM操作,实现了运行期生成新的class。
优点:JDK动态代理要求被代理的类必须实现接口,当需要代理的类没有实现接口时Cglib代理是一个很好的选择。另一个优点是Cglib动态代理比使用java反射的JDK动态代理要快 缺点:对于被代理类中的final方法,无法进行代理,因为子类中无法重写final函数
18.布隆过滤器Bloom Filter
19.开发自己的springboot-starter
20.spring面试题
spring流程:
21.分布式锁
22.单例模式
1.懒汉式双重检查:
2.静态内部类
(1)Singleton装载时不会装载静态内部类SingletonInstance,使getInstance()可用
(2)当getInstance()使用到静态内部类SingletonInstance的静态变量INSTANCE时会导致静态内部类的装载
(3)JVM装载SingletonInstance时是线程安全的(由JVM的类装载机制保证)
总结:可保证懒加载和线程安全
3.枚举方式
不仅能避免多线程安全问题,还能防止反序列化重新创建新的对象。
参考: https://www.cnblogs.com/jamaler/p/11421049.html
22.1 枚举类为什么能实现单例
单例模式三个主要特点:1、构造方法私有化;2、实例化的变量引用私有化;3、获取实例的方法共有。
枚举单例:
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
编译后相当于:
public final class EnumSingleton extends Enum< EnumSingleton> {
public static final EnumSingleton ENUMSINGLETON;
public static EnumSingleton[] values();
public static EnumSingleton valueOf(String s);
static {};
}
枚举Enum是个抽象类,其实一旦一个类声明为枚举,实际上就是继承了Enum。
反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。
枚举是真正的 final,客户端不允许创建枚举类的实例,也不能对其进行拓展。
Java 枚举本质上是 int 值。只能通过公有的静态 final 域为枚举类导出实例。
枚举类不给外界实例化的机会,只能它自己实例化。
枚举类型防止反序列化创建新对象。 在序列化和反序列化期间,任何特定于类的writeObject,readObject,readObjectNoData,writeReplace和readResolve方法都会被忽略。 同样,任何serialPersistentFields或serialVersionUID字段声明也会被忽略,所有枚举类型的fixedserialVersionUID都是0L。(也就是说枚举类型序列化反序列化机制与其他类型的不一样)。 其次,枚举对象的序列化、反序列化有自己的一套机制。序列化时,仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf()方法来根据名字查找枚举对象。
枚举在反序列化的过程中并没有创建新的对象,而通过name属性拿到原有的对象,因此保证了枚举类型实现单例模式的序列化安全。
枚举类型防止反射机制创建新对象。
底层源码是不允许通过反射机制创建一个枚举对象的,因此保证了枚举类型实现单例模式的反射安全。
https://blog.csdn.net/whgtheone/article/details/82990139
23.并发安全解决方案
24.避免所误删的方案
25.面试知识点
SQL优化
索引失效条件
Redis与数据库数据不一致该怎么处理?
Mybatis二级缓存及作用
鉴权token流程
25.1 简历参考:
26.mybatis执行sql的流程
27.websocket与http的区别
28.什么资源需要池化技术
29.动态代理原理图
30.设计模式和设计原则
## 31.GC Root节点
https://blog.csdn.net/qq_15037231/article/details/102081075
32.GC垃圾回收
GC工具:GCView、GCEasy、JClarity、GCPlot
JVM工具: Virsul GC、MBeans
在新生代中又分为了三个区域:Eden 空间、To Survivor空间、From Survivor空间。一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间(一般为8:1:1)。
新的对象实例被创建的时候通常在Eden空间,发生在Eden空间上的GC称为Minor GC。当在新生代发生一次GC后,会将Eden和其中一个Survivor空间的内存复制到另外一个Survivor中,如果反复复制到15次(代)对象一直存活,此时内存对象将会被移至老年代。
新生代中Eden占了大部分,而两个Survivor实际上占了很小一部分。这是因为大部分的对象被创建过后很快就会被GC(这里也许运用了是二八原则)。
老年代的垃圾回收称为“Major GC”。
33.GC调优步骤
34.为什么分布式锁不建议使用Redis?
-
主从切换可能丢失锁信息
当第一个线程在主服务器上设置了锁,但此时还没来得及把锁同步到从服务器,主服务器就挂了, 从服务器为主服务器 。 如果在并发量大的情况下,虽然第一个线程获取了锁,其他线程会在当前的主服务器(之前的从服务器,但是并没有同步已经设置的锁字段)上设置锁字段,这样并不能保证锁的互斥性。
-
缓存易失性
假如第一个线程设置了锁,但是之后触发内存淘汰机制很不幸淘汰了设置的锁字段,接下来的线程在第一个线程没有释放锁的情况下,也是重新设置锁字段的,这样并不能保证锁的安全性。
35. Spring&JDK源码中的设计模式
36.设计模式:抽象工厂、原型(Prototype)
37.脑裂问题
对付HA(High Available高可用)系统“裂脑”的对策,目前达成共识的的大概有以下几条:
1)添加冗余的心跳线,例如:双线条线(心跳线也HA),尽量减少“裂脑”发生几率;
2)启用磁盘锁。正在服务一方锁住共享磁盘,“裂脑”发生时,让对方完全“抢不走”共享磁盘资源。但使用锁磁盘也会有一个不小的问题,如果占用共享盘的一方不主动“解锁”,另一方就永远得不到共享磁盘。现实中假如服务节点突然死机或崩溃,就不可能执行解锁命令。后备节点也就接管不了共享资源和应用服务。于是有人在HA中设计了“智能”锁。即:正在服务的一方只在发现心跳线全部断开(察觉不到对端)时才启用磁盘锁。平时就不上锁了。
3)设置仲裁机制。例如设置参考IP(如网关IP),当心跳线完全断开时,2个节点都各自ping一下参考IP,不通则表明断点就出在本端。不仅“心跳”、还兼对外“服务”的本端网络链路断了,即使启动(或继续)应用服务也没有用了,那就主动放弃竞争,让能够ping通参考IP的一端去起服务。更保险一些,ping不通参考IP的一方干脆就自我重启,以彻底释放有可能还占用着的那些共享资源。
常见的解决方案
在实际生产环境中,我们可以从以下几个方面来防止裂脑问题的发生:
同时使用串行电缆和以太网电缆连接,同时用两条心跳线路,这样一条线路坏了,另一个还是好的,依然能传送心跳消息。
当检测到裂脑时强行关闭一个心跳节点(这个功能需特殊设备支持,如Stonith、feyce)。相当于备节点接收不到心跳消患,通过单独的线路发送关机命令关闭主节点的电源。
做好对裂脑的监控报警(如邮件及手机短信等或值班).在问题发生时人为第一时间介入仲裁,降低损失。例如,百度的监控报警短倍就有上行和下行的区别。报警消息发送到管理员手机上,管理员可以通过手机回复对应数字或简单的字符串操作返回给服务器.让服务器根据指令自动处理相应故障,这样解决故障的时间更短.
当然,在实施高可用方案时,要根据业务实际需求确定是否能容忍这样的损失。对于一般的网站常规业务.这个损失是可容忍的。
参考: https://blog.csdn.net/varyall/article/details/80427606
37.1 zookeeper脑裂问题
ZAB为解决脑裂问题,要求集群内的节点数量为2N+1, 当网络分裂后,始终有一个集群的节点数量过半数,而另一个节点数量小于N+1, 因为选主需要过半数节点同意,所以任何情况下集群中都不可能出现大于一个leader的情况。
参考: https://my.oschina.net/tantexian/blog/2876309
https://blog.csdn.net/yjp198713/article/details/79400927
38.3PC与2PC
3PC:
参考: https://blog.csdn.net/xj15010735572/article/details/86233456
https://www.cnblogs.com/dream-of-cambridge/articles/8074906.html
2PC:
参考: https://www.cnblogs.com/yuzhengzhong/p/9748082.html
39.各种推送方式
40.SPI
SPI可以破坏双亲委派机制。
41.Lock与Synchronize的区别
以上第2条有误,synchronize会造成死锁。
42.MySQL分库分表
42.1 sharding-jdbc与mycat的区别
43.抢红包设计思路
44.分布式延迟任务
实现方案一:
DelayQueue
实现方案三:
RabbitMQ消息队列
45.计算机十大经典算法
46.redis性能瓶颈
47.并发安全问题解决方案
单节点 并发安全问题解决方案:1:加锁,2:做CAS原子包,3:JDK阻塞队列 消费端做幂等, 4:数据库 乐观锁或唯一索引来来抗 多节点 并发安全问题解决方案 ,1:分布式锁(redis<lua脚本+setNX+超时时间>,zk(观察者模式+创建临时节点)) 2:中间件MQ来保证有序(rocketmq,kafka,rabbitmq等),3:数据库唯一索引(量级大 分库分表 基于全局唯一业务字段 做hash路由)
48.数据唯一ID方案:UUID和MySQL数据库步长自增的优缺点
一、UUID方案:
二、MySQL固定步长自增:
比如固定步长100,对大表分成99张子表(1-99)
三、雪花算法:
四、Redis对id自增incr(id):
此方案性能不及雪算法,但能保证id不会重复。
五、各方案对比:
还有美团的Leaf(基于snowflake)、百度的UUIDGenerator、MongoDB的ObjectId
49.BlockingQueue
50.并发中保持可见性的方式
51.Java异常体系
51.1 运行时异常
NullPointerException 、ClassNotFoundException 、ArrayIndexOutOfBoundsException 、NoSuchMethodError 、
IndexOutOfBoundsException 、NumberFormatException 、SQLException 、IllegalArgumentException 、ClassCastException、
52.柔性事务流程-基于MQ
53.JVM调优核心步骤
GC分析工具:
GC在线分析GcEasy:gceasy.io
GCviewer:gcviewer.jar
java visualVM:jvisualvm
调优参考:https://blog.csdn.net/missA_fei/article/details/88555428
配合这个https://www.cnblogs.com/aspirant/p/8662690.html
https://blog.csdn.net/high2011/article/details/80177473
53.1 GC 选择
53.2 减少GC频率
53.3 垃圾回收器
jdk1.8默认的是:Parallel Scavenge + Parallel Old
另外参考: https://blog.csdn.net/missA_fei/article/details/88555428
53.4 JVM配置参数-X与-XX的区别
配置 参数 | 类型 | 说明 | 举例 |
---|---|---|---|
-X | non-standard | 非标准参数。 这些参数不是虚拟机规范规定的。因此,不是所有VM的实现(如:HotSpot,JRockit,J9等)都支持这些配置参数。 |
-Xmx、-Xms、-Xmn、-Xss |
-XX | not-stable | 不稳定参数。 这些参数是虚拟机规范中规定的。这些参数指定虚拟机实例在运行时的各种行为,从而对虚拟机的运行时性能有很大影响。 |
-XX:SurvivorRatio、-XX:+UseParNewGc |
补充: -X和-XX两种参数都可能随着JDK版本的变更而发生变化,有些参数可以能会被废弃掉,有些参数的功能会发生改变,但是JDK官方不会通知开发者这些变化,需要使用者注意。
-XX参数被称为不稳定参数,是因为这类参数的设置会引起JVM运行时性能上的差异,配置得当可以提高JVM性能,配置不当则会使JVM出现各种问题, 甚至造成JVM崩溃。
对于-XX类型的配置选项,虚拟机规范有一些惯例,针对不同的平台虚拟机也会提供不同的默认值。
- 对于布尔(Boolean)类型的配置选项,通过
-XX:+<option>
来开启,通过-XX:-<option>
来关闭。 - 对于数字(Numberic)类型的配置选项,通过
-XX:<option>=<number>
来配置。<number>
后面可以携带单位字母,比如: ‘k’或者’K’代表千字节,’m’或者’M’代表兆字节,’g’或者’G’代表千兆字节。 - 对于字符串(String)类型的配置选项,通过
-XX:<option>=<string>
来配置。这种配置通过用来指定文件,路径或者命令列表。
1.设置堆的最大和最小值 -Xmx20M(最大值) ,-Xms20M(最小值)。
2.设置初始堆内存大小
-Xms等同于 -XX:InitialHeapSize;
-Xmx等同于 -XX:MaxHeapSize,如果我们自己不设置的话 -Xms默认为系统内存的1/64,-Xmx默认为系统内存的1/4。
-Xms 此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
3.设置年轻代的大小 -Xmn2g:设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
4.设置栈的大小 -Xss128k: 设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内 存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
5.设置出现内存溢出时,内存快照的保存路径 -XX:+HeapDumpOnOutOfMemoryError 该配置会把快照保存在用户目录或者tomcat目录下,也可以通过 -XX:HeapDumpPath=/tmp/heapdump.hprof 来显示指定路径
6. -verbose:gc -verbose:gc 中参数-verbose:gc 表示输出虚拟机中GC的详细情况.
-verbose:gc 是 稳定版本 -XX:+PrintGC 是 非稳定版本
7.设置年轻代与老年代的比例
-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
8.设置年轻代中Eden区与Survivor区的比例
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
9.设置持久代大小
-XX:MaxPermSize=16m:设置持久代大小为16m。
10.设置垃圾最大年龄
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加在年轻代即被回收的概率。
参考:https://www.cnblogs.com/aspirant/p/8662690.html
https://blog.csdn.net/missA_fei/article/details/88555428
53.5 可能导致Full GC的原因
Tenured被写满
Perm域被写满
System.gc()被显示调用
上一次GC之后Heap的各域分配策略动态变化
53.6 GC类型
GC有两种类型:Scavenge GC和Full GC。
Scavenge GC
一般情况下,当新对象生成,并且在Eden申请空间失败时,就好触发Scavenge GC,堆Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。
Full GC
对整个堆进行整理,包括Young、Tenured和Perm。Perm(持久代)大小通过-XX:MaxPermSize=进行设置。
53.7 CMS与G1
CMS:
使用场景:
GC过程短暂停,适合对时延要求较高的服务,用户线程不允许长时间的停顿。
缺点:
服务长时间运行,造成严重的内存碎片化。 另外,算法实现比较复杂(如果也算缺点的话)
触发条件
- 如果没有设置 UseCMSInitiatingOccupancyOnly,虚拟机会根据收集的数据决定是否触发(线上环境建议带上这个参数,不然会加大问题排查的难度)
- 老年代使用率达到阈值 CMSInitiatingOccupancyFraction,默认92%
- 永久代的使用率达到阈值 CMSInitiatingPermOccupancyFraction,默认92%,前提是开启 CMSClassUnloadingEnabled
- 新生代的晋升担保失败
G1:
使用场景:
Garbage-First(G1,垃圾优先)收集器是服务类型的收集器,目标是多处理器机器、大内存机器。它高度符合垃圾收集暂停时间的目标,同时实现高吞吐量。
G1垃圾回集器为以下应用设计:
- 类似CMS收集器,可以和应用线程同时并发的执行
- 压缩空闲空间时没有GC引起的暂停时间
- 需要更可预言的GC暂停时间
- 不想牺牲大量的吞吐量性能
- 不需要特别大的Java堆
G1回收的第4步,它是“选择一些内存块”,而不是整代内存来回收,这是G1跟其它GC非常不同的一点,其它GC每次回收都会回收整个Generation的内存(Eden, Old), 而回收内存所需的时间就取决于内存的大小,以及实际垃圾的多少,所以垃圾回收时间是不可控的;而G1每次并不会回收整代内存,到底回收多少内存就看用户配置的暂停时间,配置的时间短就少回收点,配置的时间长就多回收点,伸缩自如。
G1和CMS比较,有一些不同点让G1成为一个更好的解决方案。一个不同点是G1是一个压缩收集器。G1收集器充分地压缩空间以完全避免为分配空间使用细粒度的空闲列表,而不是依赖于区块。这相当简化了收集器的部件,和尽量消除可能的碎片问题。同时,G1收集器相比CMS收集器而方言,提供更可预言的垃圾收集暂停时间,允许用户指定想要暂停时间指标。
G1收集器知道哪个区域基本上是空的。它首先会收集那些产出大量空闲空间的区域。这就是为什么这个垃圾收集的方法叫做垃圾优先的原因。G1收集器集中它的收集和压缩活动在堆里的那些可完全被回收的区域,那就是垃圾。G1收集器使用一个暂停预言的模式去达到一个用户定义的暂停时间指标,基于用户指定的暂停时间指标去选择收集区域的数量。
https://www.cnblogs.com/aspirant/p/8663911.html
https://www.cnblogs.com/aspirant/p/8663872.html
https://www.cnblogs.com/aspirant/p/8663897.html
53.7.1 CMS与G1特征、对比区别
https://blog.csdn.net/xzp_12345/article/details/81839026
https://blog.csdn.net/qq_25396633/article/details/72972008
https://blog.csdn.net/Fly_as_tadpole/article/details/85047616
54.Nginx配置反向代理、负载均衡、缓存、动静分离、限流
54.1反向代理、负载均衡
upstream是上游服务器的意思
54.2 缓存
参考: https://www.cnblogs.com/pyng/p/10395310.html
A、proxy_cache_path
格式:proxy_cache_path path [levels=numbers] keys_zone=zone_name:zone_size[inactive=time] [max_size=size]
说明:
path -缓存文件存放的位置
levels -缓存目录结构,可以是1、2、3位数字作为目录,最多是3位数字如:1,1:2
keys_zone -指定缓存池名字及大小,每个定义缓存路径必须不同
inactive -设置每个缓存区缓存文件的有效时长,超过该时长没被访问的缓存被删除
max_size -设置不活动的缓存大小,不活动的缓存超过该大小后被删除
B、proxy_cache
格式:
proxy_cache cache_name
说明:
指定缓存区域的名字,一个相同的区域可以在不同的地方使用。
C、proxy_cache_valid
格式:
proxy_cache_valid reply_code [reply code… | any] time; |
说明:
reply_code -不同的应答代码
time -为不同应答设置不同缓存时长 默认为分钟m
any - 代表任何代码
54.3 动静分离
54.4 限流
1.ngx_http_limit_conn_module 模块限流
在nginx_conf的http{}中加上如下配置实现限制:
#限制每个用户的并发连接数,取名one
limit_conn_zone $binary_remote_addr zone=one:10m;
#配置记录被限流后的日志级别,默认error级别
limit_conn_log_level error;
#配置被限流后返回的状态码,默认返回503
limit_conn_status 503;
然后在server{}里加上如下代码:
#限制用户并发连接数为1
limit_conn one 1;
然后我们是使用ab测试来模拟并发请求: ab -n 5 -c 5 http://10.23.22.239/index.html
得到下面的结果,很明显并发被限制住了,超过阈值的都显示503:
另外刚才是配置针对单个IP的并发限制,还是可以针对域名进行并发限制,配置和客户端IP类似。
#http{}段配置
limit_conn_zone $ server_name zone=perserver:10m;
#server{}段配置
limit_conn perserver 1;
2.ngx_http_limit_req_module限流
使用ab测试模拟客户端连续访问10次:ab -n 10 -c 10 http://10.23.22.239/index.html
如下图,设置了通的个数为5个。一共10个请求,第一个请求马上被处理。第2-6个被存放在桶中。
55.有几种方式把web项目部署到tomcat中
56.Tomcat原理及底层源码
tomcat接受请求:
请求流程:
57.SpringAOP源码面试题
59.1 AOP原理
https://www.jianshu.com/p/09b3375cc3d8
https://www.jianshu.com/p/b6d3e5a0c7e2
https://www.cnblogs.com/liuyang-93/p/12617220.html
59.2 动态代理的原理
https://www.cnblogs.com/gonjan-blog/p/6685611.html
60.各重点知识面试快速复习回火
1.SpringAOP源码:
B站图灵司马老师视频课程:
https://www.bilibili.com/video/av64845791?p=6
2.Tomcat原理及源码:
B站传播智客视频课程:
https://www.bilibili.com/video/av75303570?t=1
3.JVM调优:
4.设计模式:
网易课堂尚硅谷韩顺平视频课程:
https://study.163.com/course/courseLearn.htm?courseId=1209569921#/learn/video?lessonId=1279941146&courseId=1209569921
61.缓存穿透、缓存击穿、缓存雪崩及其解决方案
- 缓存穿透 缓存和数据库中都没有的数据,每次都会先查缓存再查数据库。
解决方案:
1.1. 如果db查询不到数据,保存空对象到缓存层,设置较短的失效时间,比如30秒。 1.2. 采用bloom filter保存缓存过的key,在访问请求到来时可以过滤掉不存在的key,防止这些请求到db层。
- 缓存击穿 缓存击穿一般是由于热点Key过期了,造成了大量请求走到DB。
解决方案:
2.1. 缓存数据永远不过期 2.2. 使用互斥锁,当缓存数据失效时,保证只有一个请求能够访问到数据库,并更新缓存,其他线程等待并重试;
- 缓存雪崩 多个key(此时的key可能就不是热点key了)同时失效,造成大量请求走到DB。
解决方案:
3.1. 每个key的失效时间在基础时间上再加上一个1~5分钟(根据业务而定)的随机值 3.2. 使用互斥锁,当缓存数据失效时,保证只有一个请求能够访问到数据库,并更新缓存,其他线程等待并重试;
62.Spring或JDK源码中各种设计模式的应用
享元模式:jdk中的String.class;各种池技术;Integer.valueOf()方法如果值在-128~127之间;
参考:https://ispotu.blog.csdn.net/article/details/106721774
63.session复制与session共享
## 64.Redis为什么是高速缓存:
(1)操作内存;
(2)单线程
(3)I/O多路复用
(4)resp协议简单
64.1 为啥redis单线程模型也能效率这么高?
1)纯内存操作 2)核心是基于非阻塞的IO多路复用机制 3)单线程反而避免了多线程的频繁上下文切换问题
65.JMM内存模型原子操作
66.Netty面试
67.面试简历经验
68.J2EE技术
J2EE的13种核心技术:JDBC, JNDI, EJBS, RMI, JSP, JAVA SERVLETS, XML, JMS, JAVA IDL, JTS, JTA, JAVA MAIL 和 JAF 。
JNDI:(Java Naming and Directory Interface,Java命名和目录接口)是SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互。
EJBS:是的Enterprise Java Beans技术的简称, 又被称为企业Java Beans。 EJB (Enterprise Java Beans) 是基于分布式事务处理的企业级应用程序的组件。
EJB容器可以接受三类EJB
-
会话Bean(Session Beans)
-
- 无状态会话Bean(Stateless Session Beans)
- 有状态会话Bean(Stateful Session Beans)
-
实体Bean(Entity Beans)
- 消息驱动Bean(Message Driven Beans ,MDBs)
RMI(Remote Method Invocation,远程方法调用) 。 RMI使用Java语言接口定义了远程对象,它集合了Java序列化和Java远程方法协议(Java Remote Method Protocol)。 Java RMI 则支持存储于不同地址空间的程序级对象之间彼此进行通信,实现远程对象之间的无缝远程调用。 它可以被看作是RPC的Java版本。
**JMS(Java Message Service) ** MS是用于和面向消息的中间件相互通信的应用程序接口(API)。它既支持点对点的域,有支持发布/订阅(publish/subscribe)类型的域,并且提供对下列类型的支持:经认可的消息传递,事务型消息的传递,一致性消息和具有持久性的订阅者支持。
Java IDL/CORBA 在Java IDL的支持下,开发人员可以将Java和CORBA集成在一起。他们可以创建Java对象并使之可在CORBA ORB中展开, 或者他们还可以创建Java类并作为和其它ORB一起展开的CORBA对象的客户。后一种方法提供了另外一种途径,通过它Java可以被用于将你的新的应用和旧的系统相集成。
**JTA(Java Transaction Architecture) ** JTA定义了一种标准的API,应用系统由此可以访问各种事务监控。 **JTS(Java Transaction Service): ** JTS是CORBA OTS事务监控的基本的实现。JTS规定了事务管理器的实现方式。该事务管理器是在高层支持Java Transaction API (JTA)规范,并且在较底层实现OMG OTS specification的Java映像。JTS事务管理器为应用服务器、资源管理器、独立的应用以及通信资源管理器提供了事务服务。
JavaMail ** JavaMail是用于存取邮件服务器的API,它提供了一套邮件服务器的抽象类。不仅支持SMTP服务器,也支持IMAP服务器。 JAF(JavaBeans Activation Framework) ** JavaMail利用JAF来处理MIME编码的邮件附件。MIME的字节流可以被转换成Java对象,或者转换自Java对象。大多数应用都可以不需要直接使用JAF。
参考: https://baike.baidu.com/item/J2EE%E6%8A%80%E6%9C%AF/655253?fr=aladdin
69.打破双亲委派
重写ClassLoader的loadClass方法:
70. volatile
volatile写底层实现
JMM对volatile的内存屏障插入策略
在每个volatile写操作的前面插入一个StoreStore屏障。在每个volatile写操作的后面插入一个StoreLoad屏障。
volatile 读底层
JMM对volatile的内存屏障插入策略
在每个volatile读操作的后面插入一个LoadLoad屏障。在每个volatile读操作的后面插入一个LoadStore屏障。
71.本地缓存与分布式缓存对比
72.mysql慢查询
慢查询mysql记录位置:
Linux:/etc/my.conf
Windows:mysql安装目录/my.ini
73.二叉树、平衡二叉树、B树、红黑树
二叉树(Binary tree): 二叉树是每个结点最多有两个子树的树结构 。 二叉树常被用于实现二叉查找树和二叉堆。 特点是每个结点最多只能有两棵子树,且有左右之分。
二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树。在一般情况下,查询效率比链表结构要高。性质:左子树所有节点的值 < 根节点 < 右子树所有节点的值。左、右子树也分别为二叉排序树。
平衡树(Balance Tree,BT) 指的是,任意节点的子树的高度差都小于等于1。常见的符合平衡树的有B树(多路平衡搜索树)、AVL树(二叉平衡搜索树)等。
平衡二叉树:(Balanced Binary Tree),即 AVL树 ,具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
满二叉树: 除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。 一棵深度为k,且有2^k-1个结点的二叉树。这种树的特点是每一层上的结点数都是最大结点数。 除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树。满二叉树的各个层的结点数形成一个首项为1,公比为2的等比数列。
完全二叉树:若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布 。在一棵二叉树中,除最后一层外,若其余层都是满的,并且或者最后一层是满的,或者是在右边缺少连续若干结点,则此二叉树为完全二叉树。 叶子结点只可能在最大的两层出现。
AVL 树:是一种自平衡二叉查找树,是一棵二叉搜索树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。
红黑树: 红黑树 (Red Black Tree) 是一种自平衡二叉查找树 。 平衡二叉B树 。
红黑树是一种平衡二叉查找树的变体,它的左右子树高差有可能大于 1,所以红黑树不是严格意义上的平衡二叉树(AVL),但 对之进行平衡的代价较低, 其平均统计性能要强于 AVL 。红黑树是一种特化的AVL树(平衡二叉树)。
B树: B树和平衡二叉树稍有不同的是B树属于多叉树又名平衡多路查找树(查找路径不只两个) 。 一种适用于外查找的树,它是一种平衡的多叉树 。所有的叶子结点都位于同一层。
B+树: B+树是B树的一个升级版,相对于B树来说B+树更充分的利用了节点的空间,让查询速度更加稳定,其速度完全接近于二分法查找。
B*树: B树是B+树的变种 。
参考: https://zhuanlan.zhihu.com/p/27700617
74.合理配置线程池数
CPU核心数N,
CPU计算密集型:N+1
I/O密集型:2N+1
corePoolSize 建议值为:每秒任务数*任务执行时间(例如0.5s) 【100 * 0.2=20】 maxPoolSize 建议和corePoolSize 配置一样、有同学建议直接设置为cpu数量+1
keepAliveTiime 设定值可根据任务峰值持续时间来设定。
工作队列:
①ArrayBlockingQueue:基于数组的有界阻塞队列,按FIFO排序
②LinkedBlockingQueue:基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按FIFO排序。此时参数maxPoolSize不起作用。
③SynchronousQueue:新任务进来时不会缓存,而是直接调度执行该任务,如果没有可用线程,则创建新线程,直到maxPoolSize。
④PriorityBlockingQueue:具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
threadFactory:用来设定线程名、是否为daemon线程等
拒绝策略:
①CallerRunsPolicy:在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
②AbortPolicy:直接丢弃任务,并抛出RejectedExecutionException异常。
③DiscardPolicy:直接丢弃任务,什么都不做。
④DiscardOldestPolicy:抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列。
https://blog.csdn.net/ye17186/article/details/89467919
75.1 PriorityQueue
PriorityQueue:默认初始容量为11,扩容为原容量的1.5倍.
PriorityQueue默认为小顶堆,底层数据结构是数组,其中数组是无序的,只有将PriorityQueue中元素依次取出后才是有序的.
该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
该策略下,直接丢弃任务,什么都不做。
该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
75.Hash结构的优缺点
76.java反射优劣势
77. 应对高并发的四把利器
78.Redis中的Hash键
79.负载均衡F5、HA、LVS、Nginx、Apache比较
80.MySQL优化层次
81.Java代码执行底层汇编代码查看
82.对象状态
1.无状态(刚刚new处理的)
2.偏向锁(一个线程在运行)
3.轻量锁(CAS AQS)
4.重量锁(synchronized)
5.GC标记(new出来没有后引用,即有堆空间占用但是没有栈空间引用)
83.AQS核心三板斧
-
自旋
-
LockSupport
-
CAS
84.RPC核心技术流程
85.HashMap为什么不用B+ tree而用红黑树?
hashmap用的是数组+红黑树 。
因为红黑树需要进行左旋,右旋操作, 而单链表不需要, 以下都是单链表与红黑树结构对比。 如果元素小于8个,查询成本高,新增成本低 如果元素大于8个,查询成本低,新增成本高
当个数不多的时候,直接链表遍历更方便,实现起来也简单。而红黑树的实现要复杂的多。
参考: https://blog.csdn.net/Fly_as_tadpole/article/details/88169841
85.1 为什么HashMap使用红黑树而不使用AVL树
AVL树和红黑树有几点比较和区别: (1)AVL树是更加严格的平衡,因此可以提供更快的查找速度,一般读取查找密集型任务,适用AVL树。 (2)红黑树更适合于插入修改密集型任务。 (3)通常,AVL树的旋转比红黑树的旋转更加难以平衡和调试。
总结: (1)AVL以及红黑树是高度平衡的树数据结构。它们非常相似,真正的区别在于在任何添加/删除操作时完成的旋转操作次数。 (2)两种实现都缩放为a O(lg N),其中N是叶子的数量,但实际上AVL树在查找密集型任务上更快:利用更好的平衡,树遍历平均更短。另一方面,插入和删除方面,AVL树速度较慢:需要更高的旋转次数才能在修改时正确地重新平衡数据结构。 (3)在AVL树中,从根到任何叶子的最短路径和最长路径之间的差异最多为1。在红黑树中,差异可以是2倍。 (4)两个都给O(log n)查找,但平衡AVL树可能需要O(log n)旋转,而红黑树将需要最多两次旋转使其达到平衡(尽管可能需要检查O(log n)节点以确定旋转的位置)。旋转本身是O(1)操作,因为你只是移动指针。
参考: https://blog.csdn.net/qq_41999455/article/details/95342982
https://blog.csdn.net/21aspnet/article/details/88939297
https://blog.csdn.net/zyywolf/article/details/101363793
https://www.jianshu.com/p/37436ed14cc6
https://zhuanlan.zhihu.com/p/27700617
85.2 ConcurrentHashMap在JDK1.7和JDK1.8版本的区别
JDK1.8版本的ConcurrentHashMap的数据结构已经接近HashMap,相对而言,ConcurrentHashMap只是增加了同步的操作来控制并发,从JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树。
1.数据结构:取消了Segment分段锁的数据结构,取而代之的是数组+链表+红黑树的结构。2.保证线程安全机制:JDK1.7采用segment的分段锁机制实现线程安全,其中segment继承自ReentrantLock。JDK1.8采用CAS+Synchronized保证线程安全。3.锁的粒度:原来是对需要进行数据操作的Segment加锁,现调整为对每个数组元素加锁(Node)。4.链表转化为红黑树:定位结点的hash算法简化会带来弊端,Hash冲突加剧,因此在链表节点数量大于8时,会将链表转化为红黑树进行存储。5.查询时间复杂度:从原来的遍历链表O(n),变成遍历红黑树O(logN)。
https://blog.csdn.net/qq_41884976/article/details/89532816
https://baijiahao.baidu.com/s?id=1617089947709260129&wfr=spider&for=pc
85.3 ConcurrentHashMap原理
参考:https://www.cnblogs.com/huangjuncong/p/9478505.html
86. park与unpark与wait的区别?
Object中的wait()和notify() 1、因为wait需释放锁,所以必须在synchronized中使用(没有锁时使用会抛出IllegalMonitorStateException) 2、notify也要在synchronized使用,并且应该指定对象 3、synchronized(),wait(),notify() 对象必须一致,一个synchronized()代码块中只能有1个线程wait()或notify()
LockSupport中的park() 和 unpark() 1、LockSupport中的park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程,而且park()和unpark()不会遇到“Thread.suspend 和 Thread.resume所可能引发的死锁”问题。 2、park和wait的区别。wait让线程阻塞前,必须通过synchronized获取同步锁。
区别 park函数是将当前调用Thread阻塞,而unpark函数则是将指定线程Thread唤醒。 与Object类的wait/notify机制相比,park/unpark有两个优点: 1.以thread为操作对象更符合阻塞线程的直观定义 2.操作更精准,可以准确地唤醒某一个线程。 区别是:notify随机唤醒一个线程,notifyAll唤醒所有等待的线程,增加了灵活性
LockSupport同步线程和wait/notify不一样,LockSupport并不需要获取对象的监视器,而是给线程一个“许可”(permit)。而permit只能是0个或者1个。unpark会给线程一个permit,而且最多是1;而park会消耗一个permit并返回,如果线程没有permit则会阻塞。
permit不能叠加,也就是说permit的个数要么是0,要么是1。也就是不管连续调用多少次unpark,permit也是1个。线程调用一次park就会消耗掉permit,再一次调用park又会阻塞住。
unpark可以先于park调用。也就是我们在使用park和unpark的时候可以不用担心park的时序问题造成死锁。 相比之下,wait/notify存在时序问题,wait必须在notify调用之前调用,否则虽然另一个线程调用了notify,但是由于在wait之前调用了,wait感知不到,就造成wait永远在阻塞。
LockSupport是通过调用Unsafe函数中的接口实现阻塞和解除阻塞的。 LockSupport.park() 的实现原理是通过二元信号量做的阻塞,要注意的是,这个信号量最多只能加到1。
参考: https://www.cnblogs.com/set-cookie/p/9582547.html
总结下 LockSupport的park/unpark和Object的wait/notify:
- 面向的对象不同;
- 跟Object的wait/notify不同LockSupport的park/unpark不需要获取对象的监视器;
- 实现的机制不同,因此两者没有交集。 也就是说LockSupport阻塞的线程,notify/notifyAll没法唤醒。
- 和wait方法不同,执行park进入休眠后并不会释放持有的锁。
虽然两者用法不同,但是有一点, LockSupport
的park和Object的wait一样也能响应中断.
87.枚举类实现单例的原因
当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的(因为虚拟机在加载枚举的类的时候,会使用ClassLoader的loadClass方法,而这个方法使用同步代码块保证了线程安全)。所以,创建一个enum类型是线程安全的。 也就是说,我们定义的一个枚举,在第一次被真正用到的时候,会被虚拟机加载并初始化,而这个初始化过程是线程安全的 。
在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum
的valueOf
方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject
、readObject
等方法。
普通的Java类的反序列化过程中,会通过反射调用类的默认构造函数来初始化对象。所以,即使单例中构造函数是私有的,也会被反射给破坏掉。由于反序列化后的对象是重新new出来的,所以这就破坏了单例。
但是,枚举的反序列化并不是通过反射实现的。所以,也就不会发生由于反序列化导致的单例破坏问题。
87.1枚举类型防止反序列化创建新对象原理
首先,在序列化和反序列化期间,任何特定于类的writeObject,readObject,readObjectNoData,writeReplace和readResolve方法都会被忽略。 同样,任何serialPersistentFields或serialVersionUID字段声明也会被忽略,所有枚举类型的fixedserialVersionUID都是0L。(也就是说枚举类型序列化反序列化机制与其他类型的不一样)。
其次,枚举对象的序列化、
反序列化有自己的一套机制。序列化时,仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf()方法来根据名字查找枚举对象。
参考: https://www.pianshen.com/article/221293400/
88.Kafka 重复消费、丢数据
1.Producer端
afka 作为一个可分区和可复制的消息队列。我们可以利用它的分区副本机制,为每一个 Topic 的数据分为多个分区,每个分区放在不同的节点上,每个分区默认只有一分数据,通过设置参数 –replication-factor 2 ,可以执行每个分区有三份数据,其中有两份是用来备份的。
分区有副本了,也会存在 leader 和 follower 的角色,zookeeper 中维护了一个 ISR 列表。
-
Consumer端
消费端数据丢失的原因是 offset 的自动提交。
由于在使用kafka的高级API时,消费者会自动每隔一段时间将offset保存到zookeeper上,此时如果刚好将偏移量提交到zookeeper上后,但这条数据还没消费完,机器发生宕机,此时数据就丢失了。
解决方法:关闭自动提交,改成手动提交,每次数据处理完后,再提交。
数据重复消费,在消费者自动提交offset到zookeeper后,程序又消费了几条数据,但是还没有到下次自动提交offset到zookeeper之时,如果机器宕机了,然后重启,此时消费者会去读zookeeper上的偏移量进行消费,这就会导致数据重复消费。解决方法:关闭自动提交,改成手动提交。
参考: https://cloud.tencent.com/developer/news/375381
88.1 kafka消息丢失情况与解决方案
1、Kafka消息丢失的情况:
(1)auto.commit.enable=true,消费端自动提交offersets设置为true,当消费者拉到消息之后,还没有处理完 commit interval 提交间隔就到了,提交了offersets。这时consummer又挂了,重启后,从下一个offersets开始消费,之前的消息丢失了。
(2)网络负载高、磁盘很忙,写入失败,又没有设置消息重试,导致数据丢失。
(3)磁盘坏了已落盘数据丢失。
(4)单 批 数 据 的 长 度 超 过 限 制 会 丢 失 数 据 , 报kafka.common.Mess3.ageSizeTooLargeException异常
2、Kafka避免消息丢失的解决方案:
(1)设置auto.commit.enable=false,每次处理完手动提交。确保消息真的被消费并处理完成。
(2)kafka 一定要配置上消息重试的机制,并且重试的时间间隔一定要长一些,默认 1 秒钟不符合生产环境(网络中断时间有可能超过 1秒)。
(3)配置多个副本,保证数据的完整性。
(4)合理设置flush间隔。kafka 的数据一开始就是存储在 PageCache 上的,定期 flush 到磁盘上的,也就是说,不是每个消息都被存储在磁盘了,如果出现断电或者机器故障等,PageCache 上的数据就丢。可以通过 log.flush.interval.messages 和 log.flush.interval.ms 来 4.配置 flush 间隔,interval大丢的数据多些,小会影响性能但在 0.本,可以通过 replica机制保证数据不丢,代价就是需要更多资源,尤其是磁盘资源,kafka 当前支持 GZip 和 Snappy压缩,来缓解这个问题 是否使用 replica 取决于在可靠性和资源代价之间的 balance。
89.各种消息系统对比
90.spring事务注解@Transaction源码
绿色的可回滚,红色的不能回滚。
91.spring源码中bean的生命周期
spring源码中bean的创建过程:
bean过程顺序:
91.1 @Autowired注解与@Resource注解的区别:
@Autowired
@Autowired是Spring 提供的,需导入
Package:org.springframework.beans.factory.annotation.Autowired;
只按照byType 注入。
@Resource
@Resource默认按 byName 自动注入,是J2EE提供的, 需导入Package:
javax.annotation.Resource;
@Resource有两个中重要的属性:name和type ,而Spring将@Resource注解的name属性解析为bean的
名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用
type属性时则使用 byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用by
Name自动注入策略。
使用区别 @Resource(name=”loginService”) private LoginService loginService;
@Autowired(required=false)@Qualifier(“loginService”) private LoginService loginService;
(1).@Autowired 与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上; (2).@Autowired 默认按类型装配,默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设
置它的required属性为false,如:@Autowired(required=false) .
如果我们想使用名称装配可以结合 @Qualifier注解进行使用;
(3).@Resource(这个注解属于J2EE的),默认安装名称进行装配,名称可以通过name属性进行指定,如果没 有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属 性名进行装配。当找不到与名称匹配的bean时才按照类型进行装 配。但是需要注意的是,如果name属性一旦指 定,就只会按照名称进行装配。
推荐使用@Resource注解在字段上,这样就不用写setter方法了.并且这个注解是属于J2EE的,减少了与Spring
的耦合,这样代码看起就比较优雅
参考:http://blog.sina.com.cn/s/blog_9075354e0101huup.html
@Autowired注解由AutowiredAnnotationBeanPostProcessor的后置处理器来解析的
@Resource注解由CommonAnnotationBeanPostProcessor来解析的
91.2 spring循环依赖
spring默认开启循环依赖,由AbstractAutowireCapableBeanFactory中的属性allowCircularReferences(默认为true)的来控制的
博客参考:https://blog.csdn.net/java_lyvee/article/details/101793774
创建Bean的过程中放入singletonCurrentlyInCreation的set集合中,
对象不一定是bean,bean一定是一个对象。
91.3.1 spring解决循环依赖的方法
(1)Spring容器创建”testA”对象时,首先去”当前创建bean池”查找是否当前bean正在创建,如果没有发现,则继续准备其需要的构造器参数”testB”,并将”testA”标识符放置到”当前创建bean池”。
(2)接下来程序将去第一级缓存singletonObjects中查找是否存在testB实例,如果不存在,则再去第二级缓存earlySingletonObjects中寻找,如果还是没有的话,那就去找到第三级缓存singletonFactories中对应testB的ObjectFactory
(3)然后调用该类的getObject方法来创建一个testB对象。
这个时候,Spring还进行其他操作:
将获得的testB对象添加到earlySingletonObjects中然后将singletonFactories中对应的ObjectFactory给remove掉,这样做的目的是什么呢?这样做了的话,如果存在下一次查找依赖并又走到第三级缓存,就能退出循环了,还有避免GC。
一级一级向下寻找,找出了前面提到的三级缓存,也就是三个Map集合类:
singletonObjects:第一级缓存,里面放置的是实例化好的单例对象;
earlySingletonObjects:第二级缓存,里面存放的是提前曝光的单例对象;
singletonFactories:第三级缓存,里面存放的是要被实例化的对象的对象工厂。
单例Bean之间的循环依赖的解决,Spring是通过三级缓存来实现的。
https://blog.csdn.net/Apeopl/article/details/90146337
- 在finishBeanFactoryInitialization中,开始初始化A,毋庸置疑通过反射
- 之后【非完美对象】开始设置属性字段,此时发现需要一个B的对象。同时已标记A处于正在初始化阶段
- 显然接下来,开始去初始化B的对象,同样的手法,到设置属性阶段,发现需要A对象
- 于是乎,spring又开始去初始化对象A的依赖,此时先从缓存singletonObjects去取,没有再去看是否正处于初始阶段,是则再从缓存earlySingletonObjects中取,再没有,则看是否存在allowEarlyReference,是则从singletonFactories中取
- 将早期对象A设置到B中,再把B设置到A中
https://blog.csdn.net/chaitoudaren/article/details/104833575
91.3 关闭循环依赖
1.修改spring源码:
在AnnotationConfigApplicationContext中关闭:
setAllowCircularReferences(false);
会在AbstractAutowireCapableBeanFactory中设置属性allowCircularReferences为false
2.代码中关闭循环依赖:
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.register(Appconfig.class);
AbstractAutowireCapableBeanFactory beanFactory = (AbstractAutowireCapableBeanFactory)ac.getBeanFactory();
beanfactory.setAllowCircularReferences(false);
ac.refresh();
3.扩展spring来关闭循环依赖
91.4 spring生命周期初始化回调使用
方法1:
实现InitializingBean接口,重写其afterPropertiesSet()
方法2:
在xml里的
方法3:
在方法上使用注解@PostConstruct
以上三种配置方法可共存,如果共存时执行顺序:
@PostConstruct —> 实现InitializingBean接口的afterPropertiesSet() —> xml里配置的init-method
原理:
spring源码中AbstractAutowireCapableBeanFactory中代码执行顺序:
91.5 三级缓存
一级缓存singletonObjects是单例池,单例对象只会实例化一次,所以需要单例池来缓存,原型prototype就不需要这个缓存;
二级缓存singletonFactories 缓存的是一个工厂,主要为了解决循环依赖;
三级缓存earlySingletonObjects解决性能问题。
工厂模式和策略模式完成spring的循环依赖。
91.6 spring ioc容器初始化在什么阶段?
IoC容器初始化的入口是在构造方法中调用refresh()开始的。
通过ResourceLoader来完成资源文件位置的定位,DefaultResourceLoader是默认的实现,同时上下文本身就给出了ResourceLoader的实现。
创建的IoC容器是DefaultListableBeanFactory。
IoC容器对Bean的管理和依赖注入功能的实现是通过对其持有的BeanDefinition进行相关操作来完成的。
https://blog.csdn.net/qq_39632561/article/details/83070140
https://www.cnblogs.com/myadmin/p/5838795.html
https://www.cnblogs.com/wxd0108/p/5517470.html
https://www.cnblogs.com/lcj12121/p/11497128.html
91.7 spring启动过程
https://www.cnblogs.com/luoluoshidafu/p/6442055.html
91.8 spring容器启动的三种方式
https://www.cnblogs.com/duanxz/p/5074584.html
92.tomcat部署应用有几种方式
有三种:
第一种:将war包部署在tomcat的webapps目录下
第二种:在tomcat的server.xml中配置context:
Context表示应用节点,其中path是应用名,docBase是应用的目录。
Context就是servlet容器。
范围大—>小:
Engine > Host > Context > Wrapper > Servlet
Host是虚拟主机,可配置多个:
Host的主机名name和appBase可自定义。
第三种:将项目打成war包解压后的文件夹,放到webapps下
如上生成的ServletDemo文件夹复制到webapps目录下即可。
第四种:自定义xml文件描述符来定义项目路径:
在tomcat的Catalina目录中指定Host节点目录如localhost目录下自定义xml了配置context,指明项目路径。
注意:xml文件名,必须与context里的path同名!
93 tomcat 处理请求的流程
tomcat架构图:
94.MySQL索引失效的情况
(1)强制类型转换(隐式类型转换)
(2)在查询字段使用表达式
(3)范围查找后的索引字段会失效
(4)不满足最左前缀原则:如like “%dd”
不走索引: (1)like ‘%abc’ 或者 like‘%abc%’,即不符合最左前缀 (2)条件中带有表达式,比如where num/2=100 或者 substring(a,1,3)=’ab’或者age+10=30 (3)where条件中有不等于,where id !=2 或者 where id <> 2 (4)not in 单列索引a ,where a not in (xxxxx) ,不管里面是一个还是多个参数都用不到a的索引 not in not exist in 尽量转换为union (5)where条件是null:where name is null (6)in(2个及以上参数) 单列索引a,where a in (xxx) ,如果xxx参数是1个会用到索引,如果参数是2个及以上不会用到索引。 (7)类型转换:字符类型的字段与数字比较(数字类型的字段与字符比较会用到索引) (8)or部分失效情况:如果or两侧部分列是组合索引,则失效。即只能将or条件中的每个列都加上索引。 即用or分隔开的条件,如果or前的条件中的列有索引,而后面的列没有索引,那么涉及到的索引都不会被用到 (9)a in (w) and c in (x,y,…) 用到 a的索引,c的索引用不到 (10)复合索引a-b-c部分用不到,即不符合最左前缀: b用不到, c用不到, b and c用不到, c and b用不到, (11)多表join时,只有在主键和外键的数据类型相同时才能使用索引,否则即使建立了索引也不会使用 (12)如果某个数据列里包含着许多重复的值 (13)如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引 (14)果mysql估计使用全表扫描要比使用索引快,则不使用索引 (15)where条件中不使用“=”进行索引列,那么不会用到索引 (16)没有查询条件 (17)time 和date 时间格式不一致 (18)B-tree索引is null不会走,is not null会走,位图索引 is null,is not null 都会走
在MySQL中,有Handler_read_key和Handler_read_rnd_key两个变量,如果Handler_read_key值很高而Handler_read_rnd_key的值很低,则表明索引经常不被使用,应该重新考虑建立索引。可以通过:show status like ‘Handler_read%’来查看着连个参数的值。
https://baijiahao.baidu.com/s?id=1660574094916539960&wfr=spider&for=pc https://www.cnblogs.com/baiyi-ying/p/10036231.html https://www.cnblogs.com/gaoyuechen/p/8067450.html
94.1 mysql的sql性能分析:分析sql执行花费时间
第一步,开启sql耗时分析工具
第二步,执行SQL语句:
第三步,查看sql各步骤耗时:
(查刚才执行过的sql的性能)
(查各步骤的耗时)
或
如上图,sql执行中的Sending data这步耗时最长。
95.Redis慢日志
在Redis中,关于慢查询有两个设置–慢查询最大超时时间和慢查询最大日志数。
- 可以通过修改配置文件或者直接在交互模式下输入以下命令来设置慢查询的时间限制,当超过这个时间,查询的记录就会加入到日志文件中。
CONFIG SET slowlog-log-slower-than num
设置超过多少微妙的查询为慢查询,并且将这些慢查询加入到日志文件中,num的单位为毫秒,windows下redis的默认慢查询时10000微妙即10毫秒。
- 可以通过设置最大数量限制日志中保存的慢查询日志的数量,此设置在交互模式下的命令如下:
CONFIG SET slowlog-max-len num
设置日志的最大数量,num无单位值,windows下redis默认慢查询日志的记录数量为128条。
命令的解析:
CONFIG 命令会使redis客户端自行去寻找redis的.conf 配置文件,找到对应的配置项进行修改。
参考:http://redisdoc.com/debug/slowlog.html?highlight=slowlog
951.redis各种数据类型直观对比图
96.Lock及AQS原理
参考:https://www.cnblogs.com/duanxz/p/3559510.html
https://www.jianshu.com/p/02184c3ab5e4
https://www.cnblogs.com/shoshana-kong/p/10772679.html
97.sychronized原理
参考:https://blog.csdn.net/zbuger/article/details/51030772
https://www.cnblogs.com/heqiyoujing/p/11144649.html
synchronized同步块使用了monitorenter和monitorexit指令实现同步,这两个指令,本质上都是对一个对象的监视器(monitor)进行获取,这个过程是排他的,也就是说同一时刻只能有一个线程获取到由synchronized所保护对象的监视器。
线程执行到monitorenter指令时,会尝试获取对象所对应的monitor所有权,也就是尝试获取对象的锁,而执行monitorexit,就是释放monitor的所有权。
97.1 synchronized特性
-
可见性
-
有序性
-
原理性
-
可重入性
98.java中两种代理方式jdk动态代理和CGLib代理的区别
优缺点 JDK实现方式产生的代理类是接口的实现,也就是说serviceProxy是可以赋值给IService的,但是不能赋值给ServiceImpl。对应Cglib则使用的继承机制,具体说被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口。 JDK代理只能对接口进行代理,Cglib则是对实现类进行代理。 Cglib采用的是继承,所以不能对final修饰的类进行代理。 JDK采用反射机制调用委托类的方法,Cglib采用类似索引的方式直接调用委托类方法; 从 jdk6 到 jdk7、jdk8 ,动态代理的性能得到了显著的提升,与cglib的性能上已经差别不大
参考:https://blog.csdn.net/github_39433650/article/details/90648941
99.队列Queue的常用方法区别
100.线程的参数、拒绝策略
当池中线程数大于coolPoolSize,超过keepAliveTime时间的闲置线程会被回收掉。回收的是非核心线程,核心线程一般是不会回收的。如果设置allowCoreThreadTimeOut(true),则核心线程在闲置keepAliveTime时间后也会被回收。
任务队列是一个阻塞队列,线程执行完任务后会去队列取任务来执行,如果队列为空,线程就会阻塞,直到取到任务。
参考:https://www.cnblogs.com/gaopengpy/p/12149060.html
拒绝策略:https://www.jianshu.com/p/15d6244bf340
101.SpringCloud与Dubbo区别
(1)
SpringCloud | Dubbo | |
---|---|---|
CAP法则 | 主要满足的是A和P法则,即高可用和分区可用性 | 主要满足的是C和P法则,即强一致性和分区可用性 |
作用定位 | 服务调用的生态 | 服务调用的解决方案或工具 |
通信机制 | 基于Http协议+rest接口调用远程过程 | RPC远程过程调用。Dubbo缺省协议采用单一长连接和NIO异步通讯(Netty)。使用Netty这样的NIO框架,是基于TCP协议传输的,配合以Hession序列化完成RPC |
传输方式 | http协议传输,带宽会比较多,同时使用http协议一般会使用JSON报文,消耗会更大 | 二进制的传输,占用带宽会更少 |
开发难度 | 简单易用。只要整合Spring Cloud的子项目就可以顺利的完成各种组件的融合。 | 难度较大,原因是dubbo的jar包依赖问题很多大型工程无法解决.Dubbo缺需要通过实现各种Filter来做定制,开发成本以及技术难度略高。 |
注册中心 | 只能用eureka或者自研 | 可以选择zk,redis等多种 |
缺陷 | 带了很多监控、限流措施,但是功能可能和欧美习惯相同,国内需要进行适当改造,但更简单,就是ServletFilter而已 | 通过dubbofilter,很多东西没有,需要自己继承,如监控,如日志,如限流,如追踪。Dubbo框架内部就必须要组建一个维护团队。 |
优点 | 简单易用。满足了构建微服务所需的所有解决方案。能减少已有项目的迁移成本。 | dubbo支持各种通信协议,而且消费方和服务方使用长链接方式交互,通信速度上略胜Spring Cloud,如果对于系统的响应时间有严格要求,长链接更合适。 |
全面评价 | 往“体系”方向发展的方案 | 仅是个工具而已 |
源码风格 | 大道至简,纯粹,干净利落 | 炫技,绕来绕去,繁杂 |
适用场景 | 适用于 中小企业 | 适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。同步调用场景多并且能支撑搭建Dubbo 这套比较复杂环境的成本的产品。但如果产品业务中由于后台业务逻辑复杂、时间长而导致异步逻辑比较多的话,可能Dubbo 并不合适。同时,对于人手不足的初创产品而言,这么重的架构维护起来也不是很方便。 |
服务更新机制 | 每次更新仅同步增量数据,也就是更新的数据。 | 每次更新都同步全量数据 |
服务更新实时性 | 其他Client只有在拉取服务增量信息时才会感知到某个服务的更新,延时最大为30S,也就是拉取周期。 | Zookeeper会立即反馈订阅的Client,实时性很高。 |
节点性质 | Eureka不区分Consumer或者Provider,两者都统称为Client,一个Client内可能同时含有Provider,Consumer,通过服务发现组件获取的是其他所有的Client节点信息,在调用时根据应用名称来筛选节点 | Dubbo只有Consumer订阅Provider节点,也就是Consumer发现Provider节点信息 |
信息同步 | EurekaServer节点间的服务信息同步是基于异步Http实现的。每隔Server节点在接收Client的服务请求时,立即处理请求,然后将此次请求的信息拷贝,封装成一个Task,存入Queue中。Server初始化时会启动一个线程定期的从TaskQueue中批量提取Task,然后执行。服务同步不保证一定成功,虽然有失败重试,但超过一定时限后就放弃同步。 | 使用zookeeper同步机制:Client的每一个事务操作都由Leader广播给所有Follower,当超过半数的Follower都返回执行成功后,才执行事务的ack。 |
注册服务 | 基于Http协议来实现的,Provider对外暴露的是应用信息,比如应用名称,ip地址等等,Consumer发现的是应用的信息,当调用的时候随机选择一个Provider的IP地址,应用名称,然后依据Http协议发送请求。Consumer关注的是应用名称,根据应用名称来决定调用的是哪个服务集群. | 基于java接口及Hession2或Kyro序列化的来实现传输的.Provider对外暴露接口,Consumer根据接口的规则调用。也就是Provider向Zookeeper注册的是接口信息,Consumer从Zookeeper发现的是接口的信息,通过接口的name,group,version来匹配调用。 |
服务依赖 | 通过Json交互,省略了版本管理的问题,但是具体字段含义需要统一管理,自身Rest API方式交互,为跨平台调用奠定了基础。 | Dubbo服务依赖略重,需要有完善的版本管理机制,但是程序入侵少 |
Dubbo | Spring Cloud | |
---|---|---|
服务注册中心 | Zookeeper | Spring Cloud Netflix Eureka |
服务调用方式 | RPC | REST API |
服务网关 | 无 | Spring Cloud Netflix Zuul |
断路器 | 不完善 | Spring Cloud Netflix Hystrix |
分布式配置 | 无 | Spring Cloud Config |
服务跟踪 | 无 | Spring Cloud Sleuth |
消息总线 | 无 | Spring Cloud Bus |
数据流 | 无 | Spring Cloud Stream |
批量任务 | 无 | Spring Cloud Task |
Dubbo对于上表中总结为“无”的组件不代表不能实现,而只是Dubbo框架自身不提供,需要另外整合以实现对应的功能,比如:
- 分布式配置:可以使用淘宝的diamond、百度的disconf来实现分布式配置管理。但是Spring Cloud中的Config组件除了提供配置管理之外,由于其存储可以使用git,因此它天然的实现了配置内容的版本管理,可以完美的与应用版本管理整合起来。
- 服务跟踪:可以使用京东开源的Hydra或者扩展Filter用Zippin来做服务跟踪
- 批量任务:可以使用当当开源的Elastic-Job、tbschedule
Dubbo自身只是实现了服务治理的基础,其他为保证集群安全、可维护、可测试等特性方面都没有很好的实现,但是几乎大部分关键组件都能找到第三方开源来实现,这些组件主要来自于国内各家大型互联网企业的开源产品。
SpringCloud是一个生态,而Dubbo是SpringCloud生态中关于服务调用一种解决方案或工具。
springcloud的接口协议约定比较自由且松散,需要有强有力的行政措施来限制接口无序升级。微服务之间通过 Feign 进行通信处理业务。
Dubbo确实类似于Spring Cloud的一个子集。
其实相比于Dubbo,Spring Cloud可以说是一个更完备的微服务解决方案,它从功能性上是Dubbo的一个超集,个人认为从选型上对于一些中小型企业Spring Cloud可能是一个更好的选择。
Spring Cloud也并不是和http+JSON强制绑定的,如有必要Thrift、protobuf等高效的RPC、序列化协议同样可以作为替代方案。
从整个大的平台架构来讲,dubbo框架只是专注于服务之间的治理,如果我们需要使用配置中心、分布式跟踪这些内容都需要自己去集成,这样无形中使用dubbo的难度就会增加。Spring Cloud几乎考虑了服务治理的方方面面,更有Spring Boot这个大将的支持,开发起来非常的便利和简单。
Dubbo只是实现了服务治理,而Spring Cloud子项目分别覆盖了微服务架构下的众多部件,而服务治理只是其中的一个方面。Dubbo提供了各种Filter,对于上述中“无”的要素,可以通过扩展Filter来完善。
REST接口相比RPC更为轻量化,服务提供方和调用方的依赖只是依靠一纸契约,不存在代码级别的强依赖,当然REST接口也有痛点,因为接口定义过轻,很容易导致定义文档与实际实现不一致导致服务集成时的问题,但是该问题很好解决,只需要通过每个服务整合swagger,让每个服务的代码与文档一体化,就能解决。所以在分布式环境下,REST方式的服务依赖要比RPC方式的依赖更为灵活。
通常我们在提供对外服务时,都会以REST的方式提供出去,这样可以实现跨平台的特点,任何一个语言的调用方都可以根据接口定义来实现。那么在Dubbo中我们要提供REST接口时,不得不实现一层代理,用来将RPC接口转换成REST接口进行对外发布。若我们每个服务本身就以REST接口方式存在,当要对外提供服务时,主要在API网关中配置映射关系和权限控制就可实现服务的复用了。
如果选择Dubbo请务必在各个环节做好整套解决方案的准备,不然很可能随着服务数量的增长,整个团队都将疲于应付各种架构上不足引起的困难。而如果选择Spring Cloud,相对来说每个环节都已经有了对应的组件支持,可能有些也不一定能满足你所有的需求,但是其活跃的社区与高速的迭代进度也会是你可以依靠的强大后盾。
总结一下,dubbo曾经确实很牛逼,但是Spring Cloud是站在近些年技术发展之上进行开发,因此更具技术代表性。
spring cloud整机,dubbo需要自己组装;整机的性能有保证,组装的机子更自由。
参考:https://blog.csdn.net/u010664947/article/details/80007767
https://blog.csdn.net/qq_27529917/article/details/80955405
https://www.cnblogs.com/hankal/p/9818060.html
https://blog.csdn.net/ChauncyNong/article/details/80961630
https://blog.csdn.net/xunjiushi9717/article/details/91988479
101.1 SpringCloud与Dubbo的优缺点
一 Dubbo基于Dubbo协议进行远程rpc调用,Dubbo协议实质是Tcp-IP协议
Dubbo基于长连接,效率上比springcloud快
Dubbo是Java序列化调用,Dubbo只解决了远程rpc调用
二 springcloud是一个框架集,解决了微服务系统中方方面面的问题,是一个微服务全家桶
springcloud开发成本低,更适合中小公司
springcloud是基于Http调用,使用Rest API
1、能力支持方面
上文也提到,SpringCloud 提供了一整套微服务治理的功能组件,很多组件基本上都是”开箱即用”的,并且相互之间能很好的兼容,举个例子,如果要在 Spring Cloud 里实现服务发现、负载均衡和熔断降级,你只需要引用SpringCloud 的依赖组件即可,直接通过注解便可使用,基本上零配置;而 dubbo 框架,除了上述提到的能力支持之外,如果想要使用熔断降级,那你可能需要额外引用 hystrix 或者 resilience4j 来实现;温馨提示,hystrix 官方目前也已经宣布不再更新,并且推荐使用 resilience4j 。
2、协议兼容方面
SpringCloud 里并没有限制服务之间的通信协议,但是主流的一些客户端比如 restTemple、feign 等都是直接支持使用 Ribbon 来做服务注册发现和智能路由的,其底层通信的协议都是HTTP;而dubbo框架缺省是基于NIO异步传输使用 TCP 长连接并采用 Hessian 二进制序列化方式通信的;
这会涉及后续系统在扩展上的兼容性问题,比如需要调用一个三方系统或者是被第三方系统调用,相比而言 HTTP 协议可能更加通用。
3、模型定义方面
dubbo 在模型设计上将一个接口定义为一个服务,而 SpringCloud 里则是将一个应用定义为一个服务,这两者在模型上是存在很大差异的,你也许会奇怪,这个对使用会有影响吗?从现有使用方面来说是没有什么影响的,但是你如果有关注 Service Mesh 最新微服务技术的话,目前对 Dubbo 协议这块可能支持暂时还不完善,其中很大一部分原因就是因为在服务模型上与 K8S 的服务模型有差异;
4、调用性能方面
如果分布式系统中比较关注远程调用的性能,那 Dubbo 可能是一个较好的选择,基于 NIO 和 TCP 长连接的通信传输方式,在性能上相比 HTTP 协议是有绝对优势的;当然基于 SpringCloud 你也可以使用 gRPC 协议来解决性能问题,那就是另外一个问题了。
Dubbo 的一些优点:
Dubbo 支持 RPC 调用,服务之间的调用性能会很好。
支持多种序列化协议,如 Hessian、HTTP、WebService。
Dobbo Admin后台管理功能强大,提供了路由规则、动态配置、访问控制、权重调节、均衡负载等功能。
在国内影响力比较大,中文社区文档较为全面。
Dubbo 的一些问题:
Registry 严重依赖第三方组件(zookeeper 或者 redis),当这些组件出现问题时,服务调用很快就会中断。
Dubbo 只支持 RPC 调用。使得服务提供方(抽象接口)与调用方在代码上产生了强依赖,服务提供者需要不断将包含抽象接口的 jar 包打包出来供消费者使用。一旦打包出现问题,就会导致服务调用出错,并且以后发布部署会成很大问题(太强的依赖关系)。
另外,以后要兼容 .NET Core 服务,Dubbo RPC 本身不支持跨语言(可以用跨语言 RPC 框架解决,比如 Thrift、gRPC(重复封装了),或者自己再包一层 REST 服务,提供跨平台的服务调用实现,但相对麻烦很多)
Dubbo 只是实现了服务治理,其他微服务框架并未包含,如果需要使用,需要结合第三方框架实现(比如分布式配置用淘宝的 Diamond、服务跟踪用京东的 Hydra,但使用相对麻烦些),开发成本较高,且风险较大。
社区更新不及时(虽然最近在疯狂更新),但也难免阿里以后又不更新了,就尴尬了。
主要是国内公司使用,但阿里内部使用 HSF,相对于 Spring Cloud,企业应用会差一些。
Spring Cloud 的一些优点:
有强大的 Spring 社区、Netflix 等公司支持,并且开源社区贡献非常活跃。
标准化的将微服务的成熟产品和框架结合一起,Spring Cloud 提供整套的微服务解决方案,开发成本较低,且风险较小。
基于 Spring Boot,具有简单配置、快速开发、轻松部署、方便测试的特点。
支持 REST 服务调用,相比于 RPC,更加轻量化和灵活(服务之间只依赖一纸契约,不存在代码级别的强依赖),有利于跨语言服务的实现,以及服务的发布部署。另外,结合 Swagger,也使得服务的文档一体化。
提供了 Docker 及 Kubernetes 微服务编排支持。
国内外企业应用非常多,经受了大公司的应用考验(比如 Netfilx 公司),以及强大的开源社区支持。
Spring Cloud 的一些问题:
支持 REST 服务调用,可能因为接口定义过轻,导致定义文档与实际实现不一致导致服务集成时的问题(可以使用统一文档和版本管理解决,比如 Swagger)。
另外,REST 服务调用性能会比 RPC 低一些(但也不是强绑定)
Spring Cloud 整合了大量组件,相关文档比较复杂,需要针对性的进行阅读。
101.2 SpringCloud所有组件
eureka:注册中心
consul:服务发现
Feign:声明式服务调用
Ribbon:负载均衡
Zuul:服务网关
Hystrix :熔断限流
Config:配置中心
Bus:消息总线
Sleuth、Zipkin:链路追踪
SpringAdminBoot:服务监控
102.Dubbo底层原理
参考:https://blog.csdn.net/ityouknow/article/details/100789012
https://blog.csdn.net/qq_33101675/article/details/78701305
https://www.jianshu.com/p/e8800af25368
103.Collections.sort()和Arrays.sort()这两个排序算法的实现方式
Arrays.sort()的排序算法:
Collections.sort()的排序算法:
如果LegacyMergeSort.userRequested为true的话就会使用归并排序,可以通过下面代码设置为true:
不过方法legacyMergeSort的注释上有这么一句话,说明以后传统归并可能会被移除了。
如果不为true的话就会用一个叫TimSort的排序算法(归并排序的优化版本)
参考:https://blog.csdn.net/xlgen157387/article/details/79863301
Collections.sort的方法也最终调用的是Arrays.sort方法的。
Java6中sort方法默认采用的是传统快速排序,从Java7开始则使用改进版的双轴快速排序。
Java8中Arrays的排序方法只提供了两种算法的实现:
1、sort方法:双轴快速排序算法实现。 2、parallelSort方法:并行归并排序算法实现。
104.归并排序和堆排序、快速排序的比较
归并排序时间复杂度是O(nlogn);快排时间复杂度为O(nlogn);插入排序时间复杂度:O(n²);冒泡排序平均时间复杂度为O(n²);
堆排时间复杂度为O(nlogn);
若从空间复杂度来考虑:首选堆排序,其次是快速排序,最后是归并排序。
若从稳定性来考虑,应选取归并排序,因为堆排序和快速排序都是不稳定的。
若从平均情况下的排序速度考虑,应该选择快速排序。
插入排序动画演示:
选择排序动画:
104.1 快排、堆排、归并都是O(nlogn),为什么jdk还是选择快速排序
快排与堆排比较:
快速排序是最快的通用排序算法,在大多数情况中,快速排序是最佳选择。可以从上面的分析看到,对于基准数,也就是枢纽元的选择非常重要,极大影响快排的性能。一般有首元素法、随机法、三值中值分割法等。
从快速排序怎么优化的得到它能更好的结论:
1) 利用存储子任务的栈来消除递归
2) 利用基于三中值分区的中枢值
3) 设定一个使用切分时数组长度的最小值,如果小于这个值,就使用插入排序(这个最小值根据经验给定,一般设定为4或者5)
4) 当处理子数组的时候,首先将大的子数组压入栈中,这样可以最小化栈的总大小,确保小的问题首先被解决
根据这四点可以论证快排比堆排序好:
由上述代码可知,堆排序的过程就是n次建堆的过程,
总比较次数小于2nlogn (以2为底),即比较次数比普通快排少。
建堆的代码已经相当简练,优化带来的效率提高有限。
因为不涉及到切分问题,所以能加快速度的 第3点只能用1次,而在快排中可以用多次。
堆排序是循环而非递归过程,不存在显式栈问题,而且这个循环调用函数引起了堆的变化,所以不可能提到循环外面。
循环展开和多路并发方面也不如 快排,(快排是适用分治法的,各个子问题相互独立)。
快排与归并比较:
归并排序的性能对于主存排序不如快速排序好,而且它的编程也不省事。
快速排序内存写的操作比归并排序少。
虽然归并会做几乎完美的分割,效果较快排好,但是多计N*LongN,归并反而慢。
归并理论上效率很好,但是在while循环时,要判断是否出界,多做n*logn次比较,还要把数据拷贝回去,速度明显慢于快排。
https://blog.csdn.net/fycy2010/article/details/47103825
105.垃圾回收时什么时候才回收?
方法区主要回收的内容有:废弃常量和无用的类。对于废弃常量也可通过引用的可达性来判断,但是对于无用的类则需要同时满足下面3个条件:
- 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例;
- 加载该类的
ClassLoader
已经被回收; - 该类对应的
java.lang.Class
对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
106.spring 的优点
1.降低了组件之间的耦合性 ,实现了软件各层之间的解耦
2.可以使用容易提供的众多服务,如事务管理,消息服务等
3.容器提供单例模式支持
4.容器提供了AOP技术,利用它很容易实现如权限拦截,运行期监控等功能
5.容器提供了众多的辅助类,能加快应用的开发
6.spring对于主流的应用框架提供了集成支持,如hibernate,JPA,Struts等
7.spring属于低侵入式设计,代码的污染极低
8.独立于各种应用服务器
9.spring的DI机制降低了业务对象替换的复杂性
10.Spring的高度开放性,并不强制应用完全依赖于Spring,开发者可以自由选择spring的部分或全部
(实战经验)107.你对现有系统做了哪些升级的活
(1)mysql主从分离改造
以前能改造就适当改造,不能改造的就放弃,新业务代码按照规则改造:业务方法名insert、update开头的进入主库,select和query开头的进入从库
(2)写了个日志分表插入的公共组件
写了个AOP,在切面里先查有没有当月的表,没有就新建,按月(上中下旬)三旬生成表,插入日志数据 。
参考:https://www.cnblogs.com/jianjianyang/p/4910851.html
(3)稿件的异步执行的改造
同步等待改为起异步线程执行,CompletableFuture.whenComplete((s, throwable) -> {}),用来等待法Kaffa的消息,上传、视频文件等耗时任务完成;
再加上fork/join结果,
(4)新闻稿件排序接口
以前是按照权重的倒数存在redis缓存里,查出来再排序,但权重相同的没法排序
我的改进:存在score的值与两部分组成:权重值.时间的毫秒值,组成数字
(5)合并请求,但是没用Hystrix,用的不是SpringCloud的框架
有个需求查近期1w篇稿件的详情:用普通的java代码自定义LinkedBlockingQueue
(6)每次都要查用户name,没做反范式冗余
不用每次都在具体逻辑里写查name的sql改为AOP处理,凡是业务方法上使用指定AOP注解后,都会先查name,再执行后面的逻辑。
(7)经常用的方法做成公共方法避免重复造轮子
(8)改造为抽象工厂设计模式
扩展性更强
(实战经验)108.1 你在你的项目里做了什么?
1.微信公众号接入:登录、验证、获取信息
(实战经验)108.项目中你遇到了什么棘手难处理的问题?怎么解决的?
(1)苏州桥和石景山稿件总出现缓存不一致的情况,有时会有一个因为网络问题同步会失败;
解决方案:最终一致性,满足AP。本地缓存一份稿件信息,起一个线程分别向苏州桥和石景山两处机房推数据,等待两处机房反馈信息是成功还是失败,都成功,则线程成功;如果有一个失败,则向失败的机房重新推一份数据,反复重试直到成功。
(2)稿件锁:审核人占锁、发稿人占锁、编辑人占锁、
解决方案:
放弃数据库字段限制,开始使用redis的setxn锁做分布式锁,SETNX 稿件id 用户id,谁抢到锁谁白鸡稿件,编辑完保存释放锁。但是不能设置超时时间。后来发现有些编辑习惯不好编辑稿件页面一直不关闭就没法保存,锁不释放,遂放弃。
加过期时间:那时没用红锁:Redisson红锁。
存一份设置默认30分钟过期时间:SET 稿件id 占用用户id EX 1800;
添加正在编辑的字段友好提示编辑失败的原因是别人正在编辑,不能搞得编辑失败还找不到具体原因,页面js定时器发现超过1小时没保存就提示并强制保存,把正在编辑的 字段改为未编辑。
对于长期占用但没编辑的:有些稿件要急于转手给其他编辑不能让一个编辑长期占用,则后台管理的按钮强制解锁稿件;超时1小时自动解锁。
对于正在编辑的:编辑占用时提示对方手机号、微信号,人工干预解决。
(3)热点数据过期短时间内集体过期
稿件设置的过期时间一本为1个月,redis缓存里的数据被删除,设置1个月附近设置 随机过期时间。
(4)与第三方对接,接口慢、出问题责任不清,甚至是找不到是哪台机器,拿日志数据说话。
充分打日志一套规则:发起时间,调用接口、本机ip,对方ip,事件类型,成功与否,对方返回数据,对方耗时,己方耗时,报什么错、
4个W:Who(自己和对方ip)、When(何时发起调用)、What(做什么业务)、where(在程序的位置:哪个类的哪个方法调用/出错)、
到底是谁接口慢,谁接口出错。
(实战经验)109.手画出你项目架构图,针对性提问
109.1 API网关
一个API网关的基本功能包含了统一接入、协议适配、流量管理与容错、以及安全防护,这四大基本功能构成了网关的核心功能。网关首要的功能是负责统一接入,然后将请求的协议转换成内部的接口协议,在调用的过程中还要有限流、降级、熔断等容错的方式来保护网关的整体稳定,同时网关还要做到基本的安全防护(防刷控制),以及黑白名单(比如IP白名单)等基本安全措施,如下图所示:
整体的网关架构示例如下所示:
https://www.jianshu.com/p/7baab672b822
110.分布式session
(1)方案:Spring Session + Redis实现分布式Session共享
参考:https://www.cnblogs.com/SimpleWu/p/10118674.html
111.Semaphore,CountDownLatch,CyclicBarrier区别与使用
类比联想:
CountDownLatch:
联想场景:游戏倒计时10s、软件使用倒计时3天、百度网盘加速试用倒计时60s、30s抢答到时间后机会就是别人的,此期间别人只能看着等着你使用。
CyclicBarrier:
联想:等所有的数据汇集齐了才能整合进行接下来的分析;规定的10人会议人到齐了就开会否则来人就等着;赛马场比赛的马队到齐就打开栅栏开始赛跑否则来了就要等着;
Semaphore:
联想:在窗口排队买火车票每次只能放进去一个人买;进火车站每次只能放有限的一拨人进去,超过人数限制的只能等着。
CountDownLatch的countDown()不会引起阻塞,所以CountDownLatch可以应用于主线程等待所有子线程结束后再继续执行的情况。
CountDownLatch当计数到0时,计数无法被重置;CyclicBarrier计数达到指定值时,计数置为0重新开始。CountDownLatch每次调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响;CyclicBarrier只有一个await()方法,调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞。
CountDownLatch是减计数方式,计数==0时释放所有等待的线程;CyclicBarrier是加计数方式,计数达到构造方法中参数指定的值时释放所有等待的线程。Semaphore,每次semaphore.acquire(),获取一个资源,每次semaphore.acquire(n),获取n个资源,当达到semaphore 指定资源数量时就不能再访问线程处于阻塞,必须等其它线程释放资源。
CountDownLatch、CyclikBarrier、Semaphore 都有一个int类型参数的构造方法。CountDownLatch、CyclikBarrier这个值作为计数用,达到该次数即释放等待的线程,而Semaphore 中所有acquire获取到的资源达到这个数,会使得其它线程阻塞。
https://www.cnblogs.com/zhaoyan001/p/10775676.html
https://www.cnblogs.com/MrEven/p/11570251.html
112.快排优化
https://www.cnblogs.com/noKing/archive/2017/11/29/7922397.html
https://blog.csdn.net/u010325665/article/details/86299710
https://blog.csdn.net/QuZDLvT/article/details/98181898
113.GC Roots
1.是虚拟机栈中的引用的对象 我们在程序中正常创建一个对象,对象会在堆上开辟一块空间,同时会将这块空间的地址作为引用保存到虚拟机栈中。 如果对象生命周期结束了,那么引用就会从虚拟机栈中出栈,因此如果在虚拟机栈中有引用,就说明这个对象还是有用的,这种情况是最常见的。 2.在类中定义了全局的静态的对象,也就是使用了static关键字 由于虚拟机栈是线程私有的,所以这种对象的引用会保存在共有的方法区中,显然将方法区中的静态引用作为GC Roots是必须的。 3.常量引用,就是使用了static final关键字 由于这种引用初始化之后不会修改,所以方法区常量池里的引用的对象也应该作为GC Roots。 4.本地方法栈中引用的对象 在使用JNI技术时,有时候单纯的Java代码并不能满足我们的需求,我们可能需要在Java中调用C或C++的代码, 因此会使用native方法,JVM内存中专门有一块本地方法栈,用来保存这些对象的引用。
114.JAVA线程池如何合理配置核心线程数?
1.先看下机器的CPU核数,然后在设定具体参数: System.out.println(Runtime.getRuntime().availableProcessors()); 即CPU核数 = Runtime.getRuntime().availableProcessors() 2.分析下线程池处理的程序是CPU密集型,还是IO密集型 CPU密集型:核心线程数 = CPU核数 + 1 IO密集型:核心线程数 = CPU核数 * 2 注:IO密集型(某大厂实践经验) 核心线程数 = CPU核数 / (1-阻塞系数) 例如阻塞系数 0.8,CPU核数为4 则核心线程数为20
参考:https://blog.csdn.net/weixin_41910694/article/details/90704670
线程池7各参数参考:https://blog.csdn.net/ye17186/article/details/89467919
114.1另一个设参版本
1、默认值 * corePoolSize=1 * queueCapacity=Integer.MAX_VALUE * maxPoolSize=Integer.MAX_VALUE * keepAliveTime=60s * allowCoreThreadTimeout=false * rejectedExecutionHandler=AbortPolicy()
2、如何来设置 * 需要根据几个值来决定 - tasks :每秒的任务数,假设为500~1000 - taskcost:每个任务花费时间,假设为0.1s - responsetime:系统允许容忍的最大响应时间,假设为1s * 做几个计算 - corePoolSize = 每秒需要多少个线程处理? * threadcount = tasks/(1/taskcost) =taskstaskcout = (500~1000)0.1 = 50~100 个线程。corePoolSize设置应该大于50 * 根据8020原则,如果80%的每秒任务数小于800,那么corePoolSize设置为80即可 - queueCapacity = (coreSizePool/taskcost)responsetime * 计算可得 queueCapacity = 80/0.11 = 800。意思是队列里的线程可以等待1s,超过了的需要新开线程来执行 * 切记不能设置为Integer.MAX_VALUE,这样队列会很大,线程数只会保持在corePoolSize大小,当任务陡增时,不能新开线程来执行,响应时间会随之陡增。 - maxPoolSize = (max(tasks)- queueCapacity)/(1/taskcost) * 计算可得 maxPoolSize = (1000-800)/10 = 20 * (最大任务数-队列容量)/每个线程每秒处理能力 = 最大线程数 - rejectedExecutionHandler:根据具体情况来决定,任务不重要可丢弃,任务重要则要利用一些缓冲机制来处理 - keepAliveTime和allowCoreThreadTimeout采用默认通常能满足
3、 以上都是理想值,实际情况下要根据机器性能来决定。如果在未达到最大线程数的情况机器cpu load已经满了,则需要通过升级硬件(呵呵)和优化代码,降低taskcost来处理。
115.TCP 为什么三次握手而不是两次握手
- 为了实现可靠数据传输, TCP 协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的。 三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤
- 如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认
参考:https://blog.csdn.net/lengxiao1993/article/details/82771768
116.mysql优化思路
1.优化更需要优化的sql;
2.定位优化对象的性能瓶颈:优化前需了解查询的瓶颈是IO还是CPU,可通过PROFILING很容易定位查询的瓶颈。
3.明确优化目标;
4.从Explain入手;
5.多使用profile;(在mysql5.7之后,profile信息将逐渐被废弃,mysql推荐使用performance schema)
SQL优化的基本原则:
1.永远用小结果集驱动大结果集;
From子句中sql解析顺序为从右向左,执行时会以最左边的表为基础表循环与右边表数据做笛卡尔积,所以以小结果集驱动能减少循环次数,从而减少对被驱动结果集的访问,从而减少被驱动表的锁定。
2.尽可能在索引中完成排序;
排序算法有两种:a.查出排序字段和行指针,排序,再通过行指针获得行数据所需列,返回结果集;b.取出所有排序列数据,在排序缓冲区中排完序直接返回结果集。
索引排序是利用索引的有序性对数据排序的。
3.只取出子集需要的colums
4.仅仅使用最有效的过滤条件;
5.尽可能避免复杂的Join和子查询;
https://www.cnblogs.com/jpfss/p/9167750.html
https://blog.csdn.net/adparking/article/details/7369282
116.1 mysql的profile使用
https://www.cnblogs.com/flzs/p/9974822.html
https://www.cnblogs.com/LiuYanYGZ/p/12237363.html
116.2 mysql的performance schema的使用
https://blog.csdn.net/max1231ff/article/details/105351733
https://www.cnblogs.com/zhoujinyi/p/5236705.html
117.Mybatis的二级缓存的设计模式
(1)装饰者模式
CachingExecutor是Executor的装饰者,以增强Executor的功能,使其具有缓存查询的功能。
https://blog.csdn.net/qiushisoftware/article/details/98944211
(2)建造者模式
Cache cache = new CacheBuilder();
http://www.mybatis.cn/archives/752.html
Mybatis至少遇到了以下的设计模式的使用:
1、Builder模式,例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder;
2、工厂模式,例如SqlSessionFactory、ObjectFactory、MapperProxyFactory;
3、单例模式,例如ErrorContext和LogFactory;
4、代理模式,Mybatis实现的核心,比如MapperProxy、ConnectionLogger,用的jdk的动态代理;还有executor.loader包使用了cglib或者javassist达到延迟加载的效果;
5、组合模式,例如SqlNode和各个子类ChooseSqlNode等;
6、模板方法模式,例如BaseExecutor和SimpleExecutor,还有BaseTypeHandler和所有的子类例如IntegerTypeHandler;
7、适配器模式,例如Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现;
8、装饰者模式,例如Cache包中的cache.decorators子包中等各个装饰者的实现;
9、迭代器模式,例如迭代器模式PropertyTokenizer;
https://www.cnblogs.com/CQqfjy/p/12302786.html
https://www.jianshu.com/p/f33223c0e1ed
118.static、final修饰类、成员变量、方法
static:
https://www.cnblogs.com/qicao/p/8831901.html
https://blog.csdn.net/nimeghbia/article/details/87871441
final:
https://blog.csdn.net/jijiangpeng/article/details/94895638
final 修饰类中的属性其初始化可以在两个地方:一是其定义处,也就是说在 final 属性定义时直接给其赋值;二是在构造函数中。这两个地方只能选其一,要么在定义时给值,要么在构造函数中给值,不能同时既在定义时赋值,又在构造函数中赋予另外的值。
没有定义为静态类,那么在这个内部类中如果要利用static关键字来修饰某个成员方法或者成员变量是不允许的。
只有将某个内部类修饰为静态类,然后才能够在这个类中定义静态的成员变量与成员方法。这是静态内部类都有的一个特性。
一般的非静态内部类,可以随意的访问外部类中的成员变量与成员方法。
即使这些成员方法被修饰为private(私有的成员变量或者方法),其非静态内部类都可以随意的访问。在其他类中是无法访问被定义为私有的成员变量或则方法。
不能够从静态内部类的对象中访问外部类的非静态成员(包括成员变量与成员方法)。在静态内部类中,无论在成员方法内部还是在其他地方,都只能够引用外部类中的静态的变量,而不能够访问非静态的变量。在普通的非静态内部类中是没有这个限制的。在静态内部类中,可以定义静态的方法(也只有在静态的内部类中可以定义静态的方法)。
要在一个外部类中定义一个静态的内部类,不需要利用关键字new来创建内部类的实例。即在创建静态类内部对象时,不需要其外部类的对象。
静态内部类可以创建静态的成员而非静态的内部类不可以。
普通类是不允许声明为静态的,只有内部类才可以。
Java中static关键字可以修饰方法与变量:
(1)修饰变量的时候,这个变量属于类变量,可以直接通过类名.变量名来引用。
(2) 修饰方法的时候可以直接通过类名.方法名来访问。
119.Redis缓存失效策略
https://www.cnblogs.com/dudu2mama/p/11366292.html
120.几条遵循的多线程最佳实践
(1)给你的线程七个有意义的名字
(2)避免锁,缩小同步的范围(锁的粒度)
(3)多用同步类少用wait和notify
(4)多用并发集合少用同步集合
(5)如果可以 更偏向于使用volatile而不是synchronized
121.为什么Redis一定要用跳表来实现有序集合?
1.性能. 主要是对标AVL. 但是AVL实现复杂,对于频繁的增删操作极大可能造成子树的平衡操作,这样很明显就会浪费性能。
2.内存占用。跳表的空间利用率还是很高的,加上Redis并非使用普通的跳表结构,协调相关参数,比如层数,节点元素数等。
122.有没有办法改造一下链表提升查询速度
把链表改造为跳表。跳表也叫跳跃表,是一种动态的数据结构。如果我们需要在有序链表中进行查找某个值,需要遍历整个链表,二分查找对链表不支持,二分查找的底层要求为数组,遍历整个链表的时间复杂度为O(n)。我们可以把链表改造成B树、红黑树、AVL树等数据结构来提升查询效率,但是B树、红黑树、AVL树这些数据结构实现起来非常复杂,里面的细节也比较多。跳表就是为了提升有序链表的查询速度产生的一种动态数据结构,跳表相对B树、红黑树、AVL树这些数据结构实现起来比较简单,但时间复杂度与B树、红黑树、AVL树这些数据结构不相上下,时间复杂度能够达到O(logn)。
https://baijiahao.baidu.com/s?id=1633338040568845450&wfr=spider&for=pc
123.十万个数据集中找出前100个最大的元素
https://blog.csdn.net/zyq522376829/article/details/47686867
https://www.zhihu.com/question/28874340?sort=created
123.1 海量数据的TOP K问题的解决方法
https://blog.csdn.net/twlkyao/article/details/12037073
123.2 无序数组怎么寻找第k大的数,写一个二叉树层次遍历
https://blog.csdn.net/qiuxinfa123/article/details/85783198
https://www.cnblogs.com/kyoner/p/10465633.html
https://blog.csdn.net/weixin_43930512/article/details/91410153
124.jdk1.8对HashMap的改进
jdk1.7底层采用entry数组+链表的数据结构,而1.8采用node数组+链表/红黑树的数据结构。
jdk1.7的HashMap插入新值时使用头插法,1.8使用尾插法。
使用头插法比较快,但在多线程扩容时会引起倒序和闭环的问题。所以1.8就采用了尾插法。
扩容后新表中的索引位置计算方式不同,jdk1.7扩容时是将旧表元素的所有数据重新进行哈希计算,即hashCode & (length-1)。而1.8中扩容时只需将hashCode和老数组长度做与运算判断是0还是1,是0的话索引不变,是1的话索引变为老索引位置+老数组长度。
https://www.cnblogs.com/fangtingfei/p/12964224.html
https://www.cnblogs.com/williamjie/p/11089547.html
124.1 扩容为什么是2的n次方
1,插入新元素确定索引位置的时候是采用key的hashCode和数组长度做与运算,即hashCode&(length-1)。模拟的是取模运算,但速度比取模快很多,要保证这种运算的正确性,必须要求数组的长度是2的n次方。
2,在扩容时进行新索引位置的计算时也要求数组长度是2的n次方。
124.2 为什么HashMap不一直使用红黑树?
因为红黑树需要进行左旋,右旋操作, 而单链表不需要, 以下都是单链表与红黑树结构对比。 如果元素小于8个,查询成本高,新增成本低 如果元素大于8个,查询成本低,新增成本高
当个数不多的时候,直接链表遍历更方便,实现起来也简单。而红黑树的实现要复杂的多。
124.3 为什么链表的长度为8是变成红黑树?为什么为6时又变成链表?
空间和时间的权衡。
首先当链表长度为6时 查询的平均长度为 n/2=3
红黑树为 log(6)=2.6
为8时 : 链表 8/2=4
红黑树 log(8)=3
根据两者的函数图也可以知道随着bin中的数量越多那么红黑树花的时间远远比链表少。
链表O(n),红黑树O(logn)。
为7的时候两者应该是 链表花的时间小于红黑树的,但是为什么不是在7的时候转成链表呢,我觉得可能是因为把7当做一个链表和红黑树的过渡点。中间有个差值7可以有效防止链表和树频繁转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。
put进去的key进行计算hashCode时 只要选择计算hash值的算法足够好(hash碰撞率极低),从而遵循泊松分布,使得桶中挂载的bin的数量等于8的概率非常小,从而转换为红黑树的概率也小,反之则概率大。
之所以选择8,不是拍脑袋决定的,而是根据概率统计决定的。
https://blog.csdn.net/qq_27409289/article/details/92759730
hash桶中存放的链表长度概率 随着长度的增加而减小
hashmap中的源码注释
(二) 为什么到8转为红黑树 到6转为链表
TreeNodes(红黑树)占用空间是普通Nodes(链表)的两倍,为了时间和空间的权衡。
节点的分布频率会遵循泊松分布,链表长度达到8个元素的概率为0.00000006,几乎是不可能事件.
为什么转化为红黑树的阈值8和转化为链表的阈值6不一样,是为了避免频繁来回转化。
https://www.cnblogs.com/misscai/p/13234177.html
124.4 为什么jdk1.8中HashMap采用了尾插法?
使用头插法比较快,但在多线程扩容时会引起倒序和闭环的问题。
-
因为用的尾插法所以新数组链表不会倒置,多线程下不会出现死循环。
Java8在同样的前提下并不会引起死循环,原因是扩容转移后前后链表顺序不变,保持之前节点的引用关系。
HashMap在jdk1.7中采用头插入法,在扩容时会改变链表中元素原本的顺序,以至于在并发场景下导致链表成环的问题。而在jdk1.8中采用尾插入法,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了。
https://blog.csdn.net/zhuqiuhui/article/details/51849692
https://blog.csdn.net/fedorafrog/article/details/104448853
链表头插法的会颠倒原来一个散列桶里面链表的顺序。在并发的时候原来的顺序被另外一个线程a颠倒了,而被挂起线程b恢复后拿扩容前的节点和顺序继续完成第一次循环后,又遵循a线程扩容后的链表顺序重新排列链表中的顺序,最终形成了环。
-
使用头插会改变链表的上的顺序,但是如果使用尾插,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了。
-
Java7在多线程操作HashMap时可能引起死循环,原因是扩容转移后前后链表顺序倒置,在转移过程中修改了原来链表中节点的引用关系。
-
Java8在同样的前提下并不会引起死循环,原因是扩容转移后前后链表顺序不变,保持之前节点的引用关系。
https://blog.csdn.net/Ho528528/article/details/103903998
125.抽象类abstract与接口interface的区别
https://blog.csdn.net/m0_38105216/article/details/85067156
https://blog.csdn.net/qq_44543508/article/details/102609910
126.抽象类abstract与接口interface区别
https://ispotu.blog.csdn.net/article/details/107001778
127.jvm引用的四种状态
https://blog.csdn.net/LiBoom/article/details/81077897
128.tomcat的类加载机制
https://blog.csdn.net/qq_38182963/article/details/78660779 https://www.cnblogs.com/dengchengchao/p/11844022.html
https://www.cnblogs.com/fanguangdexiaoyuer/p/10213324.html#_label1
https://www.cnblogs.com/dw-haung/p/10103844.html
129.并发限流措施
延迟处理,拒绝处理,或者部分拒绝处理。
常见的限流算法有:计数器、漏桶和令牌桶算法。
Google开源工具包Guava提供了限流工具类RateLimiter,该类基于令牌桶算法来完成限流。
Nginx层限流
https://www.cnblogs.com/xuwc/p/9123078.html
https://www.cnblogs.com/ynyhl/p/9507084.html
130.BeanFactory与ApplicationContext的区别
https://www.jianshu.com/p/f13f554ee8ce
https://blog.csdn.net/pythias_/article/details/82752881
https://www.cnblogs.com/neon/p/10929140.html
130.1 BeanFactory的子类
spring中的容器要么是BeanFactory的子类的实现要么就是BeanFactory本身的实现。
BeanFactory的子接口有如下:
ApplicationContext, AutowireCapableBeanFactory, ConfigurableApplicationContext, ConfigurableBeanFactory, ConfigurableListableBeanFactory, ConfigurablePortletApplicationContext, ConfigurableWebApplicationContext, HierarchicalBeanFactory, ListableBeanFactory,
WebApplicationContext
BeanFactory的直接或间接实现类
AbstractApplicationContext, AbstractAutowireCapableBeanFactory, AbstractBeanFactory, AbstractRefreshableApplicationContext, AbstractRefreshableConfigApplicationContext, AbstractRefreshablePortletApplicationContext, AbstractRefreshableWebApplicationContext, AbstractXmlApplicationContext, AnnotationConfigApplicationContext, AnnotationConfigWebApplicationContext, ClassPathXmlApplicationContext, DefaultListableBeanFactory, FileSystemXmlApplicationContext, GenericApplicationContext, GenericWebApplicationContext, GenericXmlApplicationContext, ResourceAdapterApplicationContext, SimpleJndiBeanFactory, StaticApplicationContext, StaticListableBeanFactory, StaticPortletApplicationContext, StaticWebApplicationContext, XmlBeanFactory, XmlPortletApplicationContext, XmlWebApplicationContext
https://blog.csdn.net/simba_1986/article/details/79296453
131.后端接口延时大问题的排查
https://blog.csdn.net/sinat_30802291/article/details/85166787
https://blog.csdn.net/chixian4839/article/details/100728326
https://blog.csdn.net/jingyangV587/article/details/103438347
132.java工具——JVM调优、GC、压测、linux命令
jmap
jdk自带命令。jmap是一个多功能的命令,查看JVM内存使用情况。它可以生成 java 程序的 dump 文件, 也可以查看堆内对象示例的统计信息、查看 ClassLoader 的信息以及 finalizer 队列。
jmap -dump:live,format=b,file=myjmapfile.txt 19570
jstack
jdk自带命令。查看运行java程序的java stack和native stack的信息。
jstack pid
jstat
jdk自带命令。可以观察到classloader,compiler,gc相关信息。可以时时监控资源和性能 。
MAT
第三方堆栈文件分析工具。下载地址:https://www.eclipse.org/mat/
jps
jdk自带命令。查看JVM中运行的进程状态信息。
jps [options] [hostid]
jhat
jdk内置的工具之一。主要是用来分析java堆的命令。可以将堆中的对象以html的形式显示出来,包括对象的数量,大小等等,并支持对象查询语言。使用jmap等方法生成java的堆文件后,使用其进行分析。
jinfo 输出jvm各项参数信息,包括默认参数 Jinfo [option] pid
jstatd 虚拟机的jstat守护进程,主要用于监控JVM的创建与终止,并提供一个接口允许远程监控工具依附到在本地主机上运行的JVM。 jstatd工具是一个RMI服务器应用程序,主要用于监控HotSpot Java 虚拟机的创建与终止,并提供一个接口以允许远程监控工具附加到本地主机上运行的JVM上。jstatd位于 $JAVA_HOME/bin目录下 jstatd服务器需要在本地主机上存在一个RMI注册表。 jdb jdk自带工具。 JDB是 The Java Debugger 的简称,它可以在命令行下调试Java程序。在JDK自己的bin目录下。
hprof
JDK自带一个简单的性能分析工具。常被用于内存使用情况分析。它是一个动态链接库文件,监控CPU的1使用率、内存堆栈分配情况等。使用命令行格式为:
java -Xrunhprof ToBeProfiledClass
有两种分析方法:内存分配历史的跟踪记录(dump)和将占用内存的对象进行排序(sites)。命令行如下
java -Xrunhprof:heap=all|dump|sites ToBeProfiledClass
Java VisualVM(jvisualvm)
即jvisualvm,Netbeans的profile子项目,已在JDK6.0 update 7 中自带,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈。Jvisualvm动态分析jvm内存情况和gc情况,插件:visualGC.
在JDK_HOME/bin(默认是C:\Program Files\Java\jdk1.6.0_13\bin)目录下面,有一个jvisualvm.exe文件,双击打开。启动起来后和jconsole 一样同样可以选择本地和远程,如果需要监控远程同样需要配置相关参数。
ab 、abs
ab是apache 推出的压力测试工具,可以用来测试http服务器的性能,得出QPS。全称:Apache HTTP server benchmarking tool
abs 则是apache 推出的压力测试工具,可以用来测试https服务器的性能,得出QPS 工具下载地址:https://www.apachehaus.com/cgi-bin/download.plx 工具官方文档地址:http://httpd.apache.org/docs/2.4/programs/ab.html 用法简介:下载工具进入Apache24/bin目录下载就可以看到ab.exe和abs.exe 在命令行中输入
ab -n 1000 -c 20 http://127.0.0.1:8080/
-n 要发多少个请求数 -c 每次进行多少请求 注意:必须是 http://127.0.0.1:8080/ ,“/” 不可以被省略,测试地址必须是一个url。
jconsole jdk自带命令。可视化监控jvm
gc log
gc时打印出来的日志。
在jvm启动参数加上以下,可以开启gc log,配置gclog的输出位置
-XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:/usr/local/project/jvmtest/gc.log
jcmd
在JDK1.7以后新增的一个命令行工具。可以用它来导出堆、查看Java进程、导出线程信息、执行GC、还可以进行采样分析(jmc 工具的飞行记录器)。
GCViewer 日志可视化分析工具。 下载地址:http://www.tagtraum.com/gcviewer.html
GCHisto
日志分析工具。
下载:http://java.net/projects/gchisto 直接点击gchisto.jar就可以运行,点add载入gc.log
GCLogViewer
日志分析工具。
下载:http://code.google.com/p/gclogviewer/ 点击run.bat运行 整个过程gc情况的趋势图,还显示了gc类型,吞吐量,平均gc频率,内存变化趋势等 Tools里还能比较不同gc日志
HPjmeter 日志分析工具。 下载地址: http://www.hp.com/go/java 工具很强大,但只能打开由以下参数生成的GC log, -verbose:gc -Xloggc:gc.log,添加其他参数生成的gc.log无法打开。
garbagecat
日志分析工具。
http://code.google.com/a/eclipselabs.org/p/garbagecat/wiki/Documentation
Jprofiler
JProfiler是由ej-technologies GmbH公司开发的一款性能瓶颈分析工具.
下载:https://www.ej-technologies.com/products/jprofiler/overview.html
GCeasy 一款超好用的在线分析GC日志的网站 网址:https://www.gceasy.io/
jpda java远程调试工具。JPDA(Java Platform Debugger Architecture)是Java平台调试体系结构的缩写。由3个规范组成,分别是JVMTI(JVM Tool Interface),JDWP(Java Debug Wire Protocol),JDI(Java Debug Interface) 。
top
linux命令。实时显示正在执行进程的 CPU 使用率、内存使用率以及系统负载等信息。
top Hp pid
查看具体线程使用系统资源情况
vmstat
linux命令。监测指定采样周期和次数。它不仅可以统计内存的使用情况,还可以观测到 CPU 的使用率、swap 的使用情况。但 vmstat 一般很少用来查看内存的使用情况,而是经常被用来观察进程的上下文切换。Virtual Meomory Statistics(虚拟内存统计)的缩写,可对操作系统的虚拟内存、进程、CPU活动进行监控。
pidstat
linux命令。pidstat是 Sysstat 中的一个组件;可以通过yum install sysstat 安装该监控组件。pidstat 命令则是深入到线程级别的监测工具。
gdb linux命令。是一个由GNU开源组织发布的、UNIX/LINUX操作系统下的、基于命令行的、功能强大的程序调试工具。
nmon Linux工具。很轻松的监控Linux系统的 CPU、内存、网络、硬盘、文件系统、NFS、高耗进程、资源和 IBM Power 系统的微分区的信息)。 下载:http://nmon.sourceforge.net/pmwiki.php?n=Site.Downlo
JMeter Apache JMeter为一款广为流传的开源压测产品,最初被设计用于Web应用测试,如今JMeter可以用于测试静态和动态资源,例如静态文件、Java 小服务程序、CGI 脚本、Java 对象、数据库、FTP服务器等等,还能对服务器、网络或对象模拟巨大的负载,通过不同压力类别测试它们的强度和分析整体性能。另外,JMeter能够对应用程序做功能测试和回归测试,通过创建带有断言的脚本来验证你的程序返回了你期望的结果。为了最大限度的灵活性,JMeter允许使用正则表达式创建断言。 JMeter的特点包括对HTTP、FTP服务器、数据库进行压力测试和性能测试;完全的可移植性;完全 Swing和轻量组件支持包;完全多线程;缓存和离线分析/回放测试结果;可链接的取样器;具有提供动态输入到测试的功能;支持脚本编程的取样器等。在设计阶段,JMeter能够充当HTTP PROXY(代理)来记录浏览器的HTTP请求,也可以记录Apache等WebServer的log文件来重现HTTP流量,并在测试运行时以此为依据设置重复次数和并发度(线程数)来进行压测。
类似的压测工具还有:LoadRunner、NeoLoad、WebLOAD、Loadster、Load impact、CloudTest、Loadstorm、阿里云PTS、压测宝、 更多压测工具详情:https://blog.csdn.net/langzitianya/article/details/81479422
sar linux命令。(System Activity Reporter 系统活动情况报告)是目前 Linux 上最为全面的系统性能分析工具之一,可以从多方面对系统的活动进行报告,包括:文件的读写情况、系统调用的使用情况、磁盘 I/O、CPU 效率、内存使用状况、进程活动及 IPC 有关的活动等。 free linux命令。显示系统内存的使用情况,包括物理内存、交换内存(swap)和内核缓冲区内存。
iostat linux命令。是I/O statistics(输入/输出统计)的缩写,iostat工具将对系统的磁盘操作活动进行监视。它的特点是汇报磁盘活动统计情况,同时也会汇报出CPU使用情况。iostat也有一个弱点,就是它不能对某个进程进行深入分析,仅对系统的整体情况进行分析。iostat属于sysstat软件包。可以用yum直接安装。 tee linux命令用于读取标准输入的数据,并将其内容输出成文件。 tee指令会从标准输入设备读取数据,将其内容输出到标准输出设备,同时保存成文件。
132.1 Linux服务器性能监控
https://www.cnblogs.com/insane-Mr-Li/p/10727921.html
133.防止超卖的解决方案
(1)redis的setnx来实现分布式锁+分段锁
同一个锁key,同一时间只能有一个客户端拿到锁,其他客户端会陷入无限的等待来尝试获取那个锁,只有获取到锁的客户端才能执行下面的业务逻辑。
假如你现在iphone有1000个库存,那么你完全可以给拆成20个库存段,在数据库的表里建20个库存字段,比如stock_01,stock_02,类似这样的,也可以在redis之类的地方放20个库存key。然后写一个简单的随机算法,每个请求都是随机在20个分段库存里,选择一个进行加锁。这样每次就能够处理20个进程请求,但有个坑需要解决:当某段锁的库存不足,一定要实现自动释放锁然后换下一个分段库存再次尝试加锁处理。
https://blog.csdn.net/qq_41723615/article/details/104197682
(2)redis的队列来实现串行化
将要促销的商品数量以队列的方式存入redis中,每当用户抢到一件促销商品则从队列中删除一个数据,确保商品不会超卖。
代码思路:
//1.当增加商品或修改商品库存时,将库存数据存入缓存
//2.如果用户下单,则在缓存的该商品库存key进行删除操作(判断删除后值不小于0)
//3.通过缓存获取商品库存数据显示在前端
//3.逻辑判断,当库存等于0时,数据持久化操作,并对商品下架处理
https://blog.csdn.net/qq_31024823/article/details/81561651
基于redis实现用户行为频率限制——用户再次抢购时提示“该用户操作频繁,请少稍后重试,一般可设置10秒后才能再次调用秒杀接口”;
(3)基于Token令牌+MQ实现异步修改库存;(漏桶算法、令牌桶算法限流)
(4)乐观锁。
一个最简单的办法就是,给每个商品库存一个版本号version字段
134.Java推荐技术书籍
《Java编程规范》 适合对象:初级、中级
《Java编程思想》 适合对象:初级、中级
《Java数据结构和算法》 适合对象:初级、中级、高级
《Java与模式》 适合对象:中级、高级
《Java并发编程实践》 适合对象:中级、高级
《Effective Java》
《深入理解 Java 虚拟机》
《实战Java高并发程序设计》
《spring 源码深度解析》
《Head First Java》 《Java 编程思想 (第 4 版)》 《实战 Java 高并发程序设计》 《深入理解 Java 虚拟机(第 2 版)周志明》 《重构_改善既有代码的设计》 《代码整洁之道》
https://blog.csdn.net/qq_34337272/article/details/89041249
135.JAVA栈和堆的优缺点
栈的优势:存取速度比堆要快。空间被CPU高效地管理着,内存不会变成碎片。
栈的缺点:存在栈的数据大小和生存期必须是确定的,缺乏灵活性。
堆的优势:可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不在实用的数据。变量可以被全局访问。没有内存大小限制。
堆的缺点:由于运行时要动态分配内存,存取速度慢。没有高效地使用空间,随着块内存的创建和销毁,内存可能会变成碎片。
136.MQ怎么保证 消息的有序性
https://www.jianshu.com/p/02fdcb9e8784
https://www.cnblogs.com/jack1995/p/10908814.html
https://www.jianshu.com/p/cba6b44e2c21
137.Kafka为什么这么快?
优化写入速度Kafka采用了两个技术, 顺序写入 和 MMFile 。
Kafka会把收到的消息都写入到硬盘中,它绝对不会丢失数据。因为硬盘是机械结构,每次读写都会寻址->写入,其中寻址是一个“机械动作”,它是最耗时的。所以硬盘最讨厌随机I/O,最喜欢顺序I/O。为了提高读写硬盘的速度,Kafka就是使用顺序I/O。
- 磁盘顺序读写速度超过内存随机读写
- JVM的GC效率低,内存占用大。使用磁盘可以避免这一问题
- 系统冷启动后,磁盘缓存依然可用
即便是顺序写入硬盘,硬盘的访问速度还是不可能追上内存。所以Kafka的数据并 不是实时的写入硬盘 ,它充分利用了现代操作系统 分页存储 来利用内存提高I/O效率。
Memory Mapped Files(后面简称mmap)也被翻译成 内存映射文件 。它的工作原理是直接利用操作系统的Page来实现文件到物理内存的直接映射。完成映射之后你对物理内存的操作会被同步到硬盘上(操作系统在适当的时候)。
通过mmap,进程像读写硬盘一样读写内存(当然是虚拟机内存),也不必关心内存的大小有虚拟内存为我们兜底。
使用这种方式可以获取很大的I/O提升, 省去了用户空间到内核空间 复制的开销(调用文件的read会把数据先放到内核空间的内存中,然后再复制到用户空间的内存中。)
基于sendfile实现Zero Copy。 传统的文件数据拷贝实际上是经过了四次copy操作: 硬盘—>内核buf—>用户buf—>socket相关缓冲区—>协议引擎 sendfile系统调用则提供了一种减少以上多次copy,提升文件传输性能的方法。sendfile的引入不仅减少了数据复制,还减少了上下文切换。 sendfile(socket, file, len); 运行流程如下: (1)sendfile系统调用,文件数据被copy至内核缓冲区 (2)再从内核缓冲区copy至内核中socket相关的缓冲区 (3)最后再socket相关的缓冲区copy到协议引擎 相较传统read/write方式,2.1版本内核引进的sendfile已经减少了内核缓冲区到user缓冲区,再由user缓冲区到socket相关缓冲区的文件copy,而在内核版本2.4之后,文件描述符结果被改变,sendfile实现了更简单的方式,再次减少了一次copy操作。 Kafka把所有的消息都存放在一个一个的文件中,当消费者需要数据的时候Kafka直接把文件发送给消费者,配合mmap作为文件读写方式,直接把它传给sendfile。 批量压缩 在很多情况下,系统的瓶颈不是CPU或磁盘,而是网络IO,对于需要在广域网上的数据中心之间发送消息的数据流水线尤其如此。进行数据压缩会消耗少量的CPU资源,不过对于kafka而言,网络IO更应该需要考虑。
如果每个消息都压缩,但是压缩率相对很低,所以Kafka使用了批量压缩,即将多个消息一起压缩而不是单个消息压缩 Kafka允许使用递归的消息集合,批量的消息可以通过压缩的形式传输并且在日志中也可以保持压缩格式,直到被消费者解压缩 Kafka支持多种压缩协议,包括Gzip和Snappy压缩协议。
总结
Kafka速度的秘诀在于,它把所有的消息都变成一个批量的文件,并且进行合理的批量压缩,减少网络IO损耗,通过mmap提高I/O速度,写入数据的时候由于单个Partion是末尾添加所以速度最优;读取数据的时候配合sendfile直接暴力输出。
https://www.cnblogs.com/binyue/p/10308754.html
https://www.jianshu.com/p/275602182f39
138.从指定位置读取文件内容
(1)RandomAccessFile的seek()方法 RandomAccessFile r = new RandomAccessFile(new File(“c:/1.txt”, “r”));//只读方式打开文件 r.seek(100);//指定下一次的开始位置
(2)FileInputStream的position()方法 FileInputStream.getChannel().position(123)
(3)FileInputStream的skip()方法 FileInputStream fis = FileInputStream(File file);指定文件 fis.skip(long n);指定位置 byte[] bs = new byte[int length]; 指定长度 fis.read(bs); 得到内容
(4)DataInputStream的skip()方法
DataInputStream dis=new DataInputStream(new FileInputStream(new File(“c:/rr.ifo”))); dis.skip(32);
注意:RandomAccessFile中方法:read(byte[],int off, int len)中,off都是指在byte中的偏移(即开始位置),并不是指从制定位置开始。
(5)RandomAccessFile的skipBytes()方法
skipBytes(int n):跳过n字节的位置,相对于当前的point。
https://bbs.csdn.net/topics/300083048
139.读取超过内存的大文件
思路:大文件切割为小文件,再起固定线程数的线程池逐个处理,如排序等。
注意:面向文件的开发应该尽量全过程采用流式操作的思想,应该直接创建目标文件,然后边读边写,程序里面只需要维护一个不太大的buffer,比如4K或8K。读取一部分,就把这一部分写入新文件,不要都保留在内存里。
利用递归的手法分配调入内存,处理一部分数据释放一部分资源。
https://blog.csdn.net/javageektech/article/details/106774313
140.https原理
https://www.cnblogs.com/sueyyyy/p/12012570.html
141.Mysql死锁原因,怎么解决和避免?
https://www.cnblogs.com/LBSer/p/5183300.html
https://www.cnblogs.com/amunote/p/10354327.html
https://www.jb51.net/article/159737.htm
解决:
https://blog.csdn.net/lqzxpp/article/details/86012208
https://blog.csdn.net/hotdust/article/details/51524469
141.kafka消息丢失、重复消费、缓慢延时大、提高并发的解决方法
消息丢失、重复消费:
https://www.cnblogs.com/guoyu1/p/11994245.html
https://www.cnblogs.com/wangzhuxing/p/10124308.html
https://blog.csdn.net/zjh_746140129/article/details/88779640
https://blog.csdn.net/weixin_44259720/article/details/104844231
https://blog.csdn.net/shaolong1013/article/details/85302877
https://blog.csdn.net/wudaoshihun/article/details/83515355
https://www.jianshu.com/p/63fb9082bb0d
另一条思路:
gzip压缩率要比snappy高,snappy优势在于压缩速度。压缩率高意味着单条数据要小。
消息延时大:
推荐consumer要和partition数量一致,consumer过多,浪费消费线程;过少会造成consumer消费压力过大,产生消息堆积
1、检查producer和consumer所在机器的系统时间有没有误差
2、检查下consumer.poll(maxTime)方法的参数,这个参数是最大等待时间。假如你配置里申明每次取100条消息,但500ms内producer没有生产出100条消息,那么consumer会一直等待maxTime这些时间才会返回结果。假如你系统实时性要求高于吞吐量要求,那么这个参数要设置小一点
在 Kafka0.9 版本之后,消费进度被迁入到 Kakfa 的一个专门的 topic 叫“__consumer_offsets”里面。
当然,作为一个成熟的组件,Kafka 也提供了一些工具来获取这个消费进度的信息帮助我们实现自己的监控,这个工具主要有两个:
(1)Kafka 提供了工具叫做“kafka-consumer-groups.sh”(它在 Kafka 安装包的 bin 目录下)。
- 前两列是队列的基本信息,包括topic名和分区名;
- 第三列是当前消费者的消费进度;
- 第四列是当前生产消息的总数;
- 第五列就是消费消息的堆积数(也就是第四列与第三列的差值)。
(2)第二个工具是JMX
Kafka 通过 JMX 暴露了消息堆积的数据,然后我们就可以通过写代码将这个堆积数据发送到我们的监控系统中去。
一般来说有几类
1.增加分区
2.关闭autocommit(偏移量手工提交可以按需减少分区偏移量的更新,有利于提升消费速度)
3.增加单次拉取消息的大小(大量消息的场景下可减少拉取消息的次数)
比较另类的:
1.如果不考虑数据一致性,可以将key值平均一下,这样每个分区的消息大小都差不多,有利于负载均衡
2.如果没有开启压缩,最好开启压缩(需要重启集群),可大大提高通信效率,有得消费速度提升
对于单partition的消费线程,增加一个固定长度的阻塞队列和工作线程池进一步提高并行消费的能力
https://www.cnblogs.com/blue-rain/p/12430128.html
提高并发:
https://www.cnblogs.com/barrywxx/p/11544379.html
同时kafka也提供了相关的配置参数,来让你在性能与可靠性之间权衡(一般默认):
#当达到下面的消息数量时,会将数据flush到日志文件中。默认10000
log.flush.interval.messages=10000
#当达到下面的时间(ms)时,执行一次强制的flush操作。interval.ms和interval.messages无论哪个达到,都会flush。默认3000ms
log.flush.interval.ms=1000
#检查是否需要将日志flush的时间间隔
log.flush.scheduler.interval.ms = 3000
142.dubbo协议为什么采用异步单一长连接
因为服务的现状大都是服务提供者少,通常只有几台机器, 而服务的消费者多,可能整个网站都在访问该服务, 比如Morgan的提供者只有6台提供者,却有上百台消费者,每天有1.5亿次调用, 如果采用常规的hessian服务,服务提供者很容易就被压跨, 通过单一连接,保证单一消费者不会压死提供者, 长连接,减少连接握手验证等, 并使用异步IO,复用线程池,防止C10K问题。
【C10K问题(即单机1万个并发连接问题),”10 thousand clients” problem】
Dubbo 缺省协议采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。反之,Dubbo 缺省协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低。
适用范围:传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用 dubbo 协议传输大文件或超大字符串。 适用场景:常规远程服务方法调用
143.volatile实现禁止指令重排底层操作原理
https://blog.csdn.net/a772304419/article/details/102878409
https://www.cnblogs.com/bbgs-xc/p/12731769.html
144.对象的创建过程
对象的创建大概分为以下几步:
1:检查类是否已经被加载;
2:为对象分配内存空间;
3:为对象字段设置零值;
4:设置对象头;
5:执行构造方法。
https://blog.csdn.net/fly_rice/article/details/82354188
144.0 类的加载过程
类的生命周期:
(1)加载
类的加载指的是把class文件从磁盘读入内存中,将其放入元数据区域并且创建一个Class对象,放入堆中,Class对象是类加载的最终产品,Class对象并不是new出来的对象。
元数据区域存储的信息:
- 这个类型的完整有效名
- 这个类型的直接父类完整有效名
- 这个类型的修饰符(public final abstract等)
- 这个类型的直接接口的列表
Class对象中包含的如下信息,这也是我们能够通过Class对象获取类的很多信息的原因:
- 类的方法代码,方法名,字段等
- 类的返回值
- 类的访问权限
加载class文件有很多种方式,可以从磁盘上读取,可以从网络上读取,可以从zip等归档文件中读取,可以从数据库中读取
(2)验证
验证的目的是验证class文件的正确性,是否能够被当前JVM虚拟机执行,主要包含了一些部分验证,验证非常重要,但不是必须的(正常情况下都是正确的) 文件格式验证:比如JDK8加载的是JDK6下编译的class文件,这肯定不行。 元数据验证:确保字节码描述信息符合Java语言规范的要求,你理解为校验外壳,比如类中是否实现了接口的所有方法。 字节码验证:确定程序语义执行是合法的,校验内在,校验方法体,防止字节码执行过程中危害JVM虚拟机。 符合引用验证:其对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验,比如:符号引用中的类、字段、方法的访问性是否可被当前类访问,通过全限定名,是否能找到对应的类。
(3)准备 (为类变量(静态变量) 分配内存)
验证完成之后,JVM就开始为类变量(静态变量) 分配内存,设置初始化值, 记住两点
- 不会为成员变量分配内存的。
- 初始化值是指JVM默认的指,不是程序中指定的值。
//类变量,初始化值是 null, 不是123
public static String s1 = "123"
//成员变量
public String s2 = "456"
但有一个特殊,如果一个类变量是final修饰的常量,那么在准备阶段就会被赋值为程序中指定的值,如下代码,初始值是123
//初始值是123,不是null
public static final String s1 = "123"
(4)解析 (将常量池中的符号引用转换为直接引用)
解析阶段主要是将常量池中的符号引用转换为直接引用,解析动作主要包含类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用。
符号引用包括什么呢?
- 类和方法的全限定名
- 字段的名称和描述符
- 方法的名称和描述符,
直接引用是是什么呢?一个指向目标的指针地址或者句柄。
(5)初始化 (执行类构造器)
初始化阶段用户定义的Java代码才会真正开始执行,一般来说当首次主动使用某个类的时候就会对该类初始化,初始化某个类时也会初始化这个类的父类。这里的首次主动使用,大家要理解清楚了,第二次使用时不会初始化的。类的初始化其实就是执行类构造器的过程,这个不是我们代码定义的构造方法。
https://www.cnblogs.com/sy270321/p/12258421.html
注:类的加载过程是(1)—(5)
(6)使用
使用就比较简单了,JVM初始化完成后,就开始按照顺寻执行用户代码了。
(7)卸载
类卸载有个前提,就是class的引用是空的,要么程序中手动置为空,要么进程退出时JVM销毁class对象,然后JVM退出。只要class引用不存在,那么这个类就可以回收了。
144.1 对象在内存中的布局
在HotSpot虚拟机中,对象在内存中的布局分为三块:对象头、示例数据、对齐填充。
1.对象头 包括两部分:Mark Word、类型指针、数组长度(if 数组对象) (1)Mark Word:存储对象自身的运行时数据(哈希码、GC年龄、锁标志、持有的锁等)。被设计成非固定数据结构,根据对象状态占用内部空间。 (2)类型指针:对象指向它的类元数据的指针。虚拟机通过这个指针确定对象是哪个类的实例。 (3)数组长度:对于数组对象,对象头中必须有一块数据记录数组长度,因为JVM无法从数组的元数据确定数组的大小。
2.实例数据。 实例数据是对象真正存储的有效信息,就是代码中定义的各种类型的字段内容,包括从父类继承下来的和子类中定义的。
3.对齐填充 HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍。当对象的实例数据部分没有对齐,用对齐填充补全。所以对齐填充不是必然存在的。
144.2 DCL单例为什么要加volatile
https://www.cnblogs.com/codingmengmeng/p/9846131.html
144.3 对象怎么定位
(1)句柄访问。Java堆中划分出一块句柄池,obj指向的是对象的句柄地址,句柄中包含了类数据地址和实例数据地址,这样做有什么好处呢,我们都知道会整理内存的GC算法需要改变对象的内存地址,如果使用句柄访问方式时只需要改变句柄池中执行的实例地址就行了;缺点是查找起来慢。 (2)指针访问。Obj直接指向,实例数据地址和类数据地址。好处:快速访问;缺点在整理内存时,需要改变obj的指向。HotSpot采用的指针访问的方式。
https://blog.csdn.net/wodemale/article/details/89365631
144.4 对象怎么分配的
https://www.cnblogs.com/lanmao123/p/10485416.html
https://blog.csdn.net/weixin_33877885/article/details/94172835
https://www.cnblogs.com/linxiong/p/4527865.html
144.5 类初始化时机
只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:
(1)创建类的实例,也就是new的方式;
(2)访问某个类或接口的静态变量,或者对该静态变量赋值;
(3)调用类的静态方法;
(4)反射(如 Class.forName(“com.shengsiyuan.Test”));
(5)初始化某个类的子类,则其父类也会被初始化;
(6)Java虚拟机启动时被标明为启动类的类( JavaTest),直接使用 java.exe命令来运行某个主类
145.Object o = new Object()在内存中占了多少字节
16字节
markword 8字节,因为java默认使用了calssPointer压缩,classpointer 4字节,padding 4字节 因此是16字节 如果没开启classpointer默认压缩,markword 8字节,classpointer 8字节,padding 0字节 也是16字节
User (int id,String name) User u = new User(1,‘张三’);占用多少字节 markword 8字节,开启classPointer压缩 ,classpointer 4字节,instance data int 4字节,开启普通对象指针压缩 String 4字节 padding 4 一共24字节
参考:https://blog.csdn.net/u011727756/article/details/106546178/
字节数=对象数*16
JDK是64位,8字节是引用,16字节是堆内存,总共是8+16=24字节,所以new一个Object对象占用24字节。如果JDK是32位,则new一个Object对象占用4+16=20字节。
146.as-if-serial和happens-before语义
happens-before规则如下:
程序顺序规则: 对于单个线程中的每个操作,前继操作happens-before于该线程中的任意后续操作。 监视器锁规则: 对一个锁的解锁,happens-before于随后对这个锁的加锁。 volatile变量规则: 对一个volatile域的写,happens-before于任意后续对这个volatile域的读。 传递性: 如果A happens-before B,且B happens-before C,那么A happens-before C。
数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。
as-if-serial语义的意思是:不管怎么重排序,单线程程序的执行结果不能被改变。
在单线程中,编译器和处理器不会对存在数据依赖关系
的操作做重排序,因为这种重排序会改变执行结果。
在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果。 在多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果。
https://blog.csdn.net/byhook/article/details/87971081
147.LocalThread如何解决内存泄漏问题
产生原因:
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。
作为引用对象的 ThreadLocal,就有可能会被Entry清除引用。如果这时候 ThreadLocal没有其他的引用,那么它肯定就会被GC回收了。
但是value 是强引用,而Entry 又被Entry[]持有,Entry[]又被ThreadLocalMap持有,ThreadLocalMap又被线程持有。只要线程不死或者 你不调用set,remove这两个方法之中任何一个,那么value指向的这个对象就始终 不会被回收。因为 不符合GC回收的两个条件的任何一个。
试想一下如果线程池里面的线程足够的多,并且 你传给线程的本地副本内存占用又很大。毫无疑问 会内存溢出。
解决方法:
只要调用remove 这个方法会擦除上一个value的引用,这样线程就不会持有上一个value指向对象的引用。就不会有内存露出了。
即ThreadLocal.set()方法之后一定要记得使用ThreadLocal.remove(),将不要的数据移除掉,避免内存泄漏。
147.1为什么要将ThreadLocal 定义成 static 变量
延长生命周期,之所以是static 是因为,ThreadLocal 我们更应该将他看成是 工具。
https://blog.csdn.net/puppylpg/article/details/80433271
148.UML关系图
关系类型 | 说明 | 符号图示 |
---|---|---|
关联(Association) | 拥有的关系。箭头指向被拥有者。 双向的关联可以有两个箭头或者没有箭头,单向的关联有一个箭头。 |
↗ ↕ |
依赖(Dependency) | 使用的关系。箭头指向被使用者。 | |
实现(Realization) | 类与接口的关系。箭头指向接口。 | |
泛化(Generalization) | 继承关系。箭头指向父类。 | |
聚合(Aggregation) | 整体与部分的关系。空心菱形箭头指向整体。 | |
组合(Composition) | 整体与部分的关系。实心菱形箭头指向整体。 |
各种关系的强弱顺序: 泛化 = 实现 > 组合 > 聚合 > 关联 > 依赖
https://blog.csdn.net/iteye_15118/article/details/82607694
https://blog.csdn.net/qq_35495763/article/details/80764914
149.怎么判断内存泄漏
(1)可以使用Linux环境下的内存泄漏检查工具Valgrind;
(2)写代码的时候,可以添加内存申请和释放的统计功能,统计当前申请和释放的内存是否一致,以此来判断内存是否有泄漏。
(3)首先在Java命令行中增加-verbose:gc参数
(4)当系统运行过程中,JVM进行垃圾回收的时候,会将垃圾回收的日志打印出来,通过分析这些GC日志,我们可以初步判断系统是否存在堆内存泄漏。
(5)VisualVM是一种工具,它提供了一个可视化界面,用于查看有关基于Java技术的应用程序运行时的详细信息。
使用VisualVM,您可以查看与本地应用程序和远程主机上运行的应用程序相关的数据。
(6)HPROF是一个与Java 2平台标准版(J2SE)捆绑在一起的简单命令行工具,用于堆和CPU分析。可以直接分析HPROF的输出,或将其用作JHAT等其他工具的输入。
(7)Linux命令Svmon列出消耗物理内存前十的进程
svmon -Pt10 | perl -e 'while(<>){print if($.==2||$&&&!$s++);$.=0 if(/^-+$/)}'
(8)linux命令ps
ps aux | head -1 ; ps aux | sort -rn +4 | head -10
linux命令使用详情查看:https://www.sohu.com/a/209815068_151779
按照占用物理内存的百分比排序,列出前十个进程。
只有FULL GC的行才有分析价值。
(a) 如果完全垃圾回收后的内存持续增长32,大有一直增长到Xmx设定值的趋势,那么这 个时候基本上就可以断定系统存在内存泄漏。
(b) 如果当前完全垃圾回收后内存增长到一个值之后,又能回落,总体上处于一个动态平衡,那么内存泄漏基本可以排除。 通过如上内存使用趋势分析之后,基本上就能确定系统是否存在堆内存泄漏。
https://wangkang007.gitbooks.io/jvm/content/4jvmdiao_you.html
150.JVM内存布局
在JDK8中,使用元空间代替永久代。jdk1.7及以前的堆中的方法区,在jdk1.8中被提出堆放在单独的内存空间,并重命名为元空间。
区别于永久代,元空间在本地内存中分配,永久代中的所有内容中字符串常量移至堆内存,其他内容包括类元信息、字段、静态属性、方法、常量等都移至元空间内。
JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
常量池可以分为 Class文件常量池、运行时常量池、字符串常量池。
https://blog.csdn.net/qq_45349785/article/details/106826226
方法区(元空间)与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
https://www.cnblogs.com/aflyun/p/10575740.html
https://blog.csdn.net/u011212394/article/details/85566710
https://blog.csdn.net/weixin_37817685/article/details/89071055
150.1 JVM 参数有哪些
-server Xms6000M -Xmx6000M -Xmn500M -XX:PermSize=500M -XX:MaxPermSize=500M -XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0 -Xnoclassgc -XX:+DisableExplicitGC -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled -XX:-CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=90 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:log/gc.log
151.synchronized和reentrantlock的底层实现及重入的底层原理
https://blog.csdn.net/lvxinchun/article/details/107002689
152.G1什么时候执行GC
如果mixed gc没法跟上应用分配内存的速度,导致old gen用满无法再执行mixed gc的话,就会触发full gc,full gc使用serial old gc对整个堆进行回收。
分代G1选定CSet的模式(young GC & mixed GC) Young GC:选定范围—整个young gen中的region,通过控制选定的region来控制young gc的开销。 Mixed GC: 选定范围—整个young gen+外加在global concurrent marking统计出的收益最高的N个old gen region。
G1会根据实际情况自动选择执行young gc或mixed gc,然后会定期执行全局并发标记。初始标记会搭载在young gc中完成,执行全局标记阶段过程中,不会执行mixed gc,反之也成立。如果mixed gc没法跟上应用分配内存的速度,导致old gen用满无法再执行mixed gc的话,就会触发full gc,full gc使用serial old gc对整个堆进行回收。
G1 GC如何实现低延时的对象回收的?
答案就在G1 GC每次只选择收集收益最高的那些region,保证GC过程开销在一个可控的范围内。
evacuate拷贝对象的过程需要暂停整个应用,暂停的时间可以通过-XX:MaxGCPauseMillis
来控制。但一般不建议设置太短,过短的暂停时间,会导致mixed gc回收无法跟上应用分配对象的速度,最终可能会引发full gc的噩耗,经验之谈建议这个值设定在100ms~250ms以内。
https://www.jianshu.com/p/2e80d2173666
152.1 CMS和G1区别
区别一: 使用范围不一样 CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;
由于G1收集器对堆区进行划分,所以G1收集器收集范围是老年代和新生代。不需要结合其他收集器使用
区别二: STW的时间 CMS收集器以最小的停顿时间为目标的收集器。
G1收集器可预测垃圾回收的停顿时间(建立可预测的停顿时间模型)
区别三: 垃圾碎片 CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片
G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。
区别四: 垃圾回收的过程不一样 CMS收集器 :初始标记、并发标记、重新标记、并发清除
G1收集器:初始标记、并发标记、最终标记、筛选回收
153.除了CAS,原子类,syn,Lock还有什么线程安全的方式
volatile、LocalThread、SynchronousQueue、Semaphore s = new Semaphore(1);
多线程编程的三个核心概念:原子性(atomicity)、可见性(visibility)、顺序性
154.HashMap和Hashtable的区别
https://blog.csdn.net/xuhuaabc/article/details/91475761
155.TreeMap的键key是否可以为null
- 当未实现 Comparator 接口时,key 不可以为null,否则抛 NullPointerException 异常;
- 当实现 Comparator 接口时,若未对 null 情况进行判断,则可能抛 NullPointerException 异常。如果针对null情况实现了,可以存入,但是却不能正常使用get()访问,只能通过遍历去访问。
https://blog.csdn.net/u012156116/article/details/81073570
156.CopyOnWriteArrayList的迭代器支持fail-fast吗
不支持。
CopyOnWriteArrayList可以解决ArrayList的fail-fast的问题。CopyOnWriteArrayList迭代器是fail-safe的。
Iterator fail-fast属性检查当前集合结构里的任何改动。如果发现任何改动,它抛出ConcurrentModificationException。
与 fail-fast 相对应的,就是 fail-safe 机制;在J.U.C包中集合都是有这种机制实现的。
fail-safe 指的是:在安全的副本(或者没有提供修改操作的正本)上进行遍历,集合修改和副本的遍历是没有任何关系的,但是缺点也很明显,就是读取不到最新的数据。
CopyOnWriteArrayList:
- 修改代价大,可以从源码知道,remove还是add方法,都会进行一次数组的复制,这样消耗了空间(可能导致gc的频率提高)也消耗了时间
- 读写分离,读写不一致,读的时候读的是旧的数组,写的时候写的是新的数组,所以读的时候不一定是最新的
- 读的时候不需要进行加锁,因为写的时候是写在新的数组,读的数组是旧的数组,并不会改变
- 因此,CopyOnWriteArrayList适合读多写少的场景
https://blog.csdn.net/j080624/article/details/82692326
Object[] arrayOfObject2 = Arrays.copyOf(arrayOfObject1, i + 1);
arrayOfObject2[i] = paramE;
setArray(arrayOfObject2);
就是这三句代码使得CopyOnWriterArrayList不会抛ConcurrentModificationException异常。他们所展现的魅力就在于copy原来的array,再在copy数组上进行add操作,这样做就完全不会影响COWIterator中的array了。
157.进程间通信有哪些
管道:通常指无名管道,是 UNIX 系统IPC最古老的形式。 FIFO:也称为命名管道,它是一种文件类型。 消息队列:是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。 信号量(semaphore):一个计数器,用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。 共享内存(Shared Memory):两个或多个进程共享一个给定的存储区。
https://www.cnblogs.com/zgq0/p/8780893.html
进程间通信主要分类编辑进程间通信主要包括管道,系统IPC(包括消息队列,信号,共享存储),套接字(SOCKET)。 管道包括三种: 1)普通管道PIPE,通常有两种限制,一是单工,只能单向传输;二是只能在父子或者兄弟进程间使用。 2)流管道s_pipe:去除了第一种限制,为半双工,可以双向传输。 3)命名管道:name_pipe,去除了第二种限制,可以在许多并不相关的进程之间进行通讯。 进程间通信识别系统IPC的三种方式类同,都是使用了内核里的标识符来识别。
https://iask.sina.com.cn/b/iRWXJuucpLXh.html
https://www.cnblogs.com/williamjie/p/11150740.html
158.os中管道
https://segmentfault.com/a/1190000009528245
https://blog.csdn.net/deniece1/article/details/102843474
159.分段和分页
https://blog.csdn.net/qq_37924084/article/details/78360003
159.1 物理地址、逻辑地址、线性地址、虚拟地址、有效地址
https://blog.csdn.net/mzjmzjmzjmzj/article/details/84713351
160.为什么三次握手、四次挥手?
160.1 为什么是三次握手?
主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。
如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,服务器等待发送数据或主动发送数据。但此时的客户端早已进入CLOSED状态,服务端将会一直等待下去,这样浪费服务端连接资源。
如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。
160.2 为什么四次挥手
关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。
白话解释四次挥手:
第一次挥手:FIN=1,seq=u
客户端发送关闭连接的请求:我客户端数据发完了,不再向你服务器发数据了,我要关闭连接了
第二次挥手:ACK=1,seq=v,ack=u+1
服务器发给客户端确认请求:我知道了,但我还有些数据没发完,等我全部发给你,我也关闭连接
第三次挥手:FIN=1,ACK=1,seq=w,ack=u+1
服务器发给客户端完成发送的请求:经过一段时间努力,终于我的数据也全发给你了,我要关闭连接了
第四次挥手:ACK=1,seq=u+1,ack=w+1
客户端发给服务器确认请求:好的,我知道了。
客户端延时关闭连接:客户端等一小会(2MSL)等服务器收到上条消息,然后直接自动关闭了连接,否则重说一次”好的,我知道了”。
为什么要第四次挥手?
原因和三次握手时需要第三次握手的原因类似。
为什么客户端发送 ACK 之后不直接关闭,而是要等一阵子才关闭。这其中的原因就是,要确保服务器是否已经收到了我们的 ACK 报文,如果没有收到的话,服务器会重新发 FIN 报文给客户端,客户端再次收到 ACK 报文之后,就知道之前的 ACK 报文丢失了,然后再次发送 ACK 报文。
为了保证客户端发送的最后一个ACK报文段能够到达服务器。因为这个ACK有可能丢失,从而导致处在LAST-ACK状态的服务器收不到对FIN-ACK的确认报文。服务器会超时重传这个FIN-ACK,接着客户端再重传一次确认,重新启动时间等待计时器。最后客户端和服务器都能正常的关闭。假设客户端不等待2MSL,而是在发送完ACK之后直接释放关闭,一但这个ACK丢失的话,服务器就无法正常的进入关闭连接状态。
160.3 四次挥手释放连接时,等待2MSL的意义?
两个理由:
- 保证客户端发送的最后一个ACK报文段能够到达服务端。
这个ACK报文段有可能丢失,使得处于LAST-ACK状态的B收不到对已发送的FIN+ACK报文段的确认,服务端超时重传FIN+ACK报文段,而客户端能在2MSL时间内收到这个重传的FIN+ACK报文段,接着客户端重传一次确认,重新启动2MSL计时器,最后客户端和服务端都进入到CLOSED状态,若客户端在TIME-WAIT状态不等待一段时间,而是发送完ACK报文段后立即释放连接,则无法收到服务端重传的FIN+ACK报文段,所以不会再发送一次确认报文段,则服务端无法正常进入到CLOSED状态。
- 防止“已失效的连接请求报文段”出现在本连接中。
客户端在发送完最后一个ACK报文段后,再经过2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文段。
https://www.cnblogs.com/guchengnan/p/12160597.html
161.防范CSRF攻击
CSRF(Cross-site request forgery)也被称为 one-click attack或者 session riding,中文全称是叫跨站请求伪造。
加密令牌:web应用程序可以在网页中嵌入一个加密的令牌,所有的请求都包含这个加密令牌,由于跨站请求无法获取这个令牌,所以伪造的请求很容易就被服务器识别;
https://www.cnblogs.com/lr393993507/p/9834856.html
https://www.cnblogs.com/lr393993507/p/9834856.html
https://www.cnblogs.com/wf-skylark/p/9305500.html
https://www.jianshu.com/p/1573c6ff8635
162 mysql中where与having的区别
1.where是一个约束声明,使用where约束来自数据库的数据,where是在结果集返回之前起作用的,where中不能使用聚合函数。注意:返回结果集之前起作用 2.having是一个过滤声明,是在查询返回结果集以后对查询结果进行过滤操作,在Having中可以使聚合函数。注意:返回结果集之后起作用 3.在查询过程中where子句、聚合语句、having子句,的执行优先级为where>group by>聚合语句(sum、count、avg、max、min)>having子句
163.分库分表带来的问题
问题一:跨库关联查询
解决方法:字段冗余、数据同步、全局表(广播表)、ER 表(绑定表)、系统层组装
问题二:全局主键避重问题
解决方法:UUID、数据库、redis、雪花算法Snowflake(64bit)、
问题三: 排序、翻页、函数计算问题
需要在两个节点上各取出10 条,然后合并数据,重新排序。
问题四:分布式事务
(1)全局事务(比如XA 两阶段提交;应用、事务管理器(TM)、资源管理器(DB)),
(2)基于可靠消息服务的分布式事务
(3)柔性事务TCC(Try-Confirm-Cancel)tcc-transaction
(4)最大努力通知,通过消息中间件向其他系统发送消息(重复投递+定期校对)
https://blog.csdn.net/tianmingwei/article/details/103372466
https://www.jianshu.com/p/32b3e91aa22c
https://www.cnblogs.com/dinglang/p/6084306.html
164.Java SE与Java EE的区别
JavaSE:
Java SE 以前称为 J2SE,Java Standard Edition,Java标准版。它允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的 Java 应用程序。Java SE 包含了支持 Java Web 服务开发的类,并为 Java Platform,Enterprise Edition(Java EE)提供基础。
JavaEE:
例如 : 人们常说的SSH =Spring+Struts+Hibernate架构应用整合开发,XML,EJB,WebService,UML/Rose,Ajax,Weblogic,Oracle Java Enterprise Edition,Java企业版,多用于企业级开发,包括web开发等等。企业版本帮助开发和部署可移植、健壮、可伸缩切安全的服务端Java应用。Java EE是在JavaSE的基础上构建的他提供Web 服务、组建模型、管理和通信API.可以用来实现企业级的面向服务体系结构(service-oriented architecture,SOA)和web2.0应用程序。
JavaWeb :
例如 :J DBC,JSP,Servlet,JavaBean,Html,JavaScript,Session/Cookie,MVC设计模式,Tomcat,Eclipse+MyEclipse 是指使用Java体系开发网站类应用,JSP属于Java Web范畴,JSP可以简单看作是前端页面嵌入Java代码,会被容器编译成Servlet,然后Servlet会输出HTML代码,最终成为我们看到的页面。
https://www.cnblogs.com/lsgxeva/p/10183606.html
165.CMS的并发预处理和并发可中断预处理
Rescan阶段(remark阶段的一个子阶段)会扫描新生代和老年代中的对象。
此阶段标识为Rescan (parallel),说明此阶段是并行进行的。全量的扫描新生代和老年代会很慢。
CMS号称是停顿时间最短的GC,如此长的停顿时间肯定是不能接受的。
新生代中对象的特点是“朝生夕灭”,这样如果Remark前执行一次Minor GC,大部分对象就会被回收。CMS就采用了这样的方式,在Remark前增加了一个可中断的并发预清理(CMS-concurrent-abortable-preclean),该阶段主要工作仍然是并发标记对象是否存活,只是这个过程可被中断。此阶段在Eden区使用超过2M时启动,当然2M是默认的阈值,可以通过参数修改。如果此阶段执行时等到了Minor GC,那么上述灰色对象将被回收,Reamark阶段需要扫描的对象就少了。
新生代的并发预处理和可中断预处理:
解决方法:在扫描新生代前进行一次Minor GC。
CMS 有两个参数:CMSScheduleRemarkEdenSizeThreshold、CMSScheduleRemarkEdenPenetration,默认值分别是2M、50%。两个参数组合起来的意思是预清理后,eden空间使用超过2M时启动可中断的并发预清理(CMS-concurrent-abortable-preclean),直到eden空间使用率达到50%时中断,进入remark阶段。
可终止的预清理要执行多长时间来保证发生一次Minor GC? 答案是没法保证。道理很简单,因为垃圾回收是JVM自动调度的,什么时候进行GC我们控制不了。
CMS提供了一个参数CMSMaxAbortablePrecleanTime ,默认为5S。
只要到了5S,不管发没发生Minor GC,有没有到CMSScheduleRemardEdenPenetration都会中止此阶段,进入remark。
如果在5S内还是没有执行Minor GC怎么办?
CMS提供CMSScavengeBeforeRemark参数,使remark前强制进行一次Minor GC。
这样做利弊都有。好的一面是减少了remark阶段的停顿时间;坏的一面是Minor GC后紧跟着一个remark pause。如此一来,停顿时间也比较久。
实际上为了减少remark阶段的STW时间,预清理阶段会尽可能多做一些事情来减少remark停顿时间。
remark的rescan阶段是多线程的,为了便于多线程扫描新生代,预清理阶段会将新生代分块。
每个块中存放着多个对象,这样remark阶段就不需要从头开始识别每个对象的起始位置。
多个线程的职责就很明确了,把分块分配给多个线程,很快就扫描完。
遗憾的是,这种办法仍然是建立在发生了Minor GC的条件下。
如果没有发生Minor GC,top(下一个可以分配的地址空间)以下的所有空间被认为是一个块(这个块包含了新生代大部分内容)。
这种块对于remark阶段并不会起到多少作用,因此并行效率也会降低。
老年代的并发预处理:
老年代的机制与一个叫CARD TABLE的东西(这个东西其实就是个数组,数组中每个位置存的是一个byte)密不可分。
CMS将老年代的空间分成大小为512bytes的块,card table中的每个元素对应着一个块。
并发标记时,如果某个对象的引用发生了变化,就标记该对象所在的块为 dirty card。
并发预清理阶段就会重新扫描该块,将该对象引用的对象标识为可达。
随后到了pre-cleaning阶段,那些通过current obj变得可达的对象也被标记了。同时dirty card标志也被清除。
https://www.cnblogs.com/Leo_wl/p/5393300.html
166.为什么需要使用反射创建对象
(1)反射的目的就是为了扩展未知的应用
当你做一个软件可以安装插件的功能,你连插件的类型名称都不知道,你怎么实例化这个对象呢? 因为程序是支持插件的(第三方的),在开发的时候并不知道 。所以,无法在代码中 New出来 ,但反射可以,通过反射,动态加载程序集,然后读出类,检查标记之后再实例化对象,就可以获得正确的类实例。
(2)在编码阶段不知道那个类名,要在运行期从配置文件读取类名,
这时候就没有办法硬编码,new ClassName(),而必须用到反射才能创建这个对象。
(3)增加程序的灵活性,避免将程序写死到代码里,
实例化一个 person()对象, 不使用反射, new person(); 如果想变成 实例化 其他类, 那么必须修改源代码,并重新编译。 使用反射: class.forName(“person”).newInstance(); 而且这个类描述可以写到配置文件中,如 **.xml, 这样如果想实例化其他类,只要修改配置文件的”类描述”就可以了,不需要重新修改代码并编译。
(4)非abstract, interface 这类不能直接new的对象
demo.newInstance(); 要求demo类必须非abstract, interface 这类不能直接new的, 还必须要提供无参公有构造方法,而 new Person()这种写法在编译期就可以避免上述问题, 因此:如果你只有在运行时才能知道具体的类,那你只有使用class.newInstance();
167.在Java的反射中,Class.forName和ClassLoader的区别
Class.forName加载类时将类进了初始化,而ClassLoader的loadClass并没有对类进行初始化,只是把类加载到了虚拟机中。
Class.forName除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。
而classloader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。 forName(““)得到的class是已经初始化完成的
loadClass(““)得到的class是还没有连接的。一般情况下,这两个方法效果一样,都能装载Class。但如果程序依赖于Class是否被初始化,就必须用Class.forName(name)了。最重要的区别是 forName 会初始化Class,而 loadClass 不会。因此如果要求加载时类的静态变量被初始化或静态块里的代码被执行就只能用 forName,而用 loadClass 只有等创建类实例时才会进行这些初始化。
168.动态代理的几种实现方式,分别说出相应的优缺点。
动态代理实现有三种方式,jdk动态代理(基于接口),cglib动态代理(基于继承),javassist(hibernate中使用这种方式)实现动态代理
(1)实现InvocationHandler
(2)cglib动态代理实现
Cglib是一个优秀的动态代理框架,它的底层使用ASM在内存中动态的生成被代理类的子类,使用CGLIB即使代理类没有实现任何接口也可以实现动态代理功能。CGLIB具有简单易用,它的运行速度要远远快于JDK的Proxy动态代理。
(3)Javassist是一个开源的分析、编辑和创建Java字节码的类库
它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
优缺点:
- JDK原生动态代理是Java原生支持的,不需要任何外部依赖,但是它只能基于接口进行代理(需要代理的对象必须实现于某个接口)
- CGLIB通过继承的方式进行代理(让需要代理的类成为Enhancer的父类),无论目标对象有没有实现接口都可以代理,但是无法处理final的情况。
- Javassist其实现相当地麻烦在创造的过程中,含有太多的业务代码。我们使用上述创建Proxy代理类的方式的初衷是减少系统代码的冗杂度,但是上述做法却增加了在动态创建代理类过程中的复杂度:手动地创建了太多的业务代码,并且封装性也不够,完全不具有可拓展性和通用性。如果某个代理类的一些业务逻辑非常复杂,上述的动态创建代理的方式是非常不可取的!
https://blog.csdn.net/weixin_34187862/article/details/85608912
168.1 为什么CGlib方式可以对接口实现代理?
CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
JDK动态代理的原理是根据定义好的规则,用传入的接口创建一个新类,这就是为什么采用动态代理时为什么只能用接口引用指向代理,而不能用传入的类引用执行动态类。
CGLib采用的是用创建一个继承实现类的子类,用asm库动态修改子类的代码来实现的,所以可以用传入的类引用执行代理类。CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。
168.2 Cglib动态代理执行代理方法效率之所以比JDK的高
Cglib动态代理执行代理方法效率之所以比JDK的高是因为Cglib采用了FastClass机制。
FastClass的原理简单来说就是:为代理类和被代理类各生成一个Class,这个Class会为代理类或被代理类的方法分配一个index(int类型)。这个index当做一个入参,FastClass就可以直接定位要调用的方法直接进行调用,这样省去了反射调用,所以调用效率比JDK动态代理通过反射调用高。
FastClass并不是跟代理类一块生成的,而是在第一次执行MethodProxy invoke/invokeSuper时生成的并放在了缓存中。
168.3 JDK动态代理和Gglib动态代理的区别
1.JDK动态代理是实现了被代理对象的接口,Cglib是继承了被代理对象。 2.JDK和Cglib都是在运行期生成字节码,JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。 3.JDK调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。
- JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。
- CGLib即可对接口又可对非接口进行代理。JDK动态代理只能对实现了接口的类生成代理,而不能针对普通类。
- 由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。
https://www.cnblogs.com/sandaman2019/p/12636727.html
169.泛型
消除向下转型的问题。类在进行定义的时候可以使用一个标记,此标记就表示类中属性或者方法以及参数的类型,标记在使用的时候,才会去动态的设置类型。
泛型主要针对向下转型时所带来的安全隐患,其核心组成是在声明类或接口时,不设置参数或属性的类型。
泛型的好处是在编译时检查类型安全,并且所有的强制转换都是自动和隐式的,提高了代码的重用率,避免在运行时出现 ClassCastException。
好处: 1.类型安全。 泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。 2.消除强制类型转换。消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。 3.潜在的性能收益。 泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强制类型转换)时所写的代码,只是更能确保类型安全而已。
Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
泛型在使用中还有一些规则和限制: 1、泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。 2、同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。 3、泛型的类型参数可以有多个。 4、泛型的参数类型可以使用extends语句,例如。习惯上成为“有界类型”。 5、泛型的参数类型还可以是通配符类型。例如Class<?> classType = Class.forName(Java.lang.String);
170.Timer的schedule和scheduleAtFixedRate方法的区别
(1)schedule方法:“fixed-delay”;如果第一次执行时间被delay了,随后的执行时间按 照 上一次 实际执行完成的时间点 进行计算 (2)scheduleAtFixedRate方法:“fixed-rate”;如果第一次执行时间被delay了,随后的执行时间按照 上一次开始的 时间点 进行计算。
https://blog.csdn.net/gtuu0123/article/details/6040159
171. redis如何实现延时队列?
使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。
172.后台系统怎么防止请求重复提交?
可以通过token值进行防止重复提交,存放到redis中,在表单初始化的时候隐藏在表单中,添加的时候在移除。判断这个状态即可防止重复提交。
173.Enumeration和Iterator的区别
(1) 函数接口不同 Enumeration 只有2个函数接口。 通过Enumeration,我们只能读取集合的数据,而不能对数据进行修改。 Iterator 只有3个函数接口。 Iterator除了能读取集合的数据之外,也能数据进行删除操作。
(2) Iterator 支持 fail-fast 机制,而 Enumeration 不支持 Enumeration 是JDK 1.0添加的接口。使用到它的函数包括Vector、Hashtable等类,这些类都是JDK 1.0中加入的,Enumeration存在的目的就是为它们提供遍历接口。Enumeration本身并没有支持同步,而在Vector、Hashtable实现Enumeration时,添加了同步。
而Iterator 是JDK 1.2才添加的接口,它也是为了HashMap、ArrayList等集合提供遍历接口。Iterator是支持fail-fast机制的:当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。所以说iterator 是安全的。
(3)Enumeration 与 iterator 都是迭代输出的方法,Enumeration先进后出,iterator先进先出。
java中的集合类都提供了返回Iterator的方法,就是迭代器,它和Enumeration的主要区别其实就是Iterator可以删除元素,但是Enumration却不能。使用Iterator来遍历集合时,应使用Iterator的remove()方法来删除集合中的元素,使用集合的remove()方法将抛出ConncurrentModificationException异常。
Enumeration 接口 | Iterator 接口 | |
---|---|---|
参数的含义 | 枚举类型 | 迭代器元素类型 |
所在包 | java.util | |
父类 | 无 | |
子类 | StringTokenizer | BeanContextSupport.BCSIterator, EventReaderDelegate, Scanner |
区别 | 实现 Enumeration 接口的对象,它生成一系列元素,一次生成一个。连续调用nextElement 方法将返回一系列的连续元素。 | 迭代器 |
方法 | ||
判断是否有下一个元素 | hasMoreElements()测试此枚举是否包含更多的元素。 | hasNext()如果仍有元素可以迭代,则返回 true。 |
获取元素 | nextElement()如果此枚举对象至少还有一个可提供的元素,则返回此枚举的下一个元素。 | next()返回迭代的下一个元素。 |
移除 | remove()从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。 |
Enumeration是个接口,不是类,再次,这个东西就是为了实现遍历的,现在已经被迭代器Iterator取代了。
不同点: (1)主要区别其实就是Iterator提供删除元素的方法可以删除元素,而Enumration不能。 (2)Iterator是比较新的迭代器,应该优先使用Iterator。 (3)Enumeration能够遍历Vector,HashTable,Properties类型集合元素的功能,不支持元素的移除操作。Iterator可以遍历Vector, ArrayList, LinkedList等集合元素,完全可以替代Enumeration。
174.创建对象的几种方式
使用new关键字;
Class对象的newInstance()方法;
构造函数对象的newInstance()方法;
对象反序列化;
Object对象的clone()方法;
使用Unsafe类创建对象;
(1)使用new关键字
(2)class的newInstance()方法
首先我们通过Class.forName()动态的加载类的Class对象,
然后通过newInstance()方法获得Test类的对象
(3)构造函数的newInstance()方法
类Constructor也有newInstance方法,这一点和Class有点像。从它的名字可以看出它与Class的不同,Class是通过类来创建对象,而Constructor则是通过构造器。
(4)序列化
首先我们要对Test实现Serializable接口。然后开始序列化数据。最后得到反序列化的对象。
(5)clone方式
Object对象中存在clone方法,它的作用是创建一个对象的副本。
(6)使用Unsafe类创建对象
Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。过度的使用Unsafe类会使得出错的几率变大,因此Java官方并不建议使用的,官方文档也几乎没有。Oracle正在计划从Java 9中去掉Unsafe类,如果真是如此影响就太大了。
我们无法直接创建Unsafe对象。这里我们使用反射方法得到
拿到这个对象后,调用其中的native方法allocateInstance 创建一个对象实例
Object event = unsafe.allocateInstance(Test.class);
https://baijiahao.baidu.com/s?id=1637836912223474691
175.父子类执行顺序
176.Redis调优
针对Redis的性能优化,主要从下面几个层面入手:
-
最初的也是最重要的,确保没有让Redis执行耗时长的命令
-
使用pipelining将连续执行的命令组合执行
-
操作系统的Transparent huge pages功能必须关闭:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
-
如果在虚拟机中运行Redis,可能天然就有虚拟机环境带来的固有延迟。可以通过./redis-cli –intrinsic-latency 100命令查看固有延迟。同时如果对Redis的性能有较高要求的话,应尽可能在物理机上直接部署Redis。
-
检查数据持久化策略
-
考虑引入读写分离机制
176.1 长耗时命令
避免在使用这些O(N)命令时发生问题主要有几个办法:
- 不要把List当做列表使用,仅当做队列来使用
- 通过机制严格控制Hash、Set、Sorted Set的大小
- 可能的话,将排序、并集、交集等操作放在客户端执行
- 绝对禁止使用KEYS命令
- 避免一次性遍历集合类型的所有成员,而应使用SCAN类的命令进行分批的,游标式的遍历
Redis提供了Slow Log功能,可以自动记录耗时较长的命令。相关的配置参数有两个:
slowlog-log-slower-than xxxms #执行时间慢于xxx毫秒的命令计入Slow Log
slowlog-max-len xxx #Slow Log的长度,即最大纪录多少条Slow Log
使用SLOWLOG GET [number]命令,可以输出最近进入Slow Log的number条命令。 使用SLOWLOG RESET命令,可以重置Slow Log
https://www.cnblogs.com/276815076/p/7245333.html
177.Feign工作原理
- 启动类添加@EnableFeignClients注解,Spring会扫描标记了@FeignClient注解的接口,并生成此接口的代理对象。
- @FeignClient(value = “PROVICER”)即指定了服务名称,Feign会从注册中心获取服务列表,并通过Feign集成的Ribbon负载均衡算法进行服务调用。
- 在接口方法 中使用注解@GetMapping(“路由”),指定调用的url,Feign将根据url进行远程调用。
注:Feign注意点:
SpringCloud对Feign进行了增强兼容了SpringMVC的注解 ,我们在使用SpringMVC的注解时需要注意:
- feignClient接口 有参数在参数必须加@PathVariable(“XXX”)和@RequestParam(“XXX”)
- feignClient返回值为复杂对象时其类型必须有无参构造函数。
https://www.pianshen.com/article/7615127033/
178. 自定义RPC框架
https://www.cnblogs.com/swordfall/p/8683905.html
178.1 RMI与RPC的区别
Java RMI 工作原理
一个典型的 RMI 调用如下图所示:
服务端向 RMI 注册服务绑定自己的地址;register 客户端通过 RMI 注册服务获取目标地址;subscribe+notify 客户端调用本地的 Stub 对象上的方法,和调用本地对象上的方法一致; 本地存根对象将调用信息打包,通过网络发送到服务端; 服务端的 Skeleton 对象收到网络请求之后,将调用信息解包; 然后找到真正的服务对象发起调用,并将返回结果打包通过网络发送回客户端。
RPC与RMI的区别: 1:方法调用方式不同: RMI中是通过在客户端的Stub对象作为远程接口进行远程方法的调用。每个远程方法都具有方法签名。如果一个方法在服务器上执行,但是没有相匹配的签名被添加到这个远程接口(stub)上,那么这个新方法就不能被RMI客户方所调用。
RPC中是通过网络服务协议向远程主机发送请求,请求包含了一个参数集和一个文本值,通常形成“classname.methodname(参数集)”的形式。RPC远程主机就去搜索与之相匹配的类和方法,找到后就执行方法并把结果编码,通过网络协议发回。
2:适用语言范围不同: RMI只用于Java; RPC是网络服务协议,与操作系统和语言无关。
3:调用结果的返回形式不同: Java是面向对象的,所以RMI的调用结果可以是对象类型或者基本数据类型; RMI的结果统一由外部数据表示 (External Data Representation, XDR) 语言表示,这种语言抽象了字节序类和数据类型结构之间的差异。
179.SpringMVC原理
https://www.cnblogs.com/fengquan-blog/p/11161084.html
https://www.jianshu.com/p/2bfd65bc9ce4
179.1 SpringMVC的流程
180.join()
在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
场景:
在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程将早于子线程结束。这时,如果主线程想等子线程执行完成才结束,比如子线程处理一个数据,主线程想要获得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。
join()方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。
join方法中如果传入参数表示:如果A线程中调用B线程的join(10),则表示A线程会等待B线程执行10毫秒,10毫秒过后,A、B线程并行执行。需要注意的是,jdk规定,join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕,即join(0)等价于join()。(其实join()中调用的是join(0))
join方法必须在线程start方法调用之后调用才有意义。这个也很容易理解:如果一个线程都没有start,那它也就无法同步了。
方法x.join()的作用是使所属线程x 正常执行run()中的方法,而使得调用x.join()的线程处于无限期阻塞状态,等待x线程销毁后再继续执行线程z后面的代码。
方法join()具有使线程排队运行的作用,有些类似于同步的运行效果。join()与synchronized的区别是:join在内部调用wait()方法进行等待,而synchronized关键字使用的是”对象监视器”原理作为同步。
调用thread.join()的线程被中断才会进入异常,比如a线程调用b.join(),a中断会报异常而b中断不会异常。
181. java.util.concurrent并发包
jdk1.8.0_112为例:
concurrent
——atomic
————AtomicBoolean.class
————AtomicInteger.class
————AtomicIntegerArray.class
————AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl$1.class
————AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.class
————AtomicIntegerFieldUpdater.class
————AtomicLong.class
————AtomicLongArray.class
————AtomicLongFieldUpdater$CASUpdater$1.class
————AtomicLongFieldUpdater$CASUpdater.class
————AtomicLongFieldUpdater$LockedUpdater$1.class
————AtomicLongFieldUpdater$LockedUpdater.class
————AtomicLongFieldUpdater.class
————AtomicMarkableReference$Pair.class
————AtomicMarkableReference.class
————AtomicReference.class
————AtomicReferenceArray.class
————AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl$1.class
————AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl.class
————AtomicReferenceFieldUpdater.class
————AtomicStampedReference$Pair.class
————AtomicStampedReference.class
————DoubleAccumulator$SerializationProxy.class
————DoubleAccumulator.class
————DoubleAdder$SerializationProxy.class
————DoubleAdder.class
————LongAccumulator$SerializationProxy.class
————LongAccumulator.class
————LongAdder$SerializationProxy.class
————LongAdder.class
————Striped64$Cell.class
————Striped64.class
——locks
————AbstractOwnableSynchronizer.class
————AbstractQueuedLongSynchronizer$ConditionObject.class
————AbstractQueuedLongSynchronizer$Node.class
————AbstractQueuedLongSynchronizer.class
————AbstractQueuedSynchronizer$ConditionObject.class
————AbstractQueuedSynchronizer$Node.class
————AbstractQueuedSynchronizer.class
————Condition.class
————Lock.class
————LockSupport.class
————ReadWriteLock.class
————ReentrantLock$FairSync.class
————ReentrantLock$NonfairSync.class
————ReentrantLock$Sync.class
————ReentrantLock.class
————ReentrantReadWriteLock$FairSync.class
————ReentrantReadWriteLock$NonfairSync.class
————ReentrantReadWriteLock$ReadLock.class
————ReentrantReadWriteLock$Sync$HoldCounter.class
————ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter.class
————ReentrantReadWriteLock$Sync.class
————ReentrantReadWriteLock$WriteLock.class
————ReentrantReadWriteLock.class
————StampedLock$ReadLockView.class
————StampedLock$ReadWriteLockView.class
————StampedLock$WNode.class
————StampedLock$WriteLockView.class
————StampedLock.class
——AbstractExecutorService.class
——ArrayBlockingQueue$Itr.class
——ArrayBlockingQueue$Itrs$Node.class
——ArrayBlockingQueue$Itrs.class
——ArrayBlockingQueue.class
——BlockingDeque.class
——BlockingQueue.class
——BrokenBarrierException.class
——Callable.class
——CancellationException.class
——CompletableFuture$AltResult.class
——CompletableFuture$AsynchronousCompletionTask.class
——CompletableFuture$AsyncRun.class
——CompletableFuture$AsyncSupply.class
——CompletableFuture$BiAccept.class
——CompletableFuture$BiApply.class
——CompletableFuture$BiCompletion.class
——CompletableFuture$BiRelay.class
——CompletableFuture$BiRun.class
——CompletableFuture$CoCompletion.class
——CompletableFuture$Completion.class
——CompletableFuture$OrAccept.class
——CompletableFuture$OrApply.class
——CompletableFuture$OrRelay.class
——CompletableFuture$OrRun.class
——CompletableFuture$Signaller.class
——CompletableFuture$ThreadPerTaskExecutor.class
——CompletableFuture$UniAccept.class
——CompletableFuture$UniApply.class
——CompletableFuture$UniCompletion.class
——CompletableFuture$UniCompose.class
——CompletableFuture$UniExceptionally.class
——CompletableFuture$UniHandle.class
——CompletableFuture$UniRelay.class
——CompletableFuture$UniRun.class
——CompletableFuture$UniWhenComplete.class
——CompletableFuture.class
——CompletionException.class
——CompletionService.class
——CompletionStage.class
——ConcurrentHashMap$BaseIterator.class
——ConcurrentHashMap$BulkTask.class
——ConcurrentHashMap$CollectionView.class
——ConcurrentHashMap$CounterCell.class
——ConcurrentHashMap$EntryIterator.class
——ConcurrentHashMap$EntrySetView.class
——ConcurrentHashMap$EntrySpliterator.class
——ConcurrentHashMap$ForEachEntryTask.class
——ConcurrentHashMap$ForEachKeyTask.class
——ConcurrentHashMap$ForEachMappingTask.class
——ConcurrentHashMap$ForEachTransformedEntryTask.class
——ConcurrentHashMap$ForEachTransformedKeyTask.class
——ConcurrentHashMap$ForEachTransformedMappingTask.class
——ConcurrentHashMap$ForEachTransformedValueTask.class
——ConcurrentHashMap$ForEachValueTask.class
——ConcurrentHashMap$ForwardingNode.class
——ConcurrentHashMap$KeyIterator.class
——ConcurrentHashMap$KeySetView.class
——ConcurrentHashMap$KeySpliterator.class
——ConcurrentHashMap$MapEntry.class
——ConcurrentHashMap$MapReduceEntriesTask.class
——ConcurrentHashMap$MapReduceEntriesToDoubleTask.class
——ConcurrentHashMap$MapReduceEntriesToIntTask.class
——ConcurrentHashMap$MapReduceEntriesToLongTask.class
——ConcurrentHashMap$MapReduceKeysTask.class
——ConcurrentHashMap$MapReduceKeysToDoubleTask.class
——ConcurrentHashMap$MapReduceKeysToIntTask.class
——ConcurrentHashMap$MapReduceKeysToLongTask.class
——ConcurrentHashMap$MapReduceMappingsTask.class
——ConcurrentHashMap$MapReduceMappingsToDoubleTask.class
——ConcurrentHashMap$MapReduceMappingsToIntTask.class
——ConcurrentHashMap$MapReduceMappingsToLongTask.class
——ConcurrentHashMap$MapReduceValuesTask.class
——ConcurrentHashMap$MapReduceValuesToDoubleTask.class
——ConcurrentHashMap$MapReduceValuesToIntTask.class
——ConcurrentHashMap$MapReduceValuesToLongTask.class
——ConcurrentHashMap$Node.class
——ConcurrentHashMap$ReduceEntriesTask.class
——ConcurrentHashMap$ReduceKeysTask.class
——ConcurrentHashMap$ReduceValuesTask.class
——ConcurrentHashMap$ReservationNode.class
——ConcurrentHashMap$SearchEntriesTask.class
——ConcurrentHashMap$SearchKeysTask.class
——ConcurrentHashMap$SearchMappingsTask.class
——ConcurrentHashMap$SearchValuesTask.class
——ConcurrentHashMap$Segment.class
——ConcurrentHashMap$TableStack.class
——ConcurrentHashMap$Traverser.class
——ConcurrentHashMap$TreeBin.class
——ConcurrentHashMap$TreeNode.class
——ConcurrentHashMap$ValueIterator.class
——ConcurrentHashMap$ValueSpliterator.class
——ConcurrentHashMap$ValuesView.class
——ConcurrentHashMap.class
——ConcurrentLinkedDeque$1.class
——ConcurrentLinkedDeque$AbstractItr.class
——ConcurrentLinkedDeque$CLDSpliterator.class
——ConcurrentLinkedDeque$DescendingItr.class
——ConcurrentLinkedDeque$Itr.class
——ConcurrentLinkedDeque$Node.class
——ConcurrentLinkedDeque.class
——ConcurrentLinkedQueue$CLQSpliterator.class
——ConcurrentLinkedQueue$Itr.class
——ConcurrentLinkedQueue$Node.class
——ConcurrentLinkedQueue.class
——ConcurrentMap.class
——ConcurrentNavigableMap.class
——ConcurrentSkipListMap$CSLMSpliterator.class
——ConcurrentSkipListMap$EntryIterator.class
——ConcurrentSkipListMap$EntrySet.class
——ConcurrentSkipListMap$EntrySpliterator.class
——ConcurrentSkipListMap$HeadIndex.class
——ConcurrentSkipListMap$Index.class
——ConcurrentSkipListMap$Iter.class
——ConcurrentSkipListMap$KeyIterator.class
——ConcurrentSkipListMap$KeySet.class
——ConcurrentSkipListMap$KeySpliterator.class
——ConcurrentSkipListMap$Node.class
——ConcurrentSkipListMap$SubMap$SubMapEntryIterator.class
——ConcurrentSkipListMap$SubMap$SubMapIter.class
——ConcurrentSkipListMap$SubMap$SubMapKeyIterator.class
——ConcurrentSkipListMap$SubMap$SubMapValueIterator.class
——ConcurrentSkipListMap$SubMap.class
——ConcurrentSkipListMap$ValueIterator.class
——ConcurrentSkipListMap$Values.class
——ConcurrentSkipListMap$ValueSpliterator.class
——ConcurrentSkipListMap.class
——ConcurrentSkipListSet.class
——CopyOnWriteArrayList$1.class
——CopyOnWriteArrayList$COWIterator.class
——CopyOnWriteArrayList$COWSubList.class
——CopyOnWriteArrayList$COWSubListIterator.class
——CopyOnWriteArrayList.class
——CopyOnWriteArraySet.class
——CountDownLatch$Sync.class
——CountDownLatch.class
——CountedCompleter.class
——CyclicBarrier$1.class
——CyclicBarrier$Generation.class
——CyclicBarrier.class
——Delayed.class
——DelayQueue$Itr.class
——DelayQueue.class
——Exchanger$Node.class
——Exchanger$Participant.class
——Exchanger.class
——ExecutionException.class
——Executor.class
——ExecutorCompletionService$QueueingFuture.class
——ExecutorCompletionService.class
——Executors$1.class
——Executors$2.class
——Executors$DefaultThreadFactory.class
——Executors$DelegatedExecutorService.class
——Executors$DelegatedScheduledExecutorService.class
——Executors$FinalizableDelegatedExecutorService.class
——Executors$PrivilegedCallable$1.class
——Executors$PrivilegedCallable.class
——Executors$PrivilegedCallableUsingCurrentClassLoader$1.class
——Executors$PrivilegedCallableUsingCurrentClassLoader.class
——Executors$PrivilegedThreadFactory$1$1.class
——Executors$PrivilegedThreadFactory$1.class
——Executors$PrivilegedThreadFactory.class
——Executors$RunnableAdapter.class
——Executors.class
——ExecutorService.class
——ForkJoinPool$1.class
——ForkJoinPool$DefaultForkJoinWorkerThreadFactory.class
——ForkJoinPool$EmptyTask.class
——ForkJoinPool$ForkJoinWorkerThreadFactory.class
——ForkJoinPool$InnocuousForkJoinWorkerThreadFactory$1.class
——ForkJoinPool$InnocuousForkJoinWorkerThreadFactory.class
——ForkJoinPool$ManagedBlocker.class
——ForkJoinPool$WorkQueue.class
——ForkJoinPool.class
——ForkJoinTask$AdaptedCallable.class
——ForkJoinTask$AdaptedRunnable.class
——ForkJoinTask$AdaptedRunnableAction.class
——ForkJoinTask$ExceptionNode.class
——ForkJoinTask$RunnableExecuteAction.class
——ForkJoinTask.class
——ForkJoinWorkerThread$InnocuousForkJoinWorkerThread.class
——ForkJoinWorkerThread.class
——Future.class
——FutureTask$WaitNode.class
——FutureTask.class
——LinkedBlockingDeque$1.class
——LinkedBlockingDeque$AbstractItr.class
——LinkedBlockingDeque$DescendingItr.class
——LinkedBlockingDeque$Itr.class
——LinkedBlockingDeque$LBDSpliterator.class
——LinkedBlockingDeque$Node.class
——LinkedBlockingDeque.class
——LinkedBlockingQueue$Itr.class
——LinkedBlockingQueue$LBQSpliterator.class
——LinkedBlockingQueue$Node.class
——LinkedBlockingQueue.class
——LinkedTransferQueue$Itr.class
——LinkedTransferQueue$LTQSpliterator.class
——LinkedTransferQueue$Node.class
——LinkedTransferQueue.class
——Phaser$QNode.class
——Phaser.class
——PriorityBlockingQueue$Itr.class
——PriorityBlockingQueue$PBQSpliterator.class
——PriorityBlockingQueue.class
——RecursiveAction.class
——RecursiveTask.class
——RejectedExecutionException.class
——RejectedExecutionHandler.class
——RunnableFuture.class
——RunnableScheduledFuture.class
——ScheduledExecutorService.class
——ScheduledFuture.class
——ScheduledThreadPoolExecutor$DelayedWorkQueue$Itr.class
——ScheduledThreadPoolExecutor$DelayedWorkQueue.class
——ScheduledThreadPoolExecutor$ScheduledFutureTask.class
——ScheduledThreadPoolExecutor.class
——Semaphore$FairSync.class
——Semaphore$NonfairSync.class
——Semaphore$Sync.class
——Semaphore.class
——SynchronousQueue$FifoWaitQueue.class
——SynchronousQueue$LifoWaitQueue.class
——SynchronousQueue$Transferer.class
——SynchronousQueue$TransferQueue$QNode.class
——SynchronousQueue$TransferQueue.class
——SynchronousQueue$TransferStack$SNode.class
——SynchronousQueue$TransferStack.class
——SynchronousQueue$WaitQueue.class
——SynchronousQueue.class
——ThreadFactory.class
——ThreadLocalRandom$RandomDoublesSpliterator.class
——ThreadLocalRandom$RandomIntsSpliterator.class
——ThreadLocalRandom$RandomLongsSpliterator.class
——ThreadLocalRandom.class
——ThreadPoolExecutor$AbortPolicy.class
——ThreadPoolExecutor$CallerRunsPolicy.class
——ThreadPoolExecutor$DiscardOldestPolicy.class
——ThreadPoolExecutor$DiscardPolicy.class
——ThreadPoolExecutor$Worker.class
——ThreadPoolExecutor.class
——TimeoutException.class
——TimeUnit$1.class
——TimeUnit$2.class
——TimeUnit$3.class
——TimeUnit$4.class
——TimeUnit$5.class
——TimeUnit$6.class
——TimeUnit$7.class
——TimeUnit.class
——TransferQueue.class
182.LinkedBlockingQueue和ArrayBlockingQueue的区别
https://www.cnblogs.com/lianliang/p/5765349.html