原创

JAVA知识点

JVM内存模型

内存模型分为3个大块类加载、运行数据区、执行引擎

类加载:主要功能是类的加载和查找、以及内存的分配,大体流程:加载、校验、准备、解析、初始化

运行数据区:堆、栈、方法区、本地方法区、程序计数器;堆中存放new出来的对象,栈中存放一个个栈帧执行要执行的一个个方法,方法区存放常量池以及方法,本地方法区存放native方法,程序计数器存放当前运行到哪里的地址

执行引擎:即时编译器和垃圾回收器,即时编译器:将字节码转化为cpu可执行的机器码,垃圾回收器回收堆中没用的对象;

垃圾回收算法

  • 标记清除算法:将没用的对象进行标记,然后进行垃圾回收,该算法会使内存存在零散的碎片,该算法主要用在老年代上面;
  • 标记复制算法:将堆内存分为平等的A B两份,对象都是存在A中,当触发标记复制算法时,那些需要保留的对象会做上标记并且复制到B中,然后将A全部清空,等B满了又会更上次一样将有用的对象复制到A中然后将B清空,如此反复;该算法主要用在新生代中;
  • 标记移动算法:将堆中的有用对象做上标记,将垃圾对象清除后会将有用的对象移动到一起;老年代前期会用标记清除算法,当碎片化太多了就会用标记移动算法;

什么叫线程安全,如果保证

当多个线程对共享资源进行操作,操作的结果跟预期的一样就算线程安全;线程安全要从三个方面进行保证:原子性、可见性、有序性三个方面,原子性指一个操作开始了就不能被其他操作打断,可见性指线程对变量做了修改了其他线程要马上知道,有序性指代码要有序执行避免发生指令重排序,一般我们可以用锁来保证线程安全,比如synchronized、reentrantlock等

Reentrantlock

  • 可重入锁
  • 实现了非公平锁和公平锁
  • 实现了阻塞和非阻塞的两种机制(lock  trylock)

Mybatis缓存

mybatis缓存分为两级:一级缓存和二级缓存

一级缓存存在sqlsession中,当我们执行语句时候会缓存在local cache中,当第二次执行的语句会先去查询local cache,如果存在则直接返回;当其他sqlsession执行的时候会导致脏数据;

二级缓存值多个sqlsession共享,当开启二级缓存的时候会先去查询二级缓存,再去查询一级缓存;

分布式系统不建议使用

什么事消息中间件(MQ)

消息队列(message queue)主要实现服务与服务之间的异步通信,主要由三个角色组成:生产者、服务者、消费者

生产者:主要是消息的生产方,进行消息生产

服务者:主要是对消息进行存储和投递

消费者:主要对消息进行消费

MQ使用场景

  • 削峰:当流量过大导致大量请求堆积可以用mq进行削峰,将一些没必要马上返回的数据推到mq中慢慢处理;
  • 解耦
  • 异步处理的一些操作

Spring框架理解

spring框架的两大核心概念IOC和AOP

IOC(控制反转):控制:在传统的java程序里面,都是程序去主动调用对象的构造方法去创建对象;IOC提供了一个容器,由容器控制对象的创建。反转:传统的java程序存在对象依赖关系,要我们主动去创建对象获取依赖;现在IOC容器控制创建对象后,帮我们查找和注入依赖,对象只能被动接受依赖对象,所以是反转。

IOC好处:传统的java程序存在依赖都需要主动创建依赖,代码的耦合度太高了,IOC帮忙创建对象并注入依赖,可以降低代码的耦合度。

AOP是面向切面,我们可以把一些公共的代码抽象出来,当需要用到的时候只需要一个注解就可以完成,提高了代码的复用性,降低了代码的耦合度,方便了代码的书写。


SpringMVC框架理解

springMVC框架分为:控制层、视图层、模型层

控制层:

视图层:网页页面

模型层:其实也就是要返回的数据

工作原理:用户通过http请求会被服务器的Servlet统一拦截,Servlet会根据请求的url分发给SpringMVC的控制层,到控制层后开发者就可以拿到用户的请求数据并进行逻辑处理,处理完后要返回的数据就是模型(Model),控制器就会把数据打包发回Servlet,Servlet在返回给用户。


JAVA多态

存在继承的关系,父类的句柄指向子类实例化出来的对象。概括来说就是同一个接口根据传入的对象不同产生不同的结果。


抽象类和接口类的区别

