触发Full gc的条件
- 调用System.gc()
- 老年代空间不足
- 永久代空间不足
- 统计得到的minor GC晋升到旧生代的平均大小大于老年代的剩余空间
- 堆中产生的大对象超过阈值,-XX PretenureSizeThreashold进行设定
- 老年代连续空间不足
- CMS GC的时候出现promotion failed(在Minor GC的过程中,幸存者to区容量不足以容纳Eden区和另一个Survivor区存活的对象,那么多余的被移动到老年代叫做过早提升,这会导致老年代中短期存活对象的增长,可能会引发严重的性能问题,再进一步,如果老年代满掉了,Minor GC后会进行Full GC,这将导致遍历整个堆,称为提升失败)和concurrent mode failure(老年代碎片化严重)
线程池拒绝策略
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
- ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
char 和varchar的区别?
- char的存取速度还是要比varchar要快得多,方便程序的存储与查找;但是char也为此付出的是空间的代价,因为其长度固定,所以会占据多余的空间,可谓是以空间换取时间效率。varchar则刚好相反,以时间换空间
- 声明式的char(M)长度固定,M在0-255中,varchar长度可变,范围是0-65535
Redis的数据结构跳跃表?
跳表就是链表与二分法的结合,链表从头节点到尾节点都是有序的,可以进行跳跃查找(形如二分法),降低时间复杂度,通过多级索引的方式,越高级的索引包含的范围越大,数据量越少
Mysql的主从复制
主从复制是指把数据从一个数据库服务器的主节点上复制到其他一个或者多个从节点的数据库服务器,Mysql数据库默认采用的是异步复制的方式,从节点数据库不用一直访问主服务器也可以实现更新数据
主从复制主要涉及到三个线程:log dump thread,i/o thread, SQL thread
当从节点连接到主节点时,主节点会创建一个log dump线程,用于发送binlog的内容,在读取bin-log中的操作时,此线程会对主节点的bin-log加锁,当读取完成时,甚至在发给从节点之前,锁会被释放
当从节点上执行start slave命令之后,从节点会创建一个I/O线程来连接主节点,请求主库中更新的bin-;-log,I/O线程接收到主节点binlog dump进程发送来的更新后,保存在本地的relay log中
sql线程负责读取relay log中的内容,解析成具体的操作并且执行,保证主从一致性
主从复制有几种类型?
同步复制:master的变化,必须等待slave-1...slave-n完成后才能返回
异步复制:类似ajax请求,master只需要完成自己数据库操作即可,至于salves是否收到二进制日志,是否完成操作,不需要关系
半同步复制:master只保证slaves中的一个操作成功,就返回,其他slave不管,这个功能是google为mysql引入的
有哪些方式能影响到主从复制的一致性?
半同步复制:之所以会读到旧的数据是因为主从同步需要一个时间段,而读取请求可能刚好就发生在同步阶段,为了读取到最新的数据,需要等主从同步完成之后,主库上的写请求再返回
利用中间件:借助中间件的路由作用,对服务的读写请求进行分发,从而避免出现不一致的问题,所有的读写请求都走数据中间件,通常情况下,写请求路由到主库,读请求路由到从库,记录所有路由到主库的key,在主从同步时间窗口内,如果有请求访问中间件,此时有可能从库还是旧数据,就把这个key的读请求路由到主库,此后依旧对读请求路由到从库
利用缓存:将某个库的某个key要发生写操作,记录在cache里,并且设置超时时间,然后修改数据库,当读请求发生的时候从cache里查看,对应的key有没有相关的数据,如果击中了缓存,说明发生过写操作,于是路由到主库读取最新的数据,如果miss,就路由到从库,继续读写分离操作
如何发现一个线上的慢sql语句?
- 修改数据库配置:slow_query_log设置为on,long_query_time设置慢sql的时间,slow_query_log_file记录日志的文件名,log_query_not_using_indexes,记录没有用到索引的sql,持久化可以在配置文件的my.ini里配置
- linux下有一个show processlist命令,显示哪些线程正在运行,核心主要看state列
- explain关键字
mysql里的各种log?
bin-log:记录了数据表结构和数据变更们勇于主从复制和恢复数据
redo-log:解决的问题:在内存中把数据改了,还没来得及落到磁盘,此时数据库挂了,这次更改就丢失了;为了解决此问题,在内存写完之后,会写一份redo-log,记载在某个页上做出的修改,因为它是顺序IO所以写入速度很快,文件体积很小,恢复速度也很快
undo-log:回滚和多版本控制(MVCC),存储的是逻辑日志,
relay-log:relay log各方面和binlog差不太多,区别是从服务器IO线程将主服务器的二进制日志读取过来并且记录到服务器本地文件,然后sql线程会读取relay-log的内容并且应用到从服务器,从而保证数据一致性
协程?
是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程,协程不归操作系统内核所管理,在用户态执行,其开销远远小于线程
值传递和引用传递的区别?
值传递:对于基本变量和包装类而言,传递的是该变量的一个副本,改变副本不影响原变量
引用传递:对于对象型变量而言,传递的是该对象地址的一个副本,而不是原对象本身
理解一下以下代码的不同:
StringBuilder sb = new StringBuilder("iphone");
void foo(StringBuilder builder) {
builder.append("4");
}
foo(sb); // sb 被改变了,变成了"iphone4"。
StringBuilder sb = new StringBuilder("iphone");
void foo(StringBuilder builder) {
builder = new StringBuilder("ipad");
}
foo(sb); // sb 没有被改变,还是 "iphone"。
常见的异常和错误?
异常:
- nullpointerexception:空指针异常
- classnotfoundexception:指定的类不存在
- arithmeticexception:数学运算异常
- arrayindexoutofboundsexception:数组越界异常
- illegalargumentexception:方法参数错误,比方是int值必须大于0,你传个-1进去
- illegalaccessexception:没有访问权限异常
错误:
- stackoverflow:栈溢出,应用递归的层次太深导致的堆栈溢出错误
- unknownerror:用于指示java虚拟机发生了未知的严重错误
- outofmemoryerror:内存溢出错误
- nosuchmethoderror:方法不存在错误
- nosuchfielderror:域不存在错误
- abstractmethoderror:抽象方法错误,在试图调用抽象方法的时候抛出
CopyOnWriteArrayList
继承了List,RandomAccess,Cloneable,Serializable接口
add方法:(set和remove方法也会加锁)
获取原数组->复制一个新的数组->将新的元素添加到新数组里->将原数组的引用指向新数组,整个过程使用ReentrantLock进行加锁
get方法:
读的时候不会加锁,就是普通的读方法
JMM内存模型?
java内存模型是一种虚拟机的规范,JMM规范率Java虚拟机和计算机内存是如何协同工作的:规定了所有的变量都存储在主内存,主内存是共享区域,所有线程都可以访问,但是线程对变量的操作必须在工作内存中进行,首先要讲主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成之后再将变量写回主内存,不能直接操作主内存中的变量,工作内存是每个线程的私有区域,不同的线程之间无法访问对方的工作内存
它解决的问题是:多线程通过共享信息通信的时候,存在的本地内存数据不一致,编译器会对代码指令重排序带来的问题
AQS原理和组件
AQS的核心思想是:如果请求的资源处于空闲状态,则将当前请求资源的线程设置为有效工作线程,并且将共享资源设定为锁定状态,如果被占用,那么需要一套线程等待以及被唤醒时锁的分配机制,这个机制是使用CLH队列实现的,即将获取不到锁的线程加入到队列中;
- AQS使用一个volatile修饰的int变量state来表示同步状态,通过内置的FIFO队列完成资源的排队工作,使用CAS实现原子操作对state进行修改,其底层使用了模板方法模式类似于TryAcquire这样的方法,默认情况下都抛出UnsupportedOperationException
- 如果当前线程竞争锁失败,aqs会把当前线程以及等待状态构成一个node加入到同步队列,同时阻塞该线程,当获取锁的线程释放锁的时候,会从队列中唤醒一个阻塞的节点
基于AQS的同步组件:Semaphore信号量,CountDownLatch(某一个线程运行前需要等待n个线程运行完毕),CyclicBarrier(让一组线程达到一个屏障(同步点)的时候被阻塞,直到指定数量的线程到达,被拦截的线程才会继续干活)
运行时数据区可能出现的异常?
本地方法栈,虚拟栈:超出栈的深度可能产生StackOverFlow异常,栈也可以动态扩展,如果无法申请到足够的内存也会抛出OOM
方法区:jdk6的时候还是永久代,抛出的异常是OOM:Permanent Space,后续改成了元空间OOM:Metaspace
堆:OOM:java heap space
JAVA对象创建的五个步骤,给对象分配空间的两种方法?对象头里有啥?
类加载检查,分配内存,初始化零值,设置对象头,执行init方法
- 指针碰撞:如果java堆中的内存是绝对规整的,已经被使用的内存和空闲内存各占一边,二者之间的分界点通过一个指针来表示,需要分配内存的时候把指针向空闲的地方挪动一段与对象大小相等的距离
- 空闲列表:如果java堆中内存并不规整,虚拟机会维护一个列表,表上记录可用内存块,分配内存时找到一个足够大的内存空间划分给对象实例
对象头包含两部分数据:运行时元数据(哈希值,GC分带年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳)和类型指针(指向类的元数据)
JVM的内存分配策略
对象优先在Eden区分配
大对象进入老年代 -XX:PertenureSizeThreshold参数
长期存活对象进入老年代
动态对象年龄判断:如果幸存者空间中相同年龄所有对象大小的总和大于其空间的一半,年龄大于等于该年龄的对象进入老年代,无需等到默认15的年龄
空间分配担保:发送minor gc之前,检查老年代可用空间是否大于新生代所有对象总空间,成立则说明minor gc是安全的,否则查看参数是否运行担保失败,如果运行,继续检查老年代最大可用内存是否大于历次晋升到老年代对象的平均大小,大于则尝试minor gc,否则进行full gc
STW是什么SafePoint是什么?
stop the word:进行垃圾回收的时候停止所有活动线程的操作
safePoint:代码执行过程中的一些特殊位置,当线程执行到这些位置的时候,说明虚拟机当前状态是安全的,如果有需要可以在此位置暂停
类加载过程简要描述一下?
- 加载:获取编译后的class文件,在方法区生成类信息,静态数据结构,在堆区生成该类的class对象
- 验证:文件格式,元数据,字节码,符号引用这些的验证
- 准备:为类变量(静态变量),分配内存并且进行赋0值,如果实例变量被final修饰,也会在准备阶段进行赋0值操作
- 解析:将常量池中的符号引用转为直接引用
- 初始化阶段:执行java代码,根据Java程序对变量进行赋值,初始化等操作(父类静态变量->子类静态变量->父类属性,父类构造器->子类属性->子类构造器)
数据库三大范式
列不可再分,保证每一列的原子性
属性完全依赖于主键
属性不依赖于其他非主属性
Redis缓存为什么这么快?
- 完全基于内存
- 数据结构简单,操作也简单
- 采用单线程,避免上下文切换的开销
- 使用非阻塞多路IO复用模型
- 底层模型不同,自己构建了VM机制