1. HashMap

1.7:头插法(多线程扩容时会形成循环链表,产生死循环),数组+链表,
1.8:尾插法,数组+链表+红黑树

扩容 重hash
JAVA7扩容后的rehash过程使用了单链表的头插法方式,同一位置上新元素总会被放在链表的头部位置;这样如果发生了hash冲突的话先放在一个索引上的元素终会被放到Entry链的尾部,这一点和JAVA8有区别。
JAVA8在rehash算法利用了下面的一个特性:
HashMap的扩容使用的是2次幂的扩展(指长度扩为原来2倍),所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。什么意思呢? 我们举个例子说明。
假设原数组长度 capacity 为 16,扩容之后 new capacity 为 32:
下标的计算方法,对于一个 Key,如原Hash值 key1 = 0001 1001 key2 = 0000 1001
扩容前 hash & (length - 1) :
key1 : 0001 1001 & 0000 1111 -> 0000 1001
key2 : 0000 1001 & 0000 1111 -> 0000 1001
扩容后 hash & (length - 1) :
key1 : 0001 1001 & 0001 1111 -> 0001 1001
key2 : 0000 1001 & 0001 1111 -> 0000 1001
因此,我们在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,
只需要看原来的hash值在扩容后新增的那一位是1还是0,如果是0的话索引没变,是1的话索引变成“原索引+oldCap
因此,我们在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,
LinkedHashMap(有序,加上了双向链表)
LinkedHashMap就是HashMap+双向链表

LinkedHashMap是有序的,且默认为插入顺序
ConcurrentHashMap的底层实现:
JDK1.7的 ConcurrentHashMap 底层采⽤ 分段的数组+链表 实现;采用 分段锁(Sagment) 对整个桶数组进⾏了分割分段(Segment默认16个),每⼀把锁只锁容器其中⼀部分数据,多线程访问容器⾥不同数据段的数据,就不会存在锁竞争,提⾼并发访问率。

JDK1.8的 ConcurrentHashMap 采⽤的数据结构跟HashMap1.8的结构⼀样,数组+链表/红⿊树;摒弃了Segment的概念,⽽是直接⽤ Node 数组+链表+红⿊树的数据结构来实现,通过并发控制 synchronized 和CAS来操作保证线程的安全。
2. 异常
除了error和runtimeException其他都为必检异常
- 尽量不要捕获类似Exception这样的通用异常,而是应该捕获特定异常
- 不要生吞(swallow)异常。这是异常处理中要特别注意的事情,因为很可能会导致非常难以诊断的诡异情况
- 不要e.printStackTrace();,不好确认输出到哪里了
- Throw early, catch late(提前抛出能早点定位到问题,晚点catch,在更高层面,因为有了清晰的(业务)逻辑,往往会更清楚合适的处理方式是什么)
- try-catch代码段会产生额外的性能开销,或者换个角度说,它往往会影响JVM对代码进行优化,所以建议仅捕获有必要的代码段,尽量不要一个大的try包住整段的代码;与此同时,利用异常控制代码流程,也不是一个好主意,远比我们通常意义上的条件语句(if/else、switch)要低效
- Java每实例化一个Exception,都会对当时的栈进行快照,这是一个相对比较重的操作。如果发生的非常频繁,这个开销可就不能被忽略了。
3. final、finally(关闭的连接等资源)、 finalize(垃圾回收关联)