接口:

  • 接口只能存在抽象方法,JDK8后可以存在default方法和静态方法;

  •  调用静态方法必须通过接口类调用;

  • 调用default方法必须通过实现类调用;

  • 当多个接口中有相同的方法并且一个类去实现了多个多个接口必须重写相同的方法;

  • 接口类方法默认的访问修饰符都是public;

  • 只能定义常量;

抽象类:

  • 可以存在抽象方法也可以有普通方法;

  • 可以定义变量以及常量;

  • 子类必须实现抽象类的所有抽象方法,否则子类也必须标记为抽象类;

接口类是一组规则的集合,是为了实现多态,而抽象类是为了代码的复用

final

  • 修饰类:表示类不可以被继承

  • 修饰方法:方法不可能子类重写,但可以重载

  • 修饰变量:变量一旦被赋值就不可以修改

  • 修饰类变量,只能在静态初始化块中指定初始值或者声明该类变量的指定初始值

public class Test {

final static int a;

static {
a = 1;
}
}
  • 修饰成员变量,可以在非静态初始化块声明该变量或者构造器中执行初始化值

public class Test {

final int a;
{
a = 1;
}
}

public class Test {

final int a;

public Test(int a) {
this.a = a;
}
}
  • 修饰局部变量,在初始的时候可以不用赋值,但是在用的时候一定要赋值

public class Test {

public void fun(){
final Integer a;
a = 1;
}
}

Integer

Integer类型数据在进行比较时候最好是用 compareTo方法进行比较,因为Integer -128  ~ 127 的值会进行缓存,在-128 ~ 127 比较时候用 == 是没有问题的,但是超出这个范围就会出错

private static class IntegerCache {
        //缓存的最小值
        static final int low = -128;
static final int high;
static final Integer cache[];

static {
// high value may be configured by property //缓存最大值 int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;

cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);

// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}

private IntegerCache() {}
}



重载/重写

重载:发生在一个类中,方法名称相同,参数类型 个数 顺序不同,返回值类型和修饰符可以不同
重写:发生在父子类中,方法名称 参数必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符范围大于等于父类,父类的修饰符为private则不能重写

String/StringBuffer/StringBuider

String: final修饰 每次操作都会产生新的对象
StringBuffer和StringBuilder都是在原对象操作
StringBuffer是线程安全
StringBuilder非线程安全


hashCode

  • 为了查找的快捷性(JVM会维护一张hash表,hash表中的索引就是hashcode,通过索引就可以很快找到对象在堆中的位置);

  • 两个对象equals相等,hashcode一定相等;

  • 对象的hashcode相同不代表对象相同,只能说明两个对象在散列存储结构中存放在同一个位置;

  • 重写equals那么hashcode也要重写;


HashMap(JDK8)

  • 基于数组+链表+红黑树;

  • 初始容量16 容量必须为2的次方

  • 最大的容量大小2^30

  • 负载因子0.75

  • 数组下标:key的hashcode与上hashmap容量-1

//这是Hash值
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
//这是下标得计算
if ((p = tab[i = (n - 1) & hash]) == null)
(n - 1) & hash
  • 链表深度超过8 数组长度超过64的时候会转换为红黑树

  • 当红黑树的深度为小于等于6时又会转为数组+链表

  • 数据量小的时候应该尽量避开红黑树。因为红黑树需要进行左旋,右旋,变色操作来保持平衡,并且红黑树所占用的空间是数组+链表的双倍,是一种空间换时间,当数据量不多的时候并不能体现出这种优势,所以当数组长度小于64,链表深度小于8,使用数组加链表比使用红黑树查询速度要更快、效率要更高

ConcurrentHashMap

jdk7:

原文链接:https://blog.csdn.net/y277an/article/details/95041965

oncurrentHashMap使用分段锁技术,将整个数据结构分段(默认为16段)进行存储,然后给每一段数据配一把锁(继承ReentrantLock),当一个线程占用锁访问其中一个段的数据的时候,其他段的数据仍然能被其他线程访问,能够实现真正的并发访问。

put过程:

  • 首先对key进行第1次hash,通过hash值确定segment的位置

  • 然后在segment内进行操作,获取锁

  • 获取当前segment的HashEntry数组后对key进行第2次hash,通过hash值确定在HashEntry数组的索引位置

  • 通过继承ReentrantLock的tryLock方法尝试去获取锁,如果获取成功就直接插入相应的位置,如果已经有线程获取该Segment的锁,那当前线程会以自旋的方式去继续的调用tryLock方法去获取锁,超过指定次数就挂起,等待唤醒

  • 然后对当前索引的HashEntry链进行遍历,如果有重复的key,则替换;如果没有重复的,则插入到链头

  • 释放锁

