Java面试题

收集的Java经典面试题


Java关键字final、static使用总结

final

  • final:终态,可以修饰非抽象类、非抽象类成员方法和变量
  • final类:不能被继承,没有子类,final类中的方法默认是final的。如果一个类不需要有子类,且类的实现细节不允许改变,并且这个
    类不会被扩展,可以设计为final类
  • final方法:不能被子类的方法覆盖,可以被继承。final方法使用原因:锁定方法,防止继承类修改;高效,编译器遇到final方法会转入内嵌机制,提高执行效率
  • final成员变量:常量,只能被赋值一次,赋值后值不再改变
  • final参数:可以读取该参数,但无法改变该参数的值
  • 注意:final不能用于修饰构造方法,private类型的方法默认是final类型的

    static

  • static:全局或静态
  • static:成员变量与成员方法:被类所有实例共享,静态变量在内存中只有一个拷贝,节省内存
  • static方法:静态方法,方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法
  • static代码块:JVM加载类时会执行,多处代码块会按照在类中出现的先后顺序依次执行

    static final

  • static final变量:全局常量
  • static final方法:方法不可覆盖

匿名内部类是什么?如何访问在其外面定义的变量。

访问在其外面的变量,必须通过关键字final作为形参传入
拷贝引用,为了避免引用值发生改变,例如被外部类的方法修改等,而导致内部类得到的值不一致,所以用final来让该引用不可改变

匿名内部类的创建

new 父类构造器(参数列表)|实现接口()
{
//匿名内部类的类体部分
}

应用场景

  • 只用到类的一个实例
  • 类在定义后马上用到
  • 类非常小(sun推荐代码一下)
  • 给类命名并不会导致代码更容易被理解

    使用原则

  • 不能有构造方法
  • 不能定义静态成员、方法和类
  • 匿名内部类不能是public,protected,private,static
  • 只能创建一个实例
  • 一个匿名内部类一定在new后面,用其隐含实现一个接口或一个类
  • 匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效

    初始化

    通过代码块完成初始化
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    public class OutClass {
    public InnerClass getInnerClass(final int age, final String name){
    return new InnerClass() {
    int age_ ;
    String name_;
    //构造代码块完成初始化工作
    {
    if( 18 < age && age < 36) {
    age_ = age;
    name_ = name;
    }
    }
    public String getName() {
    return name_;
    }

    public int getAge() {
    return age_;
    }
    };
    }

    public static void main(String[] args) {
    OutClass out = new OutClass();
    InnerClass inner_= out.getInnerClass( "chenssy");
    System.out.println(inner_.getName());

    InnerClass inner_= out.getInnerClass( "chenssy");
    System.out.println(inner_.getName());
    }
    }

如何理解分布式锁

对共享资源的竞争过程就是并发,对共享资源数据进行访问保护的最直接方法就是引入锁

锁的类型

  • 自旋锁(Spin Lock)
    自旋锁如果已经被别的线程获取,调用者会一直循环看该自旋锁的持有者是否已经释放了锁
    自旋锁是一种非阻塞锁,如果某线程需要获取自旋锁,但该锁已经被其他线程占用时,该线程不会
    被挂起,而是不断消耗CPU时间,不停尝试获取自旋锁
  • 互斥锁(Mutex Lock)
    互斥锁是阻塞锁,当某线程无法获取互斥锁时,该线程会被直接挂起,不再消耗CPU时间,当其他线程
    释放互斥锁时,操作系统会唤醒那个被挂起的线程
    阻塞锁不会导致CPU占用率过高,但进入时间以及恢复时间都比自旋锁略慢,竞争激励的情况下阻塞锁的性能要明显高于自旋锁
    Synchronized
    ReentrantLock
    Object.wait()/nofity()
    LockSupport.park()/unpark()
    多核处理器,根据预计线程等待锁时间来选择使用自旋锁或互斥锁
  • 可重入锁(Reentrant Lock)
    可重入锁是一种特殊的互斥锁,可以被同一个线程多次获取,而不会产生死锁
    首先是互斥锁:A不释放,B无法获取
    其次,可以被同一线程多次持有,A在释放锁之前又一次请求获取该锁,是可以成功的
    Synchronized
    ReentrantLock
  • 轻量级锁(Lightweight Lock)&偏向锁(Biased Lock)
    java锁状态:无锁,偏向锁,轻量级锁,重量级锁;且锁状态不能降级
  • JUC(Semaphore,CountDownLatch,CyclicBarrier)

    分布式锁

  • 传统方案:持久化数据库(InnoDB行锁,事务,version乐观锁)
  • 新方案:zookeeper,redis等分布式组件
  • redis缓存锁:
    setnx命令:在某个key不存在时才能set成功该key,达到多个进程并发set同一个key,只有一个可以set成功,
    通过expire设置过期时间,性能出色,劣势一旦缓存服务宕机,锁数据就丢失了。redis虽自带复制功能,但由于复制
    是异步完成,可能出现master节点写入锁数据未同步到slave节点时宕机,出现锁数据丢失

