- 所有的异常的父类都是Throwable,它有两个子类,分别是Error和Exception
- Errowr:表示系统错误,通常不能处理和恢复,比如StackOverFlowError或者OutOfMemoryError,出了问题只能结束程序
- Exception:表示程序可以处理的异常,Exception又分为Checed Exception(被检查异常)和Runtime Exception(运行时异常,也叫做不被检查的异常)
-
Checked Exception应该被检查并尝试修复(即必须try-catch处理),比如FileNotFoundException。
-
RuntimeException不要求被处理,比如NullPointerException,这通常是因为糟糕的编码造成的。
可以,都是认为没有必要,直接修改这个bug,报error的时候,程序已经中止了,捕获没有意义
- 栈:java 虚拟机栈主要是利用栈先进后出的特性存储局部变量表,动态链接等,存放局部变量和栈帧(main)。
递归可能导致这个问题,所以我们可以理解为栈溢出就是方法执行是创建的栈帧超过了栈的深度。那么最有可能的就是方法递归调用产生这种结果。
-
堆中主要存储的是对象,不存放基本类型和对象引用,全装
new
出来的对象本身。如果不断的new对象则会导致堆中的空间溢出新建对象可能导致这个问题,调整vm options 参数
oom可以捕获,触发了error之后,执行状态已经无法恢复了,辞职需要终止现场甚至终止虚拟机,但是没必要,仅在我们可控的代码下,并且try中存在申请大量内存的时候,此时的oom是可以被catch住的,当catch住之后,需要主动的释放一些可控的内存,避免又会触发oom
集合就是存放数据的容器,存放数据对象引用的容器,而不是对象本身,集合类型有单列集合和双列集合单列集合collection子接口有set、list、queue三种子接口。
单列集合 Collection:
list:有序、可以重复、可以插入多个null值、元素都有索引
set :无序、不可以重复、只允许一个null值
双列集合map:是一个键值对,从Map集合中检索元素时,只要给出键对象,就会返回对应 的值对象。
- 从数据结构:arraylist 是动态数组的数据结构,而linkedlist是双向链表的数据结构
- 从访问效率:arraylist比linkedlist在访问的效率要高一点,因为linkedlist是链表的存储,需要移动指针从前往后以此查找
- 增加和删除:在非首尾的增加删除的操作下,linkedlist比arraylist的效率要高,因为arraylist的增加删除需要影响数组内其他元素的下标
- 空间占用:linkedlist比arraylist更占空间,因为linkedlist的节点除列要储存数据还要存两个引用,一个指向前一个元素,一个指向后一个元素
- 总的来说读取多的就用arraylist,增加删除多的用linkedlist
1、Vector是线程安全的,ArrayList不是线程安全的。
2、ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍
不安全,可以通过 Collections 的 synchronizedList 方 法将其转换成线程安全的容器后再使用。
了解过,举个例子,在两个线程,线程1通过iterator在遍历集合a元素,在某个时候线程2修改了集合a结构,这个时候就会抛出一个ConcurrentModificationException 的异常,就会引起fail-fast的机制,这个机制是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变操作时,就会引发fail-fast的机制,主要原因就是迭代器在遍历访问集合的时候,会使用一个modcount的变量,当集合发生变化的时候,这个modcount就会改变值,当迭代器使用hashnext或next的时候遍历下一个元素的时候,就会检测到modcount变量是否与expectemodcount变量一致,不一致抛出异常,一致则继续
copyonwritearraylist就是当我们在一个容器里面添加元素的时候,不直接往容器里面添加,而是先copy一份出来,在copy出来的新容器里面添加元素,再把原引用指向这个新容器
优点:适用于读大于写的场景,保证读的高性能
缺点:内存占有的问题,占有内存比较大,不能保证实时一致性,可以保证最终一致性
set是一个无序的,通常说的set无序是指的hashset,因为hashset是根据hash值排序的,但是linkedhashset和treeset是可以保证元素的顺序的
应用场景:对值进行排序,去重
底层实现,底层实际使用的容器是treemap。对于treemap而言,就是采用红黑树来保存map,每个entry都被当作一个红黑树的节点
hashmap 底层数组默认16 加载因子0.75 扩容是原来的两倍
实现原理:hashmap是基于hash算法实现的当使用put的时候,先判断table或者length是否等于-
如果是则需要扩容,如果不是则根据index = (table.length - 1) & hash计算出索引下标,然后通过taqble[i]是否等于null,如果等于null则直接插入,不等于null则判断key是否存在,存在则直接替换value,如果不存在则判断该节点是否是treenode,是则在红黑树中插入,不是则遍历链表,判断key是否存在,不存在则插入节点,判断链表长度是否大于8,大于转红黑树,然后,在size++。如果实际大小大于阈值就扩容
扩容机制:元素的个数到达了扩容的阈值(容量*负载因子)就会调用resize进行扩容,扩容都是原来的2倍,扩容后的对象位置要么在原位置,要么移动到原偏移量的2倍位置
为什么是2的幂次方:计算元素下标是n-1与hash其实和hash取余n是一样的,为了防止索引越界
加载因子为什么是0.75?
- 如果加载因子设置比较大的话,占用空间比较小,都是容易发生hash冲突,如果加载因子小的话,扩容的门槛就比较低,会占用更多的空间,发生的hash冲突几率会比较小。
concurrenthashmap对整个桶数组进行分割分段,然后每个分段上都用了lock锁,在锁的粒度上更精细了,jdk1.7的concueeenthashmap是基于分段的数组+链表实现的,jdk1.8是采用数组+链表+红黑树,在jdk1.7concurrenthashmap是对整个桶数组进行f分割分段,每一把锁只锁容器一部分数据,多线程访问容器不同数据段的数据,就不会存在锁竞争,提高了并发能力默认分配16个segment,到了1.8时候摒弃segment的概念,采用node数组+链表+红黑树,并发控制syncheonized和cas来哦操作,看还是可以看到segment的数据结构,是为了兼容旧版本
在运行状态中,可以动态获取类的信息以及动态的调用对象的方法称之为反射
-
-
继承thread类
-
实现runnable的接口
-
试用excurtorservice,vallabled,future来实现有返回结果的多线程
-
-
wait与sleep区别
- 类的不同:sleep() 是 Thread线程类的静态方法,wait() 是 Object类的方法。
- 是否释放锁:sleep() 不释放锁;wait() 释放锁。
- 用途不同:Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
- 用法不同:wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。
-
start与run区别
start只是意味着这个线程进行就绪状态并没有运行,底层会去调用start0的方法,这个方法是被native修饰的,底层是c++调用的
run只是一个方法
-
newFixedThreadPool:创建的是定长的线程池,可以控制线程最大并发数,超出的线程会在线程队列中等待,使用的是无界队列,核心线程数和最大线程数一样,当线程池中的线程没有任务时候立刻销毁,使用默认线程工厂。。
-
newSingleThreadExecutor:创建的是单线程化的线程池,只会用唯一一个工作线程执行任务,可以指定按照是否是先入先出,还是优先级来执行任务。同样使用无界队列,核心线程数和最大线程数都是1个,同样keepAliveTime为0,可选择是否使用默认线程工厂。
-
newCachedThreadPool:设定一个可缓存的线程池,当线程池长度超过处理的需要,可以灵活回收空闲线程,如果没有可以回收的才新建线程。没有核心线程数,当线程没有任务60s之后就会回收空闲线程,使用有界队列。同样可以选择是否使用默认线程工厂。
-
newScheduledThreadPool:支持线程定时操作和周期性操作
1.提交任务后会首先进行当前工作线程数与核心线程数的比较,如果当前工作线程数小于核心线程数,则直接调用 addWorker() 方法创建一个核心线程去执行任务;
2.如果工作线程数大于核心线程数,即线程池核心线程数已满,则新任务会被添加到阻塞队列中等待执行,当然,添加队列之前也会进行队列是否为空的判断;
3.如果线程池里面存活的线程数已经等于核心线程数了,且阻塞队列已经满了,再会去判断当前线程数是否已经达到最大线程数 maximumPoolSize,如果没有达到,则会调用 addWorker() 方法创建一个非核心线程去执行任务;
4.如果当前线程的数量已经达到了最大线程数时,当有新的任务提交过来时,会执行拒绝策略
总结来说就是优先核心线程、阻塞队列次之,最后非核心线程。
(1)corePoolSize:线程池中常驻核心线程数 (2)maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1 (3)keepAliveTime:多余的空闲线程存活时间。当前线程池数量超过corePoolSize时,当空闲时间到达keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止。 (4)unit:keepAliveTime的时间单位 (5)workQueue:任务队列,被提交但尚未执行的任务 (6)threadFactory:表示生成线程池中的工作线程的线程工厂,用于创建线程,一般为默认线程工厂即可 (7)handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)
拒绝策略
(1)AbortPolicy(默认) 直接抛出RejectedExecutionException异常阻止系统正常运行。 (2)CallerRunsPolicy 如果队列已满,且执行线程数大于等于maximumPoolSize时,会将某些任务退回给调用者。 (3)DiscardOldestPolicy 抛弃队列中等待最久的任务 (4)DiscardPolicy 直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。
使用future来接收结果
就是以空间换时间的方式解决多线程并发问题。当使用ThreadLocal维护变量的时候,会为每一个使用该变量的线程创建一个独立的变量副本,这样就能让各线程相互隔离,保证线程安全。
- 互斥条件:一个资源一次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得资源保持不放
- 不剥夺条件:进程获得的资源,在未完全使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的环形等待资源关系
Integer integer = integers.stream().max(Integer::compareTo).get();
![image-20220917172604462](/Users/smile/Library/Application Support/typora-user-images/image-20220917172604462.png)
加载 验证 准备 解析 初始化 使用 卸载
加载 从class字节码文件中获取二进制字节流
验证 字节流里面是否符合虚拟机规范中的要求
准备 为静态变量分配内存,然后赋值(普通静态变量赋默认值,加finall的静态变量直接赋值,引用类型 null)
解析 虚拟机将常量值中的符号引用替换为直接引用的过程
初始化 执行初始化方法
使用
卸载
BootStrapClassLoader是ExtClassLoader的⽗类加载器,默认负责加载%JAVA_HOME%lib下的 jar包和class⽂件。
ExtClassLoader是AppClassLoader的⽗类加载器,负责加载%JAVA_HOME%/lib/ext⽂件夹下的 jar包和class类。
AppClassLoader是⾃定义类加载器的⽗类,负责加载classpath下的类⽂件。
双亲委派简单来说,先找父亲加载加载,不行在儿子加载器自己加载
JVM在加载⼀个类时,会调⽤AppClassLoader的loadClass⽅法来加载这个类,不过在这个⽅法中,会 先使⽤ExtClassLoader的loadClass⽅法来加载类,同样ExtClassLoader的loadClass⽅法中会先使⽤ BootstrapClassLoader来加载类,如果BootstrapClassLoader加载到了就直接成功,如果 BootstrapClassLoader没有加载到,那么ExtClassLoader就会⾃⼰尝试加载该类,如果没有加载到, 那么则会由AppClassLoader来加载这个类。 所以,双亲委派指得是,JVM在加载类时,会委派给Ext和Bootstrap进⾏加载,如果没加载到才由⾃⼰ 进⾏加载。
好处:
- 沙箱安全机制,防止核心api库被随意 篡改
- 避免类的重复加载
Tomcat底层类加载不是用的双亲委派机制
![image-20220917180927420](/Users/smile/Library/Application Support/typora-user-images/image-20220917180927420.png)
- 新生代 存放所有新创建的对象,同时负责对象的老年代晋升
- 老年代 专门保存那些会被长期驻留的对象内容,如果用户创建了较大的对象,则对该对象直接存储进老年代
- 元空间 代表直接物理内存
ioc是spring俩大核心之一,为我们提供ioc的bean容器,这个容器会自动的帮助我们去创建bean,不需要手动的创建,ioc有一个强大的功能叫做di,依赖注入,我们可以通过java代码或者xml配置的方法把我们想要注入的依赖注入进去,正是有了这个依赖注入,使得ioc有一个强大的东西结偶,在springmvc出实话的时候,会自动吧所有的bean都创建好,这样我们程序运行的时候就不需要再去创建bean,这样速度也会快点,而且这些bean默认都是单例的,比如说我们业务代码中的dao,service都只有一份,用的时候直接用,不需要在重新创建,省了很多时间,这是我理解的ioc
aop是面向切面编程,在日常工作当中,会存在恒多重复的代码,比如说事物和日志,我们需要在很多类里面,同时把代码写进去,这样是很麻烦的,比如说事物,在所有的sercive层需要开启事物提交回滚事物,这些东西,spring的aop就为我们提供了这些方法,我们可以把共有的代码提取出来,切入到我们想要切入的类中,这样方便我们开发,减少不少冗余代码,提高了代码复用性。
- 采用声明的方式来实现(基于XML)
- 是采用注解的方式来实现(基于AspectJ)。
JDK动态代理只能对实现了接口的类生成代理,而不能针对类
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 实现MethodInterceptor
-
spring 进行扫描存放到这个beandefintionmap 中对map做一个遍历
-
然后通过 推断构造方法,找到一个最优的构造方法,在通过反射创建这个对象
-
对bean对象里面的属性进行填充,从单例池里面找需要填充的对象,如果不能找到,创建这个需要填充的对象
-
回掉实现aware接口的方法 如beannameaware
-
调用beanpostprocessor初始化前的方法
-
在调用initlazingbean的初始化方法
-
在调用beanpostprocess的初始化后方法进行aop处理
-
会把初始化后bean放到单例池里面
-
使用bean对象
-
最好会调用disposablebean的dsetory的方法销毁
为了提高性能,少创建实例
有个缺点,就是所有的请求都会共享这个bean,不能做到线程安全
不一定,懒汉式就不是线程安全的,饿汉式式线程安全的,在此基础可以升级为双重检验锁的方式也是线程安全的
先判断这个对象是否为空,然后同步锁枷锁,然后在判断这个对象是否为空,还为空就创建这个对象
一级缓存存放实例化对象,二级缓存存放内存创建好的但是还没有给属性赋值的对象,三级缓存存放对象工厂,用来创建提前暴露到bean的对象
a创建过程中需要b,然后a先把自己生成一个objectfactory对象暴露到三级缓存里面,
然后再去实例化b,b实例化发现需要a,然后先去一级缓存查询,没有,再去二级缓存去查询,没有,再去查询三级缓存,三级缓存找到,把a放到二级缓存,然后删除三级缓存的a,这个时候b初始化完成之后,就把自己放到一级缓存,单例池里面,这个时候,继续回去创建a,在一级缓存里面找b,完成创建,把a放到一级缓存里面
为什么三级缓存村objectfactory,不直接缓存对象
如果是objectfactory,在spring内部中,在产生bean的过程中可以通过beanpostprocessor来敢于bean,就是拓展这个bean
三级缓存解决原型bean
原型bean,只是在用的时候getbean去创建的,没有缓存
为什么不能用三级缓存解决构造器注入
构造器注入,首先a需要依赖b,b依赖a,那么创建a的时候需要b,创建b的时候需要a,因此谁都不能创建谁
-
简单工厂模式 beanfactory根据传入的唯一标识获取bean对象
-
工厂方法模式 factorybean spring 在使用getbean嗲用获取bean的时候会调用这个bean的getobject方法
-
单例模式 Spring在创建bean的时候默认是单例的
-
代理模式 aop中使用了两种动态代理 jdk的动态代理和cglib动态代理
-
模版模式 spring的jdbctemplate 就用到了模版模式
我认为springboot是为了更好的使用spring,使用spring会有大量的xml配置,而springboot 就是简化这些配置,约定大约配置,个性化的配置可以配置一下,极大的减少了配置项,还内嵌tomcat,省去配置tomacat,主要注重于业务代码的开发
在springboot启动类上面的注解有一个是SpringBootApplication
点进去会有一个@EnableAutoConfiguration是用于加载自动配置类
自动配置主要依靠三个核心技术,1.引入starter,启动依赖组件的时候,这个组件里面必须要包含@configuration的注解类,然后在这个配置类里面需要声明需要装配到ioc容器的bean对象,2.这个配置类是放到第三方的jar里面然后通过springboot的约定大于配置的一个理念去把这个配置类的全路径放在classpath:/meta-inf/spring.factories文件里面,这样springboot就知道第三方jar的配置位置,3.springboot拿到所有第三方jar包声明的配置类后在通过spring提供的importselector这样的接口来实现对这些配置类的动态加载
@Component 是通用注解,其他注解实在这个注解上的拓展
@Service 是标注这个类是业务层
@Controller 标注是业务控制层
是一种规范,定义一些get是用来取资源,post用来加资源,put修改资源,delete删除资源
@RestController = @Controller+@ResponseBody 组合注解,是将返回值自动转成json的格式
@Controller 声明该类是Controller层的Bean,一般返回到指定页面
#传入的参数在sql中会显示为一个字符串
$传入的参数直接替换,会有sql注入的风险
逻辑分页RowBounds ,这种 方式是通过查询之后的结果,进行分页,如果数据量大的话不可以取
拦截pagehelp,调用startpage之后,会把其后的第一个执行sql拼接上limit语句
mysql默认隔离级别 可重复读
-
分布式锁
setnx key value ex 10 一行命令 过期时间加随机数 保证原子性 主从集群的问题 a线程去a1机器加锁成功,这时a1机器挂了,b线程只能去b机器枷锁,这时候成功了,就是2个线程同时工作了 需要解决单点故障就是需要多台服务器,就需要解决主从问题? 不做主从,采用过半策略,加锁的机器过半判断加锁成功,加锁不过半,则不成功
-
redis 是单线程还是多线程?
1. 无论什么版本,工作线程就是一个 2. 6.x高版本出现了io多线程 3. 单线程,满足redis的串行原子,只不过id多线程后,把输入/输出放到更多的线程去并行,好处1.执行时间缩短,更快,2.更好的压榨系统及硬件的资源
-
redis 存在线程安全的的问题吗?为什么
重复2中的单线程串行 redis可以保障内部串行 外界使用的时候要保障,业务上要自行保障顺序
-
遇到过缓存穿透吗?
redis没有,数据库也没有 缓存一个key null 布隆过滤器
-
遇到缓存击穿吗?
redis没有,数据库有,其实就是没有缓存的,热点数据过期,大量并发进入数据库
-
如何避免缓存雪崩
-
缓存是如何回收的?(怎么删除过期key的?)
1.后台在轮询,分段分批的删除那些过期的key 2.请求的时候判断已经过期了 尽量的把内存无用空间回收回来~
-
缓存是如何淘汰的?
1.内存看见不足的情况下 2.淘汰机制里不允许淘汰 3.LRU(Least Recently Used)最近最少使用算法/LFU(Least Frequently Used)最近频次最少算法/FIFO(First In First Out)先入先出算法/Random 随机淘汰策略/TTL 超时时间 4.全空间 4.设置过过期的key的集合中
1. string set zset list hash
2. 基本上就是缓存
3. 为的是服务无状态,看你的项目有哪些数据结构或对象,单机需要单机锁,在多机需要分布式锁
4. 无锁
install 是把 工程打到本地仓库,把target下的jar包安装到本地仓库
package将项目打包到项目的target目录
Idea 软件右边maven 点开,里面有一个蓝色黑闪电的标志,选中test 在点一下