size过程

  • size操作就是遍历了两次所有的Segments,每次记录Segment的modCount值,然后将两次的modCount进行比较,如果相同,则表示期间没有发生过写入操作,就将原先遍历的结果返回

  • 如果经判断发现两次统计出的modCount并不一致,要重新启用全部segment加锁的方式来进行count的获取和统计了,这样在此期间每个segement都被锁住,无法进行其他操作,统计出的count自然很准确

JDK8:

采用CAS+ synchronized


ArrayList会存在越界

虽然arrayList是自动扩容的,但是它是个非线程安全的方法,如果多个线程同时对进行add的操作,就会导致越界。


Mybatis有自带缓存为什么还要用Redis进行缓存

mybatis有一级缓存和二级缓存,一级缓存指的是在一次事务中,commit之后就会失效;二级缓存作用就不止在一次事务中了,二级缓存针对的是每个不同的mapper,每次有操作到mapper时候都会重新刷新缓存,现在都是流行分布式框架,就会导致其中一个修改了,其他的服务器不知情,没事及时刷新缓存,导致脏数据,二级缓存不推荐使用;而用redis最大的好处就是他是独立部署的,可以解决分布式的缓存的问题。


JDK8主要新特性

1.引入了lambda表达式

2.增加java.time,解决了java诟病很久的时间操作类

3.接口类增加了默认方法


缓存穿透、缓存击透

缓存穿透:缓存中没有存在数据去查询数据库,数据库也没有,而用户还一直在请求就会导致缓存击穿;解决方法:可以将key-value的缓存设置成key-null;

缓存击穿:缓存中没有存在数据,这个时候并发大量的用户同时请求,没有在缓存中读取到,又全部同时去读取数据库中的数据,导致数据库压力过大;


Exception是所有异常类的父类,throwable是所有异常和错误的父类


乐观锁和悲观锁

乐观锁:对于并发间操作产生的线程安全问题持乐观状态,认为竞争不是一直都会发生,因此它不需要持有锁,只有在比较/设置这两个动作作为一个原子操作尝试去修改,如果失败则表示发生冲突,那么就有响应的重试逻辑。

悲观锁:对于并发间操作产生的线程安全问题持悲观态度,认为竞争总是发生,所以每次对,偶资源记性操作的时候,都会持有一个独占锁。


Redis比Mysql快

1.最重要的是存储介质不同,mysql的存储介质是磁盘,而redis的存储介质是内存。

2.数据结构不同,mysql主要是以b+树的结构进行存储,而redis是采用key-value的结构进行存储。

3.redis是单线程多路复用io,单线程避免了线程切换的开销。而多路复用io避免了io等待的开销。


Springboot主要组件

config配置中心、eureka注册与发现中心、zuul网关、fegin远程服务调用组件、ribbon负载均衡、分布式任务调度中心、分布式事务、分布式锁


Redis数据结构

Redis有5种基础数据结构,分别为: string (字符串)、list (列表)、set (集合)、hash (哈希)和zset(有序集合)。

string (字符串)

字符串 string 是 Redis 最简单的数据结构。Redis 所有的数据结构都是以唯一的 key 字符串作为名称,然后通过这个唯一 key 值来获取相应的 value 数据。不同类型的数据结 构的差异就在于 value 的结构不一样。

list (列表)

Redis 的列表相当于 Java 语言里面的 LinkedList,注意它是链表而不是数组。这意味着 list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n),这点让人非常意外。
当列表弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。

hash (哈希字典)

Redis 的字典相当于 Java 语言里面的 HashMap,它是无序字典。内部实现结构上同 Java 的 HashMap 也是一致的,同样的数组 + 链表二维结构。第一维 hash 的数组位置碰撞 时,就会将碰撞的元素使用链表串接起来。
当 hash 移除了最后一个元素之后,该数据结构自动被删除,内存被回收。

set (集合)

Redis 的集合相当于 Java 语言里面的 HashSet,它内部的键值对是无序的唯一的。它的 内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值 NULL。
当集合中最后一个元素移除之后,数据结构自动删除,内存被回收。

zset (有序列表)

