算法
广度遍历二叉树、双重校验单例
GC相关
垃圾回收器有哪些
G1会stw吗,什么时候会stw
会stw
我答的full gc会stw,然后问了下边,感觉应该答初始标计 最终标记 筛选回收会stw
minor gc和major gc会stw吗
Minor GC、Major GC、Full GC的触发时机
Minor GC会引发STW,暂停其他用户的线程,等垃圾回收结束,用户线程才恢复运行。
Maror GC的速度一般会比Minor GC慢10倍以上,STW的时间更长。
full GC是开发或调优中尽量要避免的,这样STW会短一些
什么时候会触发full gc
- 调用system.gc()
- 老年代空间不足
- 方法区空间不足
- 通过minor gc进入老年代的平均大小大于老年代可用空间
- 由Eden 、 from surivor像to surivor复制时, 对象大于to space空间且大于老年代空间时
为什么新生代复制算法要分成3块,两块不行吗
那为什么需要两个survivor区?一个不行?
如果只有一个eden区和一个survivor区,那么假设场景,当发生ygc后,存活对象从eden迁移到survivor,这样看好像没什么问题,很棒,但是假设eden满了,这个时候要进行ygc,那么发现此时,eden和survivor都保存有存活对象,那么你是不是要对这两个区域进行gc,找出存活对象,那么你想想是不是难度很大,还容易造成碎片,如果你使用复制算法,那么难度很大,如果你使用标记清除算法,那么容易造成内存碎片,如果你使用标记清除算法,那么耗时很长。
所以如果存在两个survivor区,那么工作就非常的 轻松,只需要在eden区和其中一个survivor(b1)找出存活对象,一次性放到另一个空的survivor(b2),然后再直接清除eden区和survivor(b1),这样效率是不是很快?快的一。
Mysql相关
Mysql的事务隔离级别
项目中用的哪个?为什么用可重复读?怎么解决的
我说的是mysql inodb可以解决脏读幻读不可重复读问题,
怎么解决的说的是 mvcc 、加锁 记录锁、间隙锁、临健锁
Mysql 的聚簇索引
数据和索引在一起为聚簇索引,常见的聚簇索引一般是主键索引
主键索引是聚簇索引,如果没有主键索引,会怎么办
选择表中第一个不为null的唯一索引作为聚簇索引,没有的话会创建一个隐藏的索引列。
mysql加了符合索引 a,b,c,d
select a,b,c from table where a > 10 order by b ;会走索引吗 Using filesort
select a,b,c from table where a = 10 order by b ;会走索引吗 索引排序
带范围的情况下,order by字段必须出现在where中,除了第一次范围,order by字段必须在where中全等的情况下才能索引排序
Java基础
只重写equals不重写hashcode的会怎样在hashmap里,put和get的情况
测试后 put时相同的对象会put进去,get不会有问题
@Data
public class User {
private String name;
private Integer age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(name, user.name) && Objects.equals(age, user.age);
}
// @Override
// public int hashCode() {
// return Objects.hash(name, age);
// }
}
public class Test {
public static void main(String[] args) {
User user1 = new User();
user1.setAge(18);
user1.setName("aaa");
User user2 = new User();
user2.setAge(18);
user2.setName("aaa");
HashMap<User, Integer> hashMap = new HashMap<>();
hashMap.put(user1, 1);
hashMap.put(user2, 2);
System.out.println("user1 value = " + hashMap.get(user1));
System.out.println("user2 value = " + hashMap.get(user2));
System.out.println("user1 hashCode = " + user1.hashCode());
System.out.println("user2 hashCode = " + user2.hashCode());
System.out.println("mapSize = " + hashMap.size());
}
}
LinkedList和ArrayList删除中间元素的时间复杂度
都是o(n) 参考链接
第一种情况, 插入和删除是在数据后面+随机访问数据
这种情况下,肯定是使用ArrayList会提供比较好的性能。
访问速度本身快、加上插入删除数据又是性能不比LinkedList慢。
第二种情况, 插入和删除是在数据的前面和中间+随机访问数据
这种情况下,建议使用LinkedList对象。
抛开随机访问数据的速度不说,由于ArrayList的数据结构,导致ArrayList在新增数据位置前面和中间的时候需要移动数组中插入位置之后的的所有元素,这样的开销相比访问数据的开销无疑是巨大的。而又因为LinkedList是基于双链表实现的,插入、删除操作是最快的。
AutomicInteger和Synchronized的区别
AutomicInteger 使用unsafe类实现,cas实现
ReentrantLock和Synchronized的区别
一个类有两个方法A和B,Synchronized锁住方法A,线程1访问A,线程2可以不等A方法结束直接调用B吗
可以,JVM可以从方法常量池中的方法表结构中的ACC_SYNCHRONIZED访问标识区分一个方法是否为同步方法,如果设置了,线程才会去持有monitor,没设置就直接调用了。(个人理解)
Spring相关
Autowired和Resource的区别
AutoWired是byType,如果这个注解就要byName或者一个接口有两个实现类可以通过@Qualifier 指定value来指定调用哪一个
Resource是byName,如果找不到会byType,或者指定type或name
Spring中Bean创建完成后执行指定代码的几种实现方式,同时使用的话执行的先后顺序。
按照三者的执行先后顺序分别是:
● 添加注解@PostConstruct
● 实现InitializingBean接口
● 实现ApplicationListener接口
PostConstruct是在BeanPostProcessor的postProcessBeforeInitialization前置处理方法中执行的,通过反射获得类的所有方法,然后调用一个函数接口的方法,这个函数接口的实现就是判断方法是不是被 @PostConstruct 标注,如果被标注的话放到一个名字叫 currInitMethods 的集合中,这个集合中的方法会在前置处理方法阶段执行。然后才执行InitializingBean的afterPropertiesSet方法,之后执行init-method方法,最后执行BeanPostProcessor的后置处理方法。(之前说aop在这里实现,应该不对,重新找了下资料,后面Spring aop如何实现有介绍)
@PostConstruct、afterPropertiesSet和init-method的执行顺序
spring和springboot的区别
Spring框架解决了企业级的开发的复杂性,它是一个容器框架,用于装java对象(Bean),使程序间的依赖关系交由容器统一管理,松耦合,提高了可测试性和维护效率,Spring主要为我们做了两件事,一省去了我们创建对象的操作,二声明了属性赋值.
Spring Boot框架是对Spring框架的补充,它消除了Spring框架配置XML的麻烦事,完善了Spring框架的开发环境,使我们可以更加高效的完成编程,并且为我们提供了 spring-boot-starter-web 依赖,这个依赖包含了Tomcat和springmvc等一系列的web依赖(无需部署war文件)。
Springboot的启动流程
- 首先从main找到run()方法,在执行run()方法之前new一个SpringApplication对象
- 进入run()方法,创建应用监听器SpringApplicationRunListeners开始监听
- 然后加载SpringBoot配置环境(ConfigurableEnvironment),然后把配置环境(Environment)加入监听对象中
- 然后加载应用上下文(ConfigurableApplicationContext),当做run方法的返回对象
- 最后创建Spring容器,refreshContext(context),实现starter自动化配置和bean的实例化等工作。
springboot的自动装配原理
简单总结:
在项目启动的时候,Spring Boot框架会自动读取META-INF/spring.factories配置文件中org.springframework.boot.autoconfigure.EnableAutoConfiguration所配置的配置类,然后将其中所定义的bean根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中。
详细总结
-
在Spring Boot项目中有一个注解@SpringBootApplication,这个注解是对三个注解进行了封装:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan
其中@EnableAutoConfiguration是实现自动化配置的核心注解。 -
该注解通过@Import注解导入AutoConfigurationImportSelector,这个类实现了一个导入器接口ImportSelector。在该接口中存在一个方法selectImports.
-
该方法的返回值是一个数组,数组中存储的就是要被导入到spring容器中的类的全类名。在AutoConfigurationImportSelector类中重写了这个方法.
-
该方法内部就是读取了项目的classpath路径下META-INF/spring.factories文件中的所配置的类的全类名。
在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中。
Q: “spring.factories中这么多配置,每次启动都要全部加载么?
A: 经历一遍筛选,@ConditionalOnXXX 中的所有条件都满足,该类才会生效;
@Configuration 和 @Component 的区别?
@Configuration 和 @Component 到底有啥区别?
Spring 注解中 @Configuration 和 @Component 的区别总结为一句话就是:
A
@Configuration 中所有带 @Bean 注解的方法都会被动态代理(cglib),因此调用该方法返回的都是同一个实例。
@Conponent 修饰的类不会被代理,每实例化一次就会创建一个新的对象。
在 @Configuration 注解的源代码中,使用了 @Component 注解:
Spring的aop是如何实现的,具体在什么时候生成代理对象,是在哪个方法
通过动态代理来实现aop,主要作用是分离功能性需求和非功能性需求,使开发人员可以集中处理某一个关注点或者横切逻辑,减少对业务代码的侵入,增强代码的可读性和可维护性。
在Spring AOP中,织入通常是在运行时通过代理来完成的。但是,在其他AOP框架中(例如:AspectJ),织入也可以在编译时或加载时完成。Spring AOP主要关注运行时织入,因为它提供了最大的灵活性和易用性。
这篇文章中还对比了Filter、Interceptor、AOP
Spring Boot – Spring AOP原理及简单实现
Spring生成代理对象的时机
Spring 学习笔记-InstantiationAwareBeanPostProcessor接口
Spring 高手之路 21——深入剖析 Spring AOP 代理对象的创建
在Spring AOP中,这一步骤主要通过检查目标bean是否实现了特定接口或已是代理对象来完成。关键的方法是在AbstractAutoProxyCreator类的postProcessBeforeInstantiation中实现,该方法是 Spring AOP 的核心,属于 Spring Bean 生命周期的一部分,特别是在后置处理器(BeanPostProcessor)机制中起重要作用。
AbstractAutoProxyCreator implements SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor extends BeanPostProcessor
故AbstractAutoProxyCreator 涉及四个方法
Instantiation :实例化
Initialization : 初始化
方法 | 执行顺序 | 备注 |
---|---|---|
postProcessBeforeInstantiation() | 在 Bean 创建前调用 | 可用于创建代理类,如果返回的不是 null(也就是返回的是一个代理类) ,那么后续只会调用 postProcessAfterInitialization() 方法 |
postProcessAfterInstantiation() | 在 Bean 创建后调用 | 返回值会影响 postProcessProperties() 是否执行,其中返回 false 的话,是不会执行。 |
postProcessProperties() | 在 Bean 设置属性前调用 | 用于修改 bean 的属性,如果返回值不为空,那么会更改指定字段的值 |
postProcessBeforeInitialization() | 在 Bean 调用初始化方法前调用 | 允许去修改 bean 实例化后,没有调用初始化方法前状态的属性 |
postProcessAfterInitialization() | 在 Bean 调用初始化方法后调用 | 允许去修改 bean 调用初始化方法后状态的属性 |
调用 postProcessBeforeInstantiation 方法
|
创建 TestBean 对象
|
设置 field 属性
|
调用 postProcessAfterInstantiation 方法
|
调用 postProcessProperties 方法
|
调用 postProcessBeforeInitialization 方法
|
调用初始化方法
|
调用 postProcessAfterInitialization 方法
– 更新20240807
今天又重新看了下这部分,aop动态代理是在bean初始化完成之后,所以比较困惑为什么会在实例化前有创建动态代理的动作,然后问了gpt,但其实我还是没理解透彻,对于他俩的调用时机仍比较迷糊,emmmm 后面有时间再研究吧
在Spring框架中,尤其是在涉及到AOP时,AbstractAutoProxyCreator
类起着核心作用。postProcessBeforeInstantiation
和postProcessAfterInitializationwrap
中的IfNecessary
,都是用于创建动态代理对象,但它们的作用和调用时机有所不同。下面是这两个方法的详细解释以及它们之间的区别:
postProcessBeforeInstantiation
功能与调用时机:
postProcessBeforeInstantiation
这个方法在Bean实例化之前被调用。它允许对即将创建的Bean的配置进行一定的处理。
具体来说,Spring会在这个阶段检查是否需要创建一个代理对象。如果返回的值不为null(可以是一个代理对象),则Spring将使用这个对象代替实际的Bean实例进行后续处理。
用法:
这个方法主要用于在Bean对象创建前进行判断,比如确定某个Bean是否应该被代理。
例如,基于注解或配置元数据,决定是否需要为某个对象创建代理。
- wrapIfNecessary
功能与调用时机:
wrapIfNecessary
方法在Bean实例化之后被调用,用于对已存在的对象进行代理处理。它确认传入的Bean是否需要被包装(即创建代理)。
这个方法会检查该Bean是否符合某些条件
(例如,是否具有特定的注解、是否是一个可以被代理的类型),如果条件满足,它将返回代理对象。
用法:
这个方法通常在返回Bean的过程中调用,负责将Bean包装成代理。如果Bean已经是代理,则原样返回。
主要区别总结
- 调用时机:
postProcessBeforeInstantiation
在Bean实例化之前调用,用于决定是否以代理替代未实例化的Bean。
wrapIfNecessary
在Bean实例化之后调用,用于对已经存在的Bean进行包装处理。
- 用途:
postProcessBeforeInstantiation
适用于条件检查,并能在Bean还未创建时返回代理。
wrapIfNecessary
则用于对对象进行进一步的代理处理或包装,通常返回的是实例化后的Bean,按照代理的标准返回代理对象。
- 实现逻辑:
postProcessBeforeInstantiation
通常进行的是判断(返回可能的代理对象或null),而wrapIfNecessary
则是进行实际的代理包装。
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
Object cacheKey = this.getCacheKey(beanClass, beanName);
if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
if (this.isInfrastructureClass(beanClass) || this.shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
}
TargetSource targetSource = this.getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
}
Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
Object proxy = this.createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
} else {
return null;
}
}
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return this.wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
} else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
} else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
} else {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
} else {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
}
jdk动态代理为什么必须实现一个接口?
因为JDK动态代理类($Proxy0)已经继承了Proxy这个类,所以只能通过接口来与被代理类建立联系(两个类建立起联系,一是继承的关系【jdk已经不能通过这个方式了,因为java仅支持单继承】,另一种就是实现同一个接口【JDK动态代理选这种】),所以必须要求被代理类也得实现一个接口,这样的话代理类与被代理类就能通过这个接口建立联系了。