finally里面的代码可不会被执行的哦
finalize ,jdk9已废除,无法保证fnalize什么时候执行,执行的是否符合预期,不推荐使用,降低垃圾回收效率,Java平台目前在逐步使用java.lang.ref.Cleaner来替换掉原有的fnalize实现。Cleaner的实现利用了幻象引用
4. 强引用、软引用(缓存)、弱引用、虚引用(检测对象被回收)
- 强引用(“Strong” Reference),就是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”
- 软引用(SoftReference),是一种相对强引用弱化一些的引用,可以让对象豁免一些垃圾收集,只有当JVM认为内存不足时,才会去试图回收软引用指向的对象,软引用通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,
使用:浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出 - 弱引用(WeakReference)并不能使对象豁免垃圾收集,仅仅是提供一种访问在弱引用状态下对象的途径,如果试图获取时对象还在,就使用它,否则重现实例化。它同样是很多缓存实现的选择。
- 虚引用,你不能通过它访问对象。幻象引用仅仅是提供了一种确保对象被fnalize以后,做某些事情的机制,比如,通常用来做所谓的Post-Mortem清理机制,它主要用来跟踪对象被垃圾回收的活动。
方法的内部有一个强引用,这个引用保存在Java栈中,而真正的引用内容(Object)保存在Java堆中。 当这个方法运行完成后,就会退出方法栈,则引用对象的引用数为0,这个对象会被回收
软引用可以和一个引用队列(ReferenceQueue)联合使用。如果软引用所引用对象被垃圾回收,JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中。
弱引用可以和一个引用队列,
虚引用必须和一个引用队列联合使用,当 GC 准备回收一个对象,如果发现它有虚引用,就会在回收之前,把这个 虚引用 加入到与之关联的 ReferenceQueue 中
ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
String str = new String("abc");
SoftReference<String> softReference = new SoftReference<>(str, referenceQueue);
str = null;//软引用中的对象引用置为null
// Notify GC
System.gc();
System.out.println(softReference.get()); // abc
Reference<? extends String> reference = referenceQueue.poll();
System.out.println(reference); //null
5. String、StringBufer、StringBuilder
String是典型的Immutable类
StringBufer是为解决上面提到拼接产生太多中间对象的问题而提供的一个类(线程安全)
StringBuilder(线程不安全)
为了实现修改字符序列的目的,StringBufer和StringBuilder底层都是利用可修改的(char,JDK 9以后是byte)数组
在JDK 8中,字符串拼接操作会自动被javac转换为StringBuilder操
6. 不同字符占用空间与编码有关
中文在不同编码是不定长的 2-4个字节,英文一般是1个,只有Unicode编码下是2个字节
ASCII码:英文字母 1个字节的空间
UTF-8编码:英文字符 1个字节(含标点),中文字符 3个字节(含标点)
UTF-16编码,通常汉字占2个字节
Unicode编码:英文中文都是2个字节
GBK编码,一个汉字占2个字节。
字符 不等于 字节。
字符(char)是 Java 中的一种基本数据类型,由 2 个字节组成,
java一个 int 占 4 个字节,一个 double 占 8个字节 等等。
8bit(位)=1Byte(字节) 1024Byte(字节)=1KB 1024KB=1MB 1024MB=1GB 1024GB=1TB
7. 写时复制
解释:
Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,
如果有多个调用者(callers)同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的
使用
- linux 的fock(),exec()
- java容器
8. @Transactional
事务传播属性
传播特性是作用于内层方法上
PROPAGATION_REQUIRED:如果不存在外层事务,就主动创建事务;否则使用外层事务(默认)
PROPAGATION_SUPPORTS:如果不存在外层事务,就不开启事务;否则使用外层事务(内外一致)
PROPAGATION_MANDATORY:如果不存在外层事务,就抛出异常;否则使用外层事务
PROPAGATION_REQUIRES_NEW:总是主动开启事务;如果存在外层事务,就将外层事务挂起
PROPAGATION_NOT_SUPPORTED:总是不开启事务;如果存在外层事务,就将外层事务挂起
PROPAGATION_NEVER:总是不开启事务;如果存在外层事务,则抛出异常
PROPAGATION_NESTED:如果不存在外层事务,就主动创建事务;否则创建嵌套的子事务(内依赖外,外不希望被内影响,外层catch住内层的异常,外层就不会回滚)
rollbackFor
注解不生效
注解只是个标记,实际作用于注解对应的切面,需要在切面中识别注解中定义的如@Order才能生效
- try catch掉了,没抛出
2. 没有开启对注解的解析(@EnableTransactionManagement )
3. @Transactional只能作用域public方法上(因为,被aop增强的方法都应该是public的,而不能是private的)
4. 默认只对非必检异常处理,必检的IOException,SQLException,FileNotFoundException异常等无法回滚,(rollbackFor = Exception.class即可解决)
5. 数据库引擎要支持事务,如myisam不行
6. 分布式事务不支持
9. 泛型擦除
Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。
如List等类型,在编译之后都会变成 List。JVM 看到的只是 List,而由泛型附加的类型信息对 JVM 来说是不可见的。
ArrayList<String> list1 = new ArrayList<String>();
list1.add("abc");
ArrayList<Integer> list2 = new ArrayList<Integer>();
list2.add(123);
//true
System.out.println(list1.getClass() == list2.getClass());
先检查再编译,类型检查就是针对引用的
泛型类型变量不能是基本数据类型
泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数
public class Test2<T> {
public static T one; //编译错误
public static T show(T one){ //编译错误
return null;
}
}
因为类型擦除的问题,所以所有的泛型类型变量最后都会被替换为原始类型。
既然都被替换为原始类型,那么为什么我们在获取的时候,不需要进行强制类型转换呢
假设泛型类型变量为Date,虽然泛型信息会被擦除掉,但是会将(E) elementData[index],编译为(Date) elementData[index]。所以我们不用自己进行强转。
10. fail-fast ,fail-safe(它是Java集合的一种错误检测机制)
fail-fast主要是体现在当我们在遍历集合元素的时候,经常会使用迭代器,但在迭代器遍历元素的过程中,如果集合的结构(modCount)被改变的话,就会抛出异常ConcurrentModificationException,防止继续遍历。这就是所谓的快速失败机制
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。由于在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发ConcurrentModificationException。
11. Object
toString,equals,hashcode,clone(浅拷贝,对象里面的对象只拷贝引用),wait,notify,getClass,finalize
12. 创建对象的步骤
步骤:类加载检查、分配内存、初始化零值、设置对象头、执行init方法
①类加载检查:
虚拟机遇到 new 指令时,⾸先去检查是否能在常量池中定位到这个类的符号引⽤,并且检查这个符号引⽤代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执⾏相应的类加载过程。
②分配内存:
在类加载检查通过后,接下来虚拟机将为新⽣对象分配内存,分配⽅式有 “指针碰撞” 和 “空闲列表” 两种,选择那种分配⽅式由 Java 堆是否规整决定,⽽Java堆是否规整⼜由所采⽤的垃圾收集器是否带有压缩整理功能决定。
③初始化零值:
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值,这⼀步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使⽤,程序能访问到这些字段的数据类型所对应的零值。
④设置对象头:
初始化零值完成之后,虚拟机要对对象进⾏必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运⾏状态的不同,如是否启⽤偏向锁等,对象头会有不同的设置⽅式。
⑤执⾏ init ⽅法:
从虚拟机的视⻆来看,⼀个新的对象已经产⽣了,但从Java 程序的视⻆来看, ⽅法还没有执⾏,所有的字段都还为零。所以⼀般来说(除循环依赖),执⾏ new 指令之后会接着执⾏ ⽅法,这样⼀个真正可⽤的对象才算产⽣出来。
13. 内存泄漏(释放不掉)
内存泄漏指你申请了一块内存,但是没有内存释放,导致这块内存一直处于占用状态。(无用的被引用的对象)
长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏
- 静态集合类引起内存泄漏,单例对象持有外部的引用(例子:HashMap做缓存)
- 各种连接(例子:关于文件处理器的应用,在读取或者写入一些文件之后,由于发生了一些异常,close 方法又没有放在 finally 块里面)
数据库连接的connection,Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。
数据库连接池,必须显式地关闭Resultset Statement 对象
内存溢出(内存不够)
14. spring设计模式
单例:线程池,私有构造方法+两段锁获取instance
双重检测锁,第一次减少锁的开销、第二次防止重复、volatile防止重排序导致实例化未完成
两段锁
public static aaThreadPool getInstance() {
if(instance == null){
synchronized (aaThreadPool.class){
if(instance == null){
instance = new aaThreadPool();;
}
}
}
return instance;
}
1.分配内存空间
2.在这块内存上初始化对象
3.将内存地址赋值给earth变量
重排序后:
1.分配内存空间
2.将内存地址赋值给earth变量
3.在这块内存上初始化对象
并发下:线程A执行到2,实例未初始化完毕,线程b获取到锁,发现不为null,返回对象使用,此时对象未初始化
工厂: Spring使⽤⼯⼚模式通过 BeanFactory 、 ApplicationContext 创建bean 对象。