zset 可能是 Redis 提供的最为特色的数据结构,它也是在面试中面试官最爱问的数据结 构。它类似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。它的内部实现用的是一种叫着{跳跃列表}的数据结构。
zset 中最后一个 value 被移除后,数据结构自动删除,内存被回收。 zset 可以用来存 粉丝列表,value 值是粉丝的用户 ID,score 是关注时间。我们可以对粉丝列表按关注时间进行排序。
zset 还可以用来存储学生的成绩,value 值是学生的 ID,score 是他的考试成绩。我们 可以对成绩按分数进行排序就可以得到他的名次。

程序、进程、线程的概念

  • 程序:静态的代码
  • 进程:进程是系统运行程序的基本单位,系统会为每个进程分配CPU、内存、文件读写权限等
  • 线程:线程是比进程还小的执行单位,一个进程执行的时候会有多个线程,同一个进程下的线程共享一个内存空间和系统资源


线程有哪些状态

  • 创建(new):初始状态,线程刚被创建出来,还没有start()
  • 运行(runnable):运行状态,这里包含就绪和运行两种状态
  • 阻塞(blocked)
  • 等待(waiting):等待状态,表示等待其他线程进行唤醒
  • 超时(time_waiting):等待超时,可以在指定时间后进行苏醒
  • 终止(terminated):线程执行完成




异常

spring事务回滚只会对RuntimeException的错误进行回滚


List

  • ArrayList: 线性不安全,底层采用数组,有序,可重复
  • Vector::跟ArrayList差不多,但是它是线性安全的
  • LinkedList:线性不安全,底层采用双向链表,有序,可重复

Set

  • HashSet:底层基于hashMap,无序,唯一
  • LinkedHashSet:基于LinkedHashMap实现,有序,唯一
  • TreeSet:有序,唯一,红黑树,非线性安全

Map

  • HashMap:无序,唯一,数组+链表+红黑树,线性不安全
  • LinkedHashMap:有序,唯一,数组+链表+红黑树
  • HashTable:无序,唯一,数组+链表
  • ConcurrentHashMap:线性安全(CAS)、无序,数组+链表+红黑树


Cookie/Session

  • Cookie:保存在浏览器的,一般保存用户信息
  • Session:保存在服务器的,服务器记录用户的状态


URI/URL

  • URI:统一资源表示,唯一标识一个资源
  • URL:统一资源定位符,提供资源的路径
  • URL是URI的子集

HTTP/HTTPS

  • HTTP:默认使用端口是80,不加密,传输都采用明文
  • HTTPS:是在HTTP协议上面增加了SSL/TLS加密,传输是采用密文的,默认端口是443
  • HTTP比HTTPS消费资源少,但是HTTPS比HTTP安全


Memcached/Redis

共同点

  • 都是基于内存的数据库
  • 都有过期策略
  • 性能都很高

区别

  • Memcached数据结构只有k-v,Redis支持更多的数据结构:k-v list set zset hash
  • Memcached数据只存在内存里面,Redis可以把内存里面的数据持久化到磁盘中
  • Memcached是多线程非阻塞IO复用的网络模型,Reids采用单线程IO多路复用模型
  • Memcached没有原生的集群模式,Redis支持集群模式

Iterator/ListIterator

Iterator能够遍历List Set 但是只能向后遍历

ListIterator只能遍历List,但是可以向前也可以向后,并且可以修改

Java锁的种类

  • 乐观锁
  • 悲观锁
  • 可重入锁
  • synchronize

BlockingQueue的使用

多线程环境中,通过队列可以很容易实现数据共享,比如经典的“生产者”和“消费者”模型中,通过队列可以很便利地实现两者之间的数据共享。假设我们有若干生产者线程,另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程,利用队列的方式来传递数据,就可以很方便地解决他们之间的数据共享问题。但如果生产者和消费者在某个时间段内,万一发生数据处理速度不匹配的情况呢?理想情况下,如果生产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到一定程度的时候,那么生产者必须暂停等待一下(阻塞生产者线程),以便等待消费者线程把累积的数据处理完毕

volatile

  • 可见性
  • 防止指令重排序

分布式id实现

  • 基于数据库递增
  • UUID
  • 雪花算法
  • Reids自增
  • 百度Uidgenerator
  • 美团Leaf

JAVA类加载器

  • 根类加载器:加载JAVA_HOME/lib/下面的jar和class文件
  • 拓展类加载器:加载JAVA_HOME/lib/ext/下面的jar和class文件
  • 系统类加载器:加载classpath的类文件

双亲委派

