Java引用类型
JVM 系列(5)吊打面试官:说一下 Java 的四种引用类型 # 我赌你不懂系列:Java的引用类型都有哪几种
Java主要有四种引用类型,用来管理Java对象的生命周期,Java引用类型一般分为强引用FinalReference、软引用SoftReference、弱引用weakReference、虚引用PhantomReference
使用引用对象
- 1. 创建引用对象: 直接通过构造器创建引用对象,并且直接在构造器中传递关联的实际对象和引用队列。引用队列可以为空,但虚引用必须关联引用队列,否则没有意义;
- 2. 获取实际对象: 在实际对象被垃圾收集器回收之前,通过
Reference#get()可以获取实际对象,在实际对象被回收之后 get() 将返回 null,而虚引用调用 get() 方法永远是返回 null; - 3. 解除关联关系: 调用
Reference#clear()可以提前解除关联关系。
四种引用介绍
- 强引用 :强引用指向的对象不会被垃圾收集器回收;
Object obj = new Object();其中obj就是new Object()的强引用,只要这个对象有强引用存在,它就不会被回收。 - 软引用: 软引用指向的对象在内存充足时起到类似强引用的效果,但在内存不足时还是会被垃圾收集器回收。SoftReference的实例保存一个Java对象的软引用,该软引用的存在不影响垃圾回收线程对该Java对象的回收。(欺软怕硬)
SoftReference<byte[]> soft = new SoftReference<>(new byte[1024*1024*10]); soft.get(); // 可以得到原始对象 - 弱引用:弱引用指向的对象无论在内存是否充足的时候,都会被垃圾收集器回收;
- 虚引用:虚引用是无法通过get方法来获取对象的,一个虚引用对象被回收时会被放在一个ReferenceQueue队列中,意思就是虚引用回收时会给出一个信号放在队列中。
线程池
线程池参数
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲线程的存活时间
TimeUnit unit, // 存活时间的单位
BlockingQueue<Runnable> workQueue, // 阻塞队列
ThreadFactory threadFactory, // 创建线程的工厂,可指定优先级
RejectedExecutionHandler handler){ // 拒绝策略
...
}
常见线程池
Executors类是一个工厂类,提供一些工厂方法帮助我们快速建立一个线程池。主要包括三个方法:newSingleThreadExecutor、newFixedThreadPool、newCachedThreadPool
- newSingleThreadExecutor:设置核心线程数和最大线程数都为1
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } - newFixedThreadPool:设置核心线程数和最大线程数为自定义的固定值
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
- newCachedThreadPool,核心线程数为0,但是最大线程数为Integer的最大值。适合生存期短的异步任务
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
BlockingQueue:在任意时刻只有一个线程可以进行take或者put操作,并且BlockingQueue提供了超时return null的机制
SynchronousQueue:一个特殊的队列,内部没有数据的存储空间,队列不能peek,因为不存在元素,任何入队的线程都会阻塞,直到有线程来出队,也就是这个队列是一组操作,入队和出队要一起离开,出队也是一样,必须等入队,必须成对执行,否则会阻塞
ArrayBlockingQueue:由数组支持的有界队列
LinkedBlockingQueue:由链接节点支持的可选有界队列
为什么不建议使用Exectors创建线程
以上三种方式创建的线程可能会导致OOM
- LinkedBlockingQueue 无界队列:可以一直加任务
- Integer.MAX_VALUE最大核心线程数:可以一直加线程
拒绝策略
拒绝策略是一个接口,只有一个rejectedExecution方法,包括四个实现类:
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
- AbortPolicy:拒绝(啥也不做)并抛出异常。A handler for rejected tasks that throws a {@code RejectedExecutionException}.
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); } - DiscardPolicy:拒绝(啥也不做)但不抛出异常。A handler for rejected tasks that silently discards the rejected task.
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {} - DiscardOldestPolicy:弹出队列中最早的任务,然后把当前任务加入到等待队列。A handler for rejected tasks that discards the oldest unhandled request and then retries {@code execute}, unless the executor is shut down, in which case the task is discarded.
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } } - CallerRunsPolicy:直接在调用者线程中运行。A handler for rejected tasks that runs the rejected task directly in the calling thread of the {@code execute} method, unless the executor has been shut down, in which case the task is discarded.
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } }
工作线程异常处理
当线程池中的某个线程出现异常之后,会从workers集合中删除这个线程
Spring中Bean的生命周期
- 收集BeanDefinition
- 实例化:给 Bean 分配内存空间
- InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation(实例化之前执行,返回的对象如果不为null,则会替换掉原本的bean)
- 实例化(需要推断构造函数,默认使用空参构造)
- InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation 实例化之后执行,如果返回 false 会跳过依赖注入阶段
- 属性注入:@Autowired @Resource @Value
- InstantiationAwareBeanPostProcessor#postProcessProperties 依赖注入阶段执行,如@Autowired, @Value, @Resource
- 初始化
- Aware接口
- InstantiationAwareBeanPostProcessor#postProcessBeforeInitialization 初始化之后执行 这里返回的对象会替换掉原有的bean,如代理增强
- @PostConstruct
- init-method
- InstantiationAwareBeanPostProcessor#postProcessAfterInitialization 初始化之后执行
- 使用
- 销毁
- DestructionAwareBeanPostProcessor#postProcessBeforeDestruction 销毁之前执行
- @PreDestroy 销毁之前执行
- destory-method 销毁之前执行
进程之间如何设置共享内存
- 创建共享内存区,通过shmget实现。在物理内存中开辟一块共享内存区。
- 把这块共享内存区挂接映射到两个进程的地址空间上,通过shmat实现。
- 完成通信之后,撤销内存映射关系,通过shmdt进行脱离。
- 删除共享内存区,通过shmctl实现。
熔断和限流
熔断:QPS、异常数量、异常比例
限流方法:计数器固定窗口算法、计数器滑动窗口算法、漏斗算法、令牌桶
操作系统中断的处理流程
MySQL排查慢查询
- 通过
show variables like '%slow_query_log%'查看慢查询日志,找到慢查询的时长以及日志存放路径,如果没有开启慢查询可以通过set global slow_query_log='ON';开启慢查询日志 - 通过explain分析表现为慢查询的sql语句
- 检查是不是因为没有走索引导致的慢,如果是这个原因可以通过设置适当的索引,如果是因为优化器的选择了直接扫全表,可以通过force index(index_name)强制走索引
explain的type字段
type 表示 MySQL 在表中找到所需行的方式, 又称"访问类型",主要包括ALL, index, range, ref, eq_ref, const, system, NULL (从左到右, 性能从差到好)
- ALL:全表扫描
- index:全索引扫描
- range:索引范围扫描, 常见于 '<', '<=', '>', '>=', 'between' 等操作符
- ref:使用非唯一性索引或者唯一索引的前缀扫描, 返回匹配某个单独值的记录行.
- eq_ref:类似ref, 区别就在使用的索引是唯一索引. 在联表查询中使用 primary key 或者 unique key 作为关联条件.
- const/system: 当 MySQL 对查询某部分进行优化, 并转换为一个常量时, 使用这些类型访问. 如将主键置于 where 列表中, MySQL 就能将该查询转换为一个常量, system 是 const 类型的特例, 当查询的表只有一行的情况下使用 system.
- NULL:MySQL 不用访问表或者索引就直接能到结果,比如select 1;
索引失效
模型数空运最快
- 模:代表模糊查询。like的模糊查询以%开头,索引失效。
- 型:代表数据类型。类型错误,如字段类型为varchar,where条件用number,索引也会失效。
- 数:代表函数。对索引的字段使用内部函数,索引也会失效。这种情况下应该建立基于函数的索引。
- 空:是Null的意思。索引不存储空值,如果不限制索引列是not null,数据库会认为索引列有可能存在空值,所以不会按照索引进行计算。
- 运:代表运算。对索引列进行加、减、乘、除等运算,会导致索引失效。
- 最:代表最左原则。在复合索引中索引列的顺序至关重要。如果不是按照索引的最左列开始查找,则无法使用索引。
- 快:全表扫描更快的意思。如果数据库预计使用全表扫描要比使用索引快,则不使用索引。
SQL执行顺序
- FROM子句组装数据(包括通过ON进行连接);
- WHERE子句进行条件筛选;
- GROUP BY分组 ;
- 使用聚集函数进行计算;
- HAVING筛选分组;
- 计算所有的表达式;
- SELECT 的字段;
- ORDER BY排序;
- LIMIT筛选。
原码、补码和反码
整数的原码、补码和反码都一样,只是负数有所不同。原码的负数是通过符号位来标识的,反码的负数除了用原位标识之外,还将其他位置的数值取反了。补码则是相当于反码+1。
计算机底层存储都是以补码的形式存储的,因为它的效率更高。
- 原码 :最高位是符号位,0代表正数,1代表负数,非符号位为该数字绝对值的二进制.
- 反码:正数的反码与原码一致,负数的反码是对原码按位取反,只是最高位(符号位)不变。
- 补码:正数的补码与原码一致,负数的补码是对原码按位取反加1,符号位不变。
比如byte -1,它的原码为10000001,反码为11111110,补码为11111111
再比如,如下的程序输出为-106,因为150默认是int类型,包含四个字节,转成byte之后会取第四个字节,变为10010110,它是补码的形式,减一得到反码10010101,转换为原码为11101010,代表-106。
byte b = (byte) 150;
System.out.println(Integer.toBinaryString(150));
System.out.println(b);
单例模式懒汉模式
public class LazySingleton {
private LazySingleton(){}
private volatile static LazySingleton lazySingleton;
public static LazySingleton getInstance(){
if(lazySingleton == null){ // 防止每次都加锁
// 多个线程会到达此处,但是只有一个线程能够加锁,如果没有第二个null判断,会导致下一个线程获取锁后再进行实例化操作
synchronized (LazySingleton.class){
if(lazySingleton==null){ // 防止多个线程依次获取锁之后被实例化多次
lazySingleton = new LazySingleton();
}
}
}
return lazySingleton;
}
}
AtomicInteger
AtomicInteger借助了Unsafe类进行CAS操作。AtomicInteger内部存在一个int类型的value属性,用来保存当前的值,在类初始化时会获取value属性相对于对象起始位置的偏移量,便于底层进行处理。在set值的时候
public class AtomicInteger extends Number implements java.io.Serializable {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset; // value的偏移量
private volatile int value; // 存储的当前值
static {
try {
//获取value字段相对AtomicInteger对象的"起始地址"的偏移量
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
...
}
public final native boolean compareAndSwapInt(
Object object, // 需要变更的对象(对这个对象中的int属性进行cas)
long offset, // 在对象中的偏移量
int expect, // 期望值(更新之前的值)
int update); // 更新值
LockSupport.park和LockSupport.unpark
Blocker是用来标识当前线程在等待的对象,即记录线程被阻塞时被谁阻塞的,用于线程监控和分析工具来定位,它在park之前先通过setBlocker()记录阻塞线程的发起者object,当线程锁被释放后再次清除记录;
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread); // 一个本地方法
}
public static void park(Object blocker) { // 在Thread类中存在一个parkBlocker属性用来给LockSupport调用
// 如果有许可,消耗许可并马上返回
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L); // 表示永远挂起,不是计时挂起
setBlocker(t, null);
}
public static void park() { // 不需要记录当前因为什么对象park时可以使用该方法
UNSAFE.park(false, 0L);
}
Spring中Bean的创建过程
所有的ApplicationContext(如AnnotationConfigApplicationContext) 继承了 GenericApplicationContext,GenericApplicationContext 中持有一个 DefaultListableBeanFactory,DefaultListableBeanFactory 继承了 AbstractAutowireCapableBeanFactory,AbstractAutowireCapableBeanFactory中有doCreateBean方法
AbstractAutowireCapableBeanFactory
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = (BeanWrapper)this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = this.createBeanInstance(beanName, mbd, args);
}
Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
synchronized(mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
this.applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
} catch (Throwable var17) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", var17);
}
mbd.postProcessed = true;
}
}
boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
if (earlySingletonExposure) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
}
this.addSingletonFactory(beanName, () -> {
return this.getEarlyBeanReference(beanName, mbd, bean);
});
}
Object exposedObject = bean;
try {
this.populateBean(beanName, mbd, instanceWrapper);
exposedObject = this.initializeBean(beanName, exposedObject, mbd);
} catch (Throwable var18) {
if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) {
throw (BeanCreationException)var18;
}
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18);
}
if (earlySingletonExposure) {
Object earlySingletonReference = this.getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
} else if (!this.allowRawInjectionDespiteWrapping && this.hasDependentBean(beanName)) {
String[] dependentBeans = this.getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet(dependentBeans.length);
String[] var12 = dependentBeans;
int var13 = dependentBeans.length;
for(int var14 = 0; var14 < var13; ++var14) {
String dependentBean = var12[var14];
if (!this.removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
try {
this.registerDisposableBeanIfNecessary(beanName, bean, mbd);
return exposedObject;
} catch (BeanDefinitionValidationException var16) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", var16);
}
}