代理 : Spring AOP 功能的实现。
观察者: Spring 事件驱动模型就是观察者模式很经典的⼀个应⽤。
适配器:Spring AOP 的增强或通知(Advice)使⽤到了适配器模式、Spring MVC 中也是⽤到了适配器模式适配 Controller 。
将一个类的接口转换成客户希望的另外一个接口
15.创建对象步骤
步骤:类加载检查、分配内存、初始化零值、设置对象头、执行init方法
初始化零值完成之后,虚拟机要对对象进⾏必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运⾏状态的不同,如是否启⽤偏向锁等,对象头会有不同的设置⽅式。
16. spring cloud
Spring Cloud是一套非常完善的分布式框架,目前很多企业开始用微服务、Spring Cloud的优势是显而易见的 ,利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、智能路由、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署;Spring Cloud的子项目,大致可分成两类,一类是对现有成熟框架"Spring Boot化"的封装和抽象,也是数量最多的项目;第二类是开发了一部分分布式系统的基础设施的实现
springcloud 不是一个开发框架,而是一整套的解决方案,而 dubbo 仅仅是一个 rpc 的框架
Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务,Spring Cloud是一个基于Spring Boot实现的云应用开发工具。Spring -> Spring Boot > Spring Cloud 这样的关系
SpringBoot是Spring推出用于解决传统框架配置文件冗余,装配组件繁杂的基于Maven的解决方案,旨在快速搭建单个微服务
springboot:约定大于配置,内置容器,自动装配:**Spring Boot 会根据某些规则对所有配置的 Bean 进行初始化。可以减少了很多重复性的工作
spring cloud的组成
路由route: ⽹关最基础的⼯作单元。路由由⼀个ID、⼀个⽬标URL、⼀系列的断⾔(匹配条件判断)和Filter过滤器组成。如果断⾔为true,则匹配该路由。
Eureka (ap)/ Zookeeper(cp)
Hystrix / Sentinel


Hystrix(调用方,熔断降级,快速失败) / Sentinel(提供方,流量控制)
线程池隔离(受线程池中线程数量的限制,线程切换开销大)/信号量隔离(开销相对较小,并发请求受信号量的个数的限制)
Config / Nacos(配置中心)
Bus / Stream(mq)
Sleuth / Zipkin(全链路追踪)
作用域
名称 作用域
singleton 单例对象,默认值的作用域
prototype 每次获取都会创建⼀个新的 bean 实例
request 每⼀次HTTP请求都会产⽣⼀个新的bean,该bean仅在当前HTTP request内有效。
session 在一次 HTTP session 中,容器将返回同一个实例(用户多次请求,可以理解是客户端同一个IE窗口发出的多个请求)
global-session 将对象存入到web项目集群的session域中,若不存在集群,则global session相当于session
循环依赖
循环依赖其实就是循环引用,也就是两个或者两个以上的 Bean 互相持有对方,最终形成闭环。比如A 依赖于B,B又依赖于A
解决方案:多级缓存
这三级缓存分别指:
singletonFactories : 单例对象工厂的cache
earlySingletonObjects :提前暴光的单例对象的Cache
singletonObjects:单例对象的cache

17. spi(Service Provider Interface)
用来启用框架扩展和替换组件。 SPI的作用就是为这些被扩展的API寻找服务实现。
使用场景:数据库连接,dubbo框架的拓展,日志接口类对不同厂商的加载
特点:基于接口编程,桥接模式,避免 直接引入实现类,或者通过反射引入以及dubbo远程调用,而是通过插件的方式,在引入的jar包中的META-INF/services目录下的配置文件(配置文件名和接口路径一直)指定实现类的路径,来找到实现类;懒加载,缺点是遍历的方式所有实现类都会被加载
- 不需要改动源码就可以实现扩展,解耦。
- 实现扩展对原来的代码几乎没有侵入性。
- 只需要添加配置就可以实现扩展,符合开闭原则。
使用方式:工程1定义接口;工程2实现接口,并创建META-INF/services/接口名 的文件,内容为实现类的路径,然后打包;工程3引入工程1和工程2的包,即可基于接口来使用实现类,ServiceLoader来load方法加载实现类(扫描META-INF目录,获取文件名,反射生成对象缓存)
(ServiceLoader shouts = ServiceLoader.load(IShout.class))
18. ratelimiter(预消费)
两种模式,平滑模式和预热模式
RateLimiter rateLimiter = RateLimiter.create(1);
while (true){
System.out.println("rate1:" + rateLimiter.acquire(1)); //阻塞
System.out.println("rate3:" + rateLimiter.acquire(3)); //通过限制后面请求的等待时间,来支持一定程度的突发请求(预消费),此时等待1s
System.out.println("rate5:" + rateLimiter.acquire(5));
System.out.println("rate7:" + rateLimiter.acquire(7));
System.out.println("rate try:" + rateLimiter.tryAcquire(3,1,TimeUnit.SECONDS));//设置等待超时时间
}
令牌数,不是通过定时任务存放的方式,而是通过取令牌时延迟计算,有storedPermits,maxPermits,stableIntervalMicros,nextFreeTicketMicros(下次请求需要等待相应的时间到nextFreeTicketMicros时刻才可以获取令牌)来计算,其实现思路为,若当前时间晚于nextFreeTicketMicros,则计算该段时间内可以生成多少令牌,将生成的令牌加入令牌桶中并更新数据。这样一来,只需要在获取令牌时计算一次即可。
19. equals和hashcode
hashcode根据内存地址计算
两个对象相等其hash值一定相等(java规定,不重写hashcode的话集合类无法去重,集合类放入时先判断hashcode是否一样,再用equals,可以提高程序插入和查询的速度)
如果重写equals没写hashcode方法,equals判断相等,但是不同的内容用的Object的hashcode方法会出现不一样,所以要重写hashcode
20. dubbo
服务注册,服务发现,故障转移、路由转发、负载均衡