mysql两种引擎InnoDB与MyISAM对比

tips MyISAM InnoDB
Full Text索引 支持 不支持
count(*)性能(不使用where过滤) 内置计数器,性能比较高 扫描全表,性能较差(慎用),使用where过滤,同时不使用主键进行count,性能较好
对事务的支持 不支持 支持事务,具有ACDI特性,同时具有四种隔离级别
索引结构 索引采用B+树,同时数据和索引是分离的 主键索引和数据是一起的,其他索引是和数据分离的
锁级别 MyISAM主要是表锁,所以性能不高 InnoDB主要是行锁,操作的粒度降低,性能比较好
外键支持 不支持外键 支持外键

jvm加载一个类的过程,双亲委派模型中有哪些方法

加载,验证,准备,解析,初始化
双亲委派模型的方法:loadClass

jvm类加载机制:

  • 全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
  • 父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
  • 缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效

    双亲委派模型

    双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
    双亲委派机制:
  • 当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
  • 当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
  • 如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
  • 若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException

JVM内存结构

[http://kd.comzglb.clouddn.com/java-memory-model.png]

  • Heap(堆内存): 垃圾收集器管理的主要区域,GC堆,堆中没有内存完成实例分配,并且堆也无法扩展时,会抛出OutOfMemoryError异常
    • 年轻代:默认比例 1
      • Eden
      • From Survivor
      • To Survivor
    • 老年代
  • PermGen(方法区,永久代) : 存储类信息,常量,静态变量,即时编译器编译后的代码等,是线程共享区域,会对常量池回收和对类型的卸载,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常
  • Thread..N(栈) : java虚拟机栈和本地方法栈,用于方法的执行
    • java栈
    • 本地方法栈
    • 程序计数器:唯一没有规定任何OutOfMemoryError情况的区域
      [http://kd.comzglb.clouddn.com/jvm-area-ration.png]
      控制参数:
      -Xms设置堆的最小空间大小。
      -Xmx设置堆的最大空间大小。
      -XX:NewSize设置新生代最小空间大小。
      -XX:MaxNewSize设置新生代最大空间大小。
      -XX:PermSize设置永久代最小空间大小。
      -XX:MaxPermSize设置永久代最大空间大小。
      -Xss设置每个线程的堆栈大小。

GC算法

判断对象存活

引用计数
可达性分析 GC Roots开始向下搜索,没有在引用链上的清除
GC roots 一组必须活跃的引用,而非对象

  • 所有Java线程当前活跃的栈帧里指向GC堆里的对象的引用;换句话说,当前所有正在被调用的方法的引用类型的参数/局部变量/临时值。
  • VM的一些静态数据结构里指向GC堆里的对象的引用,例如说HotSpot VM里的Universe里有很多这样的引用。
  • JNI handles,包括global handles和local handles
  • (看情况)所有当前被加载的Java类
  • (看情况)Java类的引用类型静态变量
  • (看情况)Java类的运行时常量池里的引用类型常量(String或Class类型)
  • (看情况)String常量池(StringTable)里的引用

    垃圾收集算法

    标记-清除算法
    标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象
    缺点:效率不高 清除后会产生大量不连续内存碎片,会导致程序以后运行时需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作
    复制算法
    将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块的内存用完后,就将还存活这的对象复制到另外一块,再把已使用过的内存空间一次清理掉。
    优缺点:实现简单,运行高效,缺点是内存缩小为原来的一般,持续复制长生存期的对象则导致效率降低
    标记-压缩算法
    老年代特点,有人提出标记-整理算法,标记过程与标记-清除算法相同,但后续步骤是让所有存活的对象都向一端移动,然后清理掉端边界以外的内存
    分代收集算法
    GC分代基本假设:绝大部分对象生命周期非常短暂,存活时间短
    Generational Collection算法,把java堆分为新生代和老年代,然后根据各个年代特点采用最适当的收集算法。新生代中,每次垃圾收集时发现有大批对象死去,就选用复制算法。老年代中,对象存活率高,没有额外空间进行分配担保,必须使用标记清除或者标记整理算法。

    垃圾收集器–垃圾收集算法的具体实现

    Serial收集器
    只使用一个线程去回收,新生代复制算法,老年代标记-整理,垃圾收集过程中会stop the world
    Parallel收集器
    新生代复制,来年代标记整理 新生代并行,老年代串行
    Parallel Old收集器
    使用Parallel收集器,老年代并行
    CMS收集器
    目的:最短回收停顿时间,初始标记,并发标记,重新标记,并发清除
    优缺点:优点并发收集、停顿短,缺点产生大量空间碎片,并发阶段降低吞吐量
    G集器
    空间整合,不会产生空间碎片,可预测停顿。
    G整个Java堆划分为多个大小相等的独立区域,新生代与老年代不再是物理隔阂了,都是一部分区域的集合

    CMS和G比较,以及G缺点,SS解决什么样的问题

    CMS满足对相应时间的重要性需求大于对吞吐量的要求,内存中存在较多长生命周期的对象。

CMS(Concurrent Mark-Sweep)是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动JVM参数加上-XX:+UseConcMarkSweepGC ,这个参数表示对于老年代的回收采用CMS。

  • CMS回收器采用的基础算法是Mark-Sweep。所有CMS不会整理、压缩堆空间。这样就会有一个问题:经过CMS收集的堆会产生空间碎片。 CMS不对堆空间整理压缩节约了垃圾回收的停顿时间,但也带来的堆空间的浪费。为了解决堆空间浪费问题,CMS回收器不再采用简单的指针指向一块可用堆空 间来为下次对象分配使用。而是把一些未分配的空间汇总成一个列表,当JVM分配对象空间的时候,会搜索这个列表找到足够大的空间来hold住这个对象。
  • 需要更多的CPU资源。从上面的图可以看到,为了让应用程序不停顿,CMS线程和应用程序线程并发执行,这样就需要有更多的CPU,单纯靠线程切 换是不靠谱的。并且,重新标记阶段,为空保证STW快速完成,也要用到更多的甚至所有的CPU资源。当然,多核多CPU也是未来的趋势!
  • CMS的另一个缺点是它需要更大的堆空间。因为CMS标记阶段应用程序的线程还是在执行的,那么就会有堆空间继续分配的情况,为了保证在CMS回 收完堆之前还有空间分配给正在运行的应用程序,必须预留一部分空间。也就是说,CMS不会在老年代满的时候才开始收集。相反,它会尝试更早的开始收集,已 避免上面提到的情况:在回收完成之前,堆没有足够空间分配!默认当老年代使用%的时候,CMS就开始行动了。

G景,停顿时间可控,实时性较强,大幅度减少了长时间的GC,缺点会有一定程度的高吞吐。
应用程序具有如下一个或多个特征,适用于G而不是CMS
Full GC 次数太频繁或者消耗时间太长
对象分配的频率或代数提升显式变化
垃圾回收或内存整理时间较长

线程同步与阻塞的关系,同步一定阻塞吗,阻塞一定同步吗

两者没有必然关系,同步不一定阻塞,阻塞也不一定同步。

  • 同步异步
    同步异步关注的是消息通信机制。
    同步是发出一个调用时,没得到结果前,调用不返回,一旦返回得到返回值;异步是在调用发出后,直接返回,被调用者通过状态或通知来通知调用者或通过回调函数处理。
  • 阻塞非阻塞
    阻塞非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。
    阻塞调用是指调用结果返回之前,当前线程会被挂起,调用线程在得到结果之后才会返回;非阻塞调用是指在不能立刻得到结果之前,该调用不会阻塞当前线程。

mysql的查询慢了,如何优化索引

  • 开启慢查询日志,找到慢查询sql
  • 通过explain查看执行计划,注意设置SQL_NO_CACHE关闭缓存
  • where条件单表查,查看字段区分度。
  • 调整order by limit的顺序
  • 查看索引是否符合索引建立原则:最左前缀匹配,mysql会已知向右匹配知道遇到范围查询(>,<,between,like)停止;= 和in可以乱序; 选择区分度高的列建立索引 索引列不能参与计算;尽量扩展索引 not in !=不会使用索引

Join操作时内部的原理,Join的时候大表驱动小表还是小表驱动大表,走索引的时候的复杂度是怎样的

应该使用小结果集驱动大结果集,join通过嵌套循环实现的,驱动结果集越大,需要循环次数越多。

InnoDB的四种隔离机制,RC和RR的区别,以及在使用的时候有什么感受和问题

  • 第一级别 Read Uncommitted-RU 读取未提交内容
  • 第二级别 Read Committed-RC 读取提交内容
  • 第三级别 Repeatable Read-RR 可重复读
  • 第四级别 Serializable 可串行化

第一级别 Read Uncommitted(读取未提交内容)

所有事务都可以看到其他未提交事务的执行结果
本隔离级别很少实际应用,性能并不比其他级别好多少
该级别引发的问题–脏读(Dirty Read):读取到了未提交的数据

事务A,B:事务A开启事务,执行select,事务B开启事务,执行update;事务A在事务B执行update后即可查询到更新。

第二级别 Read Committed(读取提交内容)

大多数数据库系统默认隔离级别(不是mysql默认)
满足了隔离的简单定义:一个事务只能看见已经提交的事务所做的改变
该级别引发问题–不可重复读(Nonrepeatable Read):同一个事务中执行完全相同的select语句可能出现不同的结果—->原因:(有一个交叉的事务有新的commit,导致数据改变;(一个数据库被多实例操作时,同一事务的其他实例处理期间可能会有新的commit
不同事务执行相同select时有可能会出现不同结果

事务A,B:事务A开启事务,执行select,事务B开启事务,执行update;事务A在事务B执行update后提交事务后,才可查询到更新。

第三级别 Repeatable Read (可重复读)

MySQL默认事务隔离级别
确保同一事务的多个实例在并发读取数据时,看到相同的数据行
该级别引发问题—幻读(Phantom Read):当用户读取某一范围数据行时,另一个事务在该范围插入了新行,当用户再读取该范围数据行时,会发现有新的幻影行
InnoDB和Falcon存储引擎通过多版本并发控制(MVCC, Multiversion Concurrency Control)机制解决了该问题

事务A,B:事务A开启事务,执行select,事务B开启事务,执行update或insert;事务A在事务B执行update并提交后,事务A提交后方可查询到更新。

第四级别 Serializable (可串行化)

这是最高的隔离级别
通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,是在每个读的数据行上加上共享锁
在这个级别,可能导致大量的超时现象和锁竞争

事务A,B:事务A开启事务,执行select,事务B开启事务,执行update;事务B在事务A没有commit之前,是不能执行update更改数据的,只有A事务commit后,事务B方可执行update

脏读:读取到了未提交的数据
不可重复读:同一个事务中执行相同的select语句可能出现不同的结果
幻读:用户读取某一范围数据行时,另一个事务在该范围插入了新行,当用户再读取该范围数据行时,会发现有新的幻影行

分布式主键生成方式有哪些中,优势和劣势是什么?SnowFlake算法具体是怎么实现的

使用数据库的auto_increment生成主键

优点: 简单 保证唯一 保证递增 步长固定
缺点:可用性难以保证,主库写存在单点故障 扩展性差,有性能上限

单点批量ID生成服务

优点:保证了ID生成的绝对递增有序 降低了数据库压力,性能高
缺点:服务存在单点故障 存在单点性能上限

uuid

优点:本地生成ID 扩展性好
缺点:无法保证递增 uuid过长,索引查询效率低

取当前毫秒数/微秒数

优点:本地生成,时延低 ID趋势递增 ID整数,索引查询效率高
缺点:毫秒并发超过,会生成重复ID

SnowFlake算法 twitter开源的分布式ID生成算法

毫秒数(bits)业务线 机房 机器(三项共位) 毫秒内序列号(共位) 拼接生成ID

linux进程间的通信方式

管道 信号量 信号 消息队列 共享内存 套接字 命名管道

mysql索引失效的几种情况

  • 条件中有or,除非or条件中每一列都加上索引
  • like查询以%开头
  • 列类型是字符串,查询sql中要将数据使用引号引用起来

数据库范式

F:符合F的关系中的每个属性都不可再分
F:实体的属性完全依赖于主关键字。完全依赖是指不能存在仅依赖主关键字一部分的属性
F:任何非主属性不依赖于其他非主属性

InnoDB主键索引与普通索引的区别和查询时的过程

主键索引:加速查询+唯一值+表中只有一个+not null
普通索引:加速查询

sql查询两个数据集的补集

并集 union 结果集具有相同的结构 结果集列数 结果集对应的数据类型可以兼容 结果集不能包含order by和compute子句
差集 except 同上
交集 intersect 同上
补集 select * from a where (id) not (select id from b)

mysql索引B-tree的原理

如何分库分表

分表

  • 横向分表:横向切割为同样结构的不同表,表结构一样,根据特定规则来划分
  • 纵向分表:多列拆分为两张表,冷热数据分离

    分库

  • 分库:每个库中表不同
  • 分表:每个主机表相同

Linux下如何进行进程调度的

普通进程,实时进程。实时进程优先级高
实时进程:SCHED_FIFO 先进先出 SCHED_RR 轮转调度
普通进程:交互式进程 优先级较高 批处理进程 优先级较低

redis memcached区别,应用场景

  • 性能对比 redis使用单核,memcached可以使用多核,以上数据,memcached性能要高于redis
  • 内存使用效率 使用简单kv存储,memcached内存利用率更高,如果redis采用hash结构来做kv存储,由于其组合式的压缩,其内存利用率高于memcached
  • redis支持服务器端的数据操作,支持复杂的结构和操作
  • memcached 多线程,非阻塞IO复用的网络模型,引入锁带来了性能损耗,redis使用单线程的IO复用模型
  • 对数据持久化与数据同步有要求时,建议使用redis

应用场景:

  • redis:少量数据存储,高速读写访问,全部数据in-memory,提供数据落地功能
  • memcached:

如何解决Cookie盗取问题

  • Cookie设置httponly,使得浏览器的document对象看不到cookie,不能再js中操作cookie
  • 使用全站https
  • 使用session或者固定时间过期的Cookie

线程同步工具类

  • CountDownLatch–闭锁(一次性对象,进入终止状态后不能被重置):同步工具类,可以使一个或多个线程等待一组事件发生。可以理解为一扇门,闭锁到达结束状态前门是关闭的,并且没有线程可以通过。达到结束状态时,门会打开允许所有线程通过。闭锁达到结束状态后,永远保持打开状态。可以用来确保某些活动指导其他活动都完成后才继续执行。
  • Semaphore–计数信号量:用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。还可以用来实现资源池或对容器施加边界。Semaphore中管理着一组虚拟的许可。
  • CyclicBarrier–栅栏:所有线程必须同时达到栅栏位置,才能继续执行,闭锁用于等待事件,栅栏用于等待其他线程。

给你三个线程,分别处理任务,再交给主线程,你如何实现。

  • 可以使用join方法等待
  • 使用CountDownLatch

线程池底层实现

ExecutorService接口继承Executor接口,定义了终止,提交任务,跟踪任务返回结果等方法。

  • execute(Runnable command)
  • submit(task) task可为Callable或Runnable
  • shutdown()
  • shutdownNow()
  • isTerminated()
  • isShutdown()
    当创建线程池后,初始时,线程池处于RUNNING状态;
    如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕,最后终止;
    如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务,返回没有执行的任务列表;
    当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态

servlet生命周期

  • 初始化阶段,调用init()方法
  • 响应客户端请求阶段,调用service()方法
  • 终止阶段,调用destroy()方法

创建多线程的几种方式及区别

  • 继承Thread类
  • 实现Runnable接口
  • 使用Callable接口和Future
  • 线程池ExecutorService

wait,sleep区别

  • sleep睡眠时,释放CPU资源,保持对象锁,依然持有该锁
  • wait等待时,释放对象锁

多线程实现同步方式–阻塞(互斥)同步,非阻塞同步

同步和异步关注的是消息通信机制:调用者*主动等待这个调用的结果。而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态:阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

  • 阻塞同步:就是使用synchronized等进行的同步, 因为需要进行线程的阻塞与唤醒, 而这两个操作需要进行系统调用, 进行用户态与核心态的转换, 其性能影响较大,属于悲观的枷锁策略
  • 非阻塞同步:即不使用synchronized等, 不会进行线程的阻塞与唤醒. 其实现往往依赖于乐观加锁策略。

出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。
老张把水壶放到火上,立等水开。(同步阻塞)
老张觉得自己有点傻
老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)
老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。
老张把响水壶放到火上,立等水开。(异步阻塞)
老张觉得这样傻等意义不大
老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)
老张觉得自己聪明了。

所谓同步异步,只是对于水壶而言。
普通水壶,同步;响水壶,异步。
虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。
同步只能让调用者去轮询自己(情况),造成老张效率的低下。

所谓阻塞非阻塞,仅仅对于老张而言。
立等的老张,阻塞;看电视的老张,非阻塞。
情况情况老张就是阻塞的,媳妇喊他都不知道。虽然响水壶是异步的,可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。

java新特性,做一个List到Map的转换

List list
list.stream().collect(Collectors.toMap(Account::id,Account::number))

java线程池的实现,以及溢出的时候的抛弃策略

Java线程池会将提交的任务先置于工作队列中,在从工作队列中获取(SynchronousQueue直接由生产者提交给工作线程)。那么工作队列就有两种实现策略:无界队列和有界队列。无界队列不存在饱和的问题,但是其问题是当请求持续高负载的话,任务会无脑的加入工作队列,那么很可能导致内存等资源溢出或者耗尽。而有界队列不会带来高负载导致的内存耗尽的问题,但是有引发工作队列已满情况下,新提交的任务如何管理的难题,这就是线程池工作队列饱和策略要解决的问题。
饱和策略分为:Abort 策略, CallerRuns 策略,Discard策略,DiscardOlds策略。

  • Abort策略:默认策略,队列满时提交新任务抛出未检查的异常
  • CallerRuns策略:调节机制,既不抛弃任务也不抛出异常,而是将某些任务回退给调用者,会在线程池的线程中执行新的任务,而是在调用executor的线程中运行新的任务
  • Discard策略:新提交的任务被抛弃,不抛出异常
  • DiscardOldest策略:队列执行队头任务,然后尝试提交新任务,不适合工作队列为优先队列

针对不同场景,如何去设计线程池的大小,怎样计算线程池的大小

如果是CPU密集型应用,则线程池大小设置为N+1
如果是IO密集型应用,则线程池大小设置为+1
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 ) CPU数目
最佳线程数目 = (线程等待时间与线程CPU时间之比 +
CPU数目

  • 高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+减少线程上下文的切换
  • 并发不高、任务执行时间长的业务要区分开看:
      假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以适当加大线程池中的线程数目,让CPU处理更多的业务   假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换
  • 并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解耦。

JAVA反射机制,框架哪里设计过java的反射机制。通过反射调用一个方法,可以获取什么信息

spring ioc 加载类,
getDeclaredMethods可以获取类或接口声明的所有方法,包括公共,保护,默认访问和私有方法,但不包括继承的方法。
getMethods可以获取某个类的所有public方法,包括其继承类的公用方法。

springmvc原理

基于mvc的web框架

  • 发起请求到前端控制器(DispatcherServlet);
  • 前端控制器请求HandlerMapping查找Handler,可以根据xml配置、注解进行查找;
  • 处理器映射器HandlerMapping向前端控制器返回Handler;
  • 前端控制器调用处理器适配器去执行Handler;
  • 处理器适配器去执行Handler;
  • Handler执行完成给适配器返回ModelAndView;
  • 处理器适配器向前端控制器返回ModelAndView(是springmvc框架的一个底层对象,包括Model和View);
  • 前端控制器请求视图解析器去进行视图解析,根据逻辑视图名称解析真正的视图(jsp…);
  • 视图解析器向前端控制器返回View;
  • 前端控制器进行视图渲染,视图渲染就是将模型数据(在ModelAndView对象中)填充到request域中。
  • 前端控制器向用户响应结果。

Full GC和Minor GC区别,各自触发条件

Minor GC

从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC。
触发条件:
有一些值得注意的问题:

  • 当 JVM 无法为一个新的对象分配空间时会触发 Minor GC,比如当 Eden 区满了。所以分配率越高,越频繁执行 Minor GC。
  • 内存池被填满的时候,其中的内容全部会被复制,指针会从0开始跟踪空闲内存。Eden 和 Survivor 区进行了标记和复制操作,取代了经典的标记、扫描、压缩、清理操作。所以 Eden 和 Survivor 区不存在内存碎片。写指针总是停留在所使用内存池的顶部。
  • 执行 Minor GC 操作时,不会影响到永久代。从永久代到年轻代的引用被当成 GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉。
  • 质疑常规的认知,所有的 Minor GC 都会触发“全世界的暂停(stop-the-world)”,停止应用程序的线程。对于大部分应用程序,停顿导致的延迟都是可以忽略不计的。其中的真相就是,大部分 Eden 区中的对象都能被认为是垃圾,永远也不会被复制到 Survivor 区或者老年代空间。如果正好相反,Eden 区大部分新生对象不符合 GC 条件,Minor GC 执行时暂停的时间将会长很多。
    每次 Minor GC 会清理年轻代的内存。

    Major GC/Full GC

    Major GC 是清理永久代。
    Full GC 是清理整个堆空间—包括年轻代和永久代。

JMM内存模型,如何保证缓存一致性

JMM是Java程序对线程如何交互的统一的约定协议
Happens-Before 顺序: 保证了一个线程的操作结果能够对另一个线程可见
synchronized关键字提供互斥区和内存可见性, 防止重排序
volatile提供内存可见性,防止重排序,保证64位元素(double、long)的原子性读写

CMS垃圾回收器及其应用场景

老年代 集中在互联网站或B/S系统服务端上的Java应用

hashmap,如何处理hash冲突,为什么hashmap允许null值,resize过程,多线程下resize为什么会出现死循环

将相同hash值的对象组织成一个链表放在hash值对应的槽位。

java跳表的并发问题

Skip list的性质

  • 由很多层结构组成,level是通过一定的概率随机产生的。
  • 每一层都是一个有序的链表,默认是升序
  • 最底层(Level 1)的链表包含所有元素。
  • 如果一个元素出现在Level i 的链表中,则它在Level i 之下的链表也都会出现。
  • 每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。
    ConcurrentSkipListMap具有Skip list的性质 ,并且适用于大规模数据的并发访问。多个线程可以安全地并发执行插入、移除、更新和访问操作。与其他有锁机制的数据结构在巨大的压力下相比有优势。

NIO,BIO,AIO

BIO,同步阻塞式IO,简单理解:一个连接一个线程
NIO,同步非阻塞IO,简单理解:一个请求一个线程
AIO,异步非阻塞IO,简单理解:一个有效请求一个线程
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

什么是二叉平衡树,如何插入节点,删除节点,说出关键步骤

二叉平衡树,又称AVL树。它或者是一棵空树,或者是具有下列性质的树:

  • 具备二叉排序树的所有性质;
  • 左子树和右子树深度差的绝对值不超过1;
  • 左子树和右子树都是二叉平衡树。
    根节点插入,判断是否存在,否则左子树递归查找插入,如插入未成功,则在右子树递归查找插入。插入结束后判断是否需要自旋

什么是一致性hash

环形分段hash,防止hash偏斜,可通过物理节点虚拟出虚拟节点来解决

Arraylist如何实现排序

Collections.sort() 方法对 ArrayList 的元素或者任何其他 List 的实现提供的可比较的元素进行排序,这意味着这些元素的类需要实现 java.lang 包中的 Comparable 接口
一种是使用 Comparable 另一种是使用 Comparator。你最应该记住的就是一个 Comparable 对象可以说“我可以自己与另外一个对象比较”,而一个 Comparator 对象可以说“我可以比较两个不同的对象”

Zookeeper的使用场景

  • 数据发布与订阅(配置中心):发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据发布到ZK节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。例如全局的配置信息,服务式服务框架的服务地址列表等就非常适合使用。
  • 负载均衡 kafka:这里说的负载均衡是指软负载均衡。在分布式环境中,为了保证高可用性,通常同一个应用或同一个服务的提供方都会部署多份,达到对等服务。而消费者就须要在这些对等的服务器中选择一个来执行相关的业务逻辑,其中比较典型的是消息中间件中的生产者,消费者负载均衡。
  • 命名服务(naming service)dubbo:命名服务也是分布式系统中比较常见的一类场景。在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息。被命名的实体通常可以是集群中的机器,提供的服务地址,远程对象等等——这些我们都可以统称他们为名字(Name)。其中较为常见的就是一些分布式服务框架中的服务地址列表。通过调用ZK提供的创建节点的API,能够很容易创建一个全局唯一的path,这个path就可以作为一个名称。
    Dubbo中使用ZooKeeper来作为其命名服务,维护全局的服务地址列表
  • 集群管理与Master选举
  • 分布式锁
  • 分布式队列
  • 分布式通知/协调

spring的annotation实现

注解(Annotation)提供了一种安全的类似注释的机制,为我们在代码中添加信息提供了一种形式化得方法,使我们可以在稍后某个时刻方便的使用这些数据(通过解析注解来使用这些数据),用来将任何的信息或者元数据与程序元素(类、方法、成员变量等)进行关联。其实就是更加直观更加明了的说明,这些说明信息与程序业务逻辑没有关系,并且是供指定的工具或框架使用的。Annotation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的申明语句中。
Annotation其实是一种接口。通过java的反射机制相关的API来访问Annotation信息。相关类(框架或工具中的类)根据这些信息来决定如何使用该程序元素或改变它们的行为。Java语言解释器在工作时会忽略这些Annotation,因此在JVM中这些Annotation是“不起作用”的,只能通过配套的工具才能对这些Annotation类型的信息进行访问和处理。

spring的Bean的生命周期

  • 实例化BeanFactoryPostProcessor实现类
  • 执行BeanFactoryPostProcessor的postProcessBeanFactory方法
  • 实例化BeanPostProcessor实现类
  • 实例化InstantiationAwareBeanPostProcessorAdapter实现类
  • 执行InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation方法
  • 执行Bean构造器
  • 执行InstantiationAwareBeanPostProcessor的postProcessPropertyValues方法
  • 为Bean注入属性
  • 调用BeanNameAware的setBeanName方法
  • 调用BeanFactoryAware的setBeanFactory方法

内存溢出的原因是什么,排查方法有哪些

  • 是否App中的类中和引用变量过多使用了Static修饰 如public staitc Student s
  • 是否App中使用了大量的递归或无限递归(递归中用到了大量的建新的对象)
  • 是否App中使用了大量循环或死循环(循环中用到了大量的新建的对象)
  • 检查App中是否使用了向数据库查询所有记录的方法。即一次性全部查询的方法,如果数据量超过10万多条了,就可能会造成内存溢出。所以在查询时应采用“分页查询”。
  • 检查是否使用了“非字面量字符串进行+”的操作。因为String类的内容是不可变的,每次运行”+”就会产生新的对象,如果过多会造成新String对象过多,从而导致JVM没有及时回收而出现内存溢出。
  • 使用 DDMS工具进行查找内存溢出的大概位置
    排查方法:https://my.oschina.net/vbird/blog/1518938

jmap jstat jinfo jps jconsole mat jprofile

jmap:查看heap情况,如查看存活对象列表

  • jmap -heap pid → 查看堆的使用状况信息
  • jmap -histo:live pid | less → 堆中活动的对象以及大小
  • jmap -dump:format=b,file=eclipse_heap.bin pid → Dump堆信息

    jstat:观察GC情况

    可以查看很多内容 jstat -gcutil -h 20 pid 1000 100 → 查看Java进程GC的情况,1000ms统计一次gc情况统计100次

    jstack:分析线程堆栈

    查看jvm线程运行状态,是否有死锁现象等等信息
  • jstack pid → Dump线程信息

    jinfo,查看jvm配置信息

    jps

    jhat:分析dump的堆文件

    jconsole,图形界面。可以持续收集内存、线程信息,并以曲线的方式显示出来。

    这个工具真正要做的功能是查看JMX Bean的信息,性能分析中并不关心JMX信息。