要加载一个类的时候最开始是由最底层的类加载器发起的,他会先去自己的缓存里面查找有没有加载过当前这个类,如果有的话就直接返回没有的话就向上委派,一直找到最顶级的类加载器,如果最顶级的类加载器也没有找到就会通过查找路径进行加载,如果最顶级的类加载器通过路径也没有找到就会开始向下查找,由底下的类加载器去进行查找,如果有人通过路径找到了就会加载成功,如果一直到最低级的类加载器通过路径都没有查找到就会报类不存在的异常,

好处是防止核心的代码被篡改,也同事避免了类的重复加载

GC回收机制

  • 引用计数器:如果对象被引用了引用计数器就会+1,如果引用被剔除了引用计数器-1,引用计数器=0的时候表示该对象已经没有被引用了可以被GC回收了,但是存在一个问题,如果两个对象存在互相引用那么就永远都不会被GC回收
  • 可达性分析法:由GC Roots向下搜索,如果一个对象没有跟GC Roots没有存在引用连接了则会被GC 回收

PS: GC Roots是什么:虚拟机栈、静态常量

线程的状态

New(创建)、start(就绪、运行)、阻塞、等待、终止

Sleep、wait、join、yield

sleep:休眠,让出cup的执行权,时间到了再重新取回CPU的控制权,如果当前存在锁不会是让锁的资格

wait:挂起,当前线程进入到等待池里面,如果调用wait有加时间则时间到了会重新去竞争CPU的控制权,如果没有加时间则要等待调用notify方法

join:调用方调用某个线程的join方法,则会等待那个线程执行完了调用方才会继续执行

yield:跟wait有点类型,让出CUP控制权,但是马上去竞争CPU的控制权

线程安全/线程不安全

多线程情况下没有对堆内存里面的共享常量进行修改可以理解为线程安全

多线程的情况下对堆内存里面的共享常量进行修改了为线程不安全

并发的三大特性

原子性:一个操作要么全部做完,要么都不做

可见性:volatile、synchronized、final

有序性:volatile、synchronized

线程池

优点:

  • 减少资源开销:线程创建跟销毁开销都很大
  • 提高响应速度:可以把线程池里面的东西直接用
  • 对线程进行管理
为什么使用阻塞队列?

使用阻塞队列,如果入队的任务大于阻塞队列的长度,阻塞队列可以通过阻塞保留想要入队的任务

如果阻塞队列已经空了,可以让获取任务的线程挂起,释放cpu

如果阻塞队列有任务了,有可以自动唤醒挂起的线程

为什么先添加到阻塞队列,满了再去创建最大线程?

在创建新的线程的时候是要获取全局锁的,这个时候其他的线程是会进行阻塞的,会影响整体的效率。而且线程池的初衷就是为了避免频繁创建和销毁线程

线程池线程复用的原理

线程池对Thread进行了封装,在执行任务的时候并不是调用start,而且循环读取阻塞队列里面的任务,有任务就拿过来然后调用run方法

Spirng的设计模式有哪些

简单工厂模式:BeabFactory

工厂模式:FactoryBean

适配器模式:handlerAdapter

装饰器模式:类名包含wapper的,可以给对象提供一些额外的功能

动态代理:aop 、jdk代理、cglib代理

观察者模式:spring中的监听类

策略模式:spring中资源访问

Spring事务什么时候会失效

直接通过this调用方法,没有通过Autowired

方法不是public

数据库不支持事务

异常被吃掉

没有被spring管理

SpringMVC九大组件

  • handlerMapping
  • handlerAdapter
  • HandlerExceptionResolver

  • ViewResolver

  • RequestToViewNameTranslator

  • LocaleResolver

  • ThemeResolver

  • MultipartResolver

  • FlashMapManager

SpringBoot starter

定义一个配置类加上@Configuration里面写需要的Bean对象,然后在META-INF/spring.factories加入该配置类路径,springBoot就会来扫描他。

mybatis优缺点

  • 我们不用手动去创建和销毁数据库连接
  • 支持数据库与对象的ORM映射
  • 基于SQL

${} / #{}

${}是替换 #{}是占位符,#{}可以防止SQL注入

mysql锁

属性上:读锁、写锁

粒度上:

行锁

表锁

记录锁

页锁

间隙锁

临界锁


意向排他锁、意向共享锁

慢查询

看select有没有查出额外没用的列出来

用explain执行计划看有没有命中索引

数据量太大可以考虑分表分库

正文到此结束