所谓路由机制,是指设置一定的规则对服务提供者列表进行过滤。负载均衡时,只在经过了路由机制的服务提供者列表中进行选择。

网络通信模型:包括通信协议(header和body,序列化,默认hessian2)和线程派发机制(请求在io线程还是业务线程执行,默认all,即除了心跳包和io之外都在业务线程池执行; io线程和业务线程,通信协议相关参数有threads,queues,iothreads,serialization),io线程中不能有阻塞操作,否则影响效率,简单的如心跳包都放在io线程中
拓展机制
基于spi理念,filter机制
泛化调用
dubbo调用方式两种,普通的反射调用和泛化调用(不用引入jar包,网关类,业务中很少使用)
普通接口调用 ->
反射调用(java反射,已知类名,方法名,参数,抽象出公共方法)->
泛化调用(采用一种统一的方式来发起对任何服务方法的调用,适用于网关,透传等没有很多业务逻辑的场景),依赖@DubboReference的ReferenceConfig关键类;使用GenericService的$invoker方法
当服务消费端发生调用时,我们使用 Map 来存储一个具体的请求参数对象,然后传输到服务提供方。由于服务提供方引入了模型相关的 Jar,服务提供方在执行业务方法之前,需要将 Map 转化成具体的模型对象,然后再执行业务逻辑。
21. 代理
代理方式:
- 静态代理:自己手写代理类,调用被代理类的方法,代理类和原始类的关系在运行前就已经确定
- 动态代理
动态代理实现方式
- jdk自身的代理,基于反射生成的代理对象(public final class $Proxy0 extends Proxy implements Interface)代理对象继承自Proxy对象实现了被代理的接口,由于java单继承,所以java代理只能代理接口(运行时生成对象,获取类信息,甚至可以修改类定义,Class、Field、Method、Constructor,反射能绕开限制) 实例化的是Proxy对象,依赖少,代码简单,需要实现InvocationHandler类加上增强逻辑,Proxy.newProxyInstance(classLoader, classes(被代理的接口), handler(增强类));缺点只能代理接口
- cglib(cglib基于ASM,底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类,不能对final修饰的类或方法进行增强)原理是:CGLIB来创建一个继承实现类的子类,用Asm库动态修改子类的代码来实现AOP效果;高性能;
- Instrument,实际上提供了 一种虚拟机级别支持的 AOP 实现方式
字节码增强工具(目标类都没有被提前加载到JVM中)
- ASM,它可以直接生产 .class字节码文件,也可以在类被加载入JVM之前动态修改类行为。ASM的应用场景有AOP(Cglib就是基于ASM)、热部署、修改其他jar包中的类等;ASM虽然可以达到修改字节码的效果,但是代码实现上更偏底层,是一个个虚拟机指令的组合,不好理解、记忆,和Java语言的编程习惯有较大差距。
- Javassist:编辑字节码,可以说更加简单、易懂,符合java编程习惯
Instrumentation接口来做运行时字节码文件的修改,是JVM提供的一个可以修改已加载类的类库
如需要运行时修改字节码(Arthas)
Attach API的loadAgent()方法,能够将打包好的Agent jar包动态Attach到目标JVM上
问题:
同一个类中的方法,调用有注解的方法,会使AOP注解失效
原因:Spring的动态代理帮我们动态生成了一个代理的对象,暂且叫他
U
s
e
r
S
e
r
v
i
c
e
。所以调用
h
e
l
l
o
(
)
方法实际上是代理对象
UserService。所以调用hello()方法实际上是代理对象
UserService。所以调用hello()方法实际上是代理对象UserService调用的。但是在hello()方法内调用同一个类的另外一个注解方法saveUser()时,实际上是通过this.saveUser()执行的, this 指的是UserService 对象,并不是$UserService代理对象调用的,没有走代理。所以注解失效。
注解
注解的本质就是一个继承了 Annotation 接口的接口,生成注解的实例,它本质上就是一个代理类,你应当去理解好 AnnotationInvocationHandler 中 invoke 方法的实现逻辑,这是核心。一句话概括就是,通过方法名返回注解属性值。
一般是自定义注解+aop实现如登陆校验,格式化输出,操作日志打印等功能
1,定义注解;2:方法中引用;3:定义切面(获取注解属性值等信息,根据属性值进行切面前后操作)
Instrument
Instrumentation接口来做运行时字节码文件的修改,是JVM提供的一个可以修改已加载类的类库
如需要运行时修改字节码
通过java agent实现
使用 Instrumentation,使得开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,这样的特性实际上提供了 一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。
Agent是JVMTI的一种实现,JVMTI 提供了一套”代理”程序机制,可以支持第三方工具程序以代理的方式连接和访问 JVM,并利用 JVMTI 提供的丰富的编程接口,完成很多跟 JVM 相关的功能。
22. 泛型(参数是匿名内部类)
public CompletableFuture<T> whenComplete(
BiConsumer<? super T, ? super Throwable> action) {
return uniWhenCompleteStage(null, action);
}
使用
.whenComplete(
new BiConsumer<List<String>, Throwable>() {
@Override
public void accept(List<String> uid, Throwable throwable) {
System.out.println("whencomplate2");
System.out.println(uid);
}
}
)
或者
.whenComplete(
(uid, e) -> {
System.out.println("whencomplate");
System.out.println(uid);
}
泛型类:用< >括住,放在类名后面,类型参数可用于声明变量、形参、返回值、强制类型转换
public class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
泛型接口:用< >括住,放在接口名后面。
interface two<A>{
}
泛型方法:用< >括住,放在方法返回值前面。泛型参数可以被用到形参声明、方法返回值、方法定义中的变量声明和类型转换
// 比较三个值并返回最大值
public static <T extends Comparable<T>> T maximum(T x, T y, T z)
{
T max = x; // 假设x是初始最大值
if ( y.compareTo( max ) > 0 ){
max = y; //y 更大
}
if ( z.compareTo( max ) > 0 ){
max = z; // 现在 z 更大
}
return max; // 返回最大对象
}
23. try-catch性能
try块内禁止指令重排序优化,会影响性能(try块要小,try块范围尽量只覆盖抛出异常的地方)
for循环内try-catch需要处理catch内的逻辑,加上异常多的话,性能会差(for循环内不要try catch)
24. 事件驱动
需要
- 事件类,继承自EventObject,封装有事件源对象
- 监听器,实现EventListener,内部含事件类对象,定义回调方法
- 事件源,内部含有监听器类对象,并调用监听器回调方法
事件源变动 会调用回调用法,实现事件驱动
25. 缓存
只读缓存(读多)和读写缓存(读写比接近,只更新缓存,和缓存打交道,缓存和db打交道)
只读缓存更新策略:
要求没那么高的,设置key过期时间或者定期从数据库拉数据
要求高的,同步更新,通过事务保证一致性,降低性能
26.泛型
泛型方法(返回值前面➕泛型定义):
public void print(T t){
System.out.println(t);
}
泛型接口:
interface showInterface
{
public void show(T t);
}
泛型类:
class Test
{
public void show(T t){
System.out.println(t);
}
}
一般使用泛型方法和接口,不使用泛型类是因为类指定了泛型的类型后,这个对象只能使用该类型,不像泛型方法那么灵活
27. 微服务
服务描述,(接口设计规范,XML,RESTful api,postman上面能看到的各种格式规范)
服务框架(通信框架(http,socket),通信协议(http,socket),序列化)
服务注册(zk),
服务监控,
服务追踪,
服务治理(负载均衡,路由容错,删除故障节点)
mybatis #和$区别
#会加单引号,能防止sql注入,$不加,用于动态指定表名,变量名
本文详细探讨了Java中的HashMap,包括扩容和重hash策略,以及JDK7和JDK8的区别。此外,还介绍了LinkedHashMap和ConcurrentHashMap的实现。文章还涵盖了异常处理的最佳实践,如避免生吞异常和合理使用finally。同时,讨论了final、finally、finalize与垃圾回收的关系,以及强引用、软引用、弱引用和虚引用的区别。通过对String、StringBuffer、StringBuilder的对比,阐述了它们在内存占用和性能上的差异。文章还涉及了Java的写时复制策略,以及@Transactional注解的事务管理和回滚规则。最后,文章探讨了Spring设计模式,如单例、工厂模式、代理模式、观察者模式和适配器模式,以及Spring Cloud的作用域和循环依赖处理。

被折叠的 条评论
为什么被折叠?



