面试总结

aop原理

ConfigurationClassPostProcessor.processConfigBeanDefinitions解析配置类
processImports解析import的类:AspectJAutoProxyRegistrar
this.reader.loadBeanDefinition(configClasses)
AspectJAutoProxyRegistrar.registerBeanDefinitions(类为AnnotationAwareAspectJAutoProxyCreator)
registerBeanPostProcessors

ioc

AutowiredAnnotationBeanPostProcessor

mvc原理

  1. 用户请求—>DispatcherServlet
  2. DispatcherServlet—>HandlerExecutionChain
  3. DispatcherServlet—>HandlerAdapter
  4. HandlerAdapter调用handle,根据适配的结果调用真正的处理器处理方法,完成功能处理,返回一个ModelAndView对象
  5. ViewResolver把逻辑视图名解析为具体的View
  6. View根据传进来的Model模型数据进行渲染
  7. 返回DispatcherServlet,由DispatcherServlet返回响应给用户

Spring有哪些设计模式

  • 工厂方法
  • 动态代理
  • 策略模式
  • 模板方法
  • 适配器
  • 装饰器
  • 观察者
  • 单例

redis与memcache的区别

数据类型

  • redis支持的数据结构更加丰富,包括stringlistsethashsorted setpub/subtransactions
  • memcache支持简单的数据类型,需要用户自己处理复杂对象

持久性

  • redis支持数据落地持久化存储,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用
  • memcache不支持数据持久存储

分布式存储

  • redis支持master-slave复制模式
  • memcache可以使用一致性hash做分布式

value大小不同

memcache是一个内存缓存,key的长度小于250字符,单个item存储要小于1M,不适合虚拟机使用

数据一致不同

redis只使用单核,而memcache可以使用多核,所以平均每一个核上redis在存储小数据时比memcache性能更高。而在100k以上的数据中,memcache性能要高于redis。

  • redis使用的是单线程模型,保证了数据按顺序提交。

  • memcache需要使用cas保证数据一致性。

cpu利用

redis单线程模型只能使用一个cpu,可以开启多个redis进程

redis哪些数据结构

  • string set key value get key
  • hash hset hget
  • list lpush rpush lrange lindex
  • set sadd smembers
  • sorted set zadd zrange

redis原理

网站性能调优的方法

elasticsearch选举

mybatis缓存

https://www.jianshu.com/p/c553169c5921

mybatis提供了一级缓存的方案来优化数据库会话间重复查询的问题。实现的方式是每个sqlSession中都持有了自己的缓存,一种是session级别,即在一个Mybatis会话中执行的所有语句,都会共享这一个缓存。一种是statement级别,可以理解为缓存只对当前执行的这一个statement有效。

Mybatis的一级缓存最大返回是sqlsession内部,有多个sqlsession或者分布式的环境下,有操作数据库写的话,会引起脏数据,建议把一级缓存的默认级别设定为statement,即不使用一级缓存

当开启二级缓存后,会使用CachingExecutor装饰Executor,在进入后续执行前,先在CachingExecurot进行二级缓存的查询。在二级缓存的使用中,一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存是被多个SqlSession共享着的,是一个全局的变量。

Mybatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用的条件比较苛刻。

在分布式环境下,由于默认的Mybatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将Mybatis的Cache接口实现,有一定的开发成本,不如直接用Redis, Memcache实现业务上的缓存就好了。

Spring事务传播行为 6种

事务 代码
如果有事务加入,没有则新建 REQUIRED
不管有没有事务,都新建 REQUIRED_NEW
不开启事务 NOT_SUPPORTED
必须在事务中执行,否则抛出异常 MANDATORY
必须不在事务中执行,否则抛出异常 NEVER
如果有了事务就加入,否则就不用事务 SUPPORTS

ACID

  • 原子性
  • 一致性
  • 隔离性
  • 持久性

隔离级别

  • read uncommit
  • read commit 存在幻读
  • read repeatable 间隙锁
  • serializable

Exception和Error

Throwable

  • Error

    程序无法处理的错误。大多数错误与代码编写者执行的操作无关,而表示代码运行时JVM出现的问题。

    • VirtualMachineError
    • AWTError
  • Exception

    程序本身可以处理的异常

    • RuntimeException

      运行时异常。编译器不要求你必须进行异常捕获处理或者抛出声明,由程序员自行决定

      • ArithmeticException
      • IndexOutOfBoundsException
      • ArrayIndexOutOfBoundsException
      • StringIndexOutOfBoundsException
      • ClassCaseException
      • NullPointerException
    • CheckedException

      非运行时异常。编译器强制必须进行捕获处理。

      • ClassNotFoundException
      • IllegalAccessException
      • IOException
      • FileNotFoundException
      • ProtocolException
      • SocketException
      • MalformedURLException

除了Spring另外的框架

注入HttpServletRequest时为什么是线程安全的

HttpServletRequest注入的是一个动态代理对象ObjectFactoryDelegatingInvocationHandler,当我们调用request的方法的时候其实是调用了method.invoke(this.objectFactory.getObject(), args);
objectFactory对象是RequestObjectFactorygetObject方法调用的是currentRequestAttributes().getRequest()
currentRequestAttributes()方法调用的是RequestContextHolder.currentRequestAttributes(),它从requestAttributesHolder中取得RequestAttributesRequestAttributes是一个ThreadLocal<RequestAttributes>

请求刚进入springmvc的dispatcherServlet的时候会把request相关的对象设置到RequestContextHolderthreadlocal中去
dispatcherServlet.processRequest -> initContextHolders -> setRequestAttributes

synchronized原理

monitorenter
monitorexit

synchronized与lock的比较

  1. synchronized情形下,如果占有锁的线程一直占有,则其他线程只能一直等待。lock可以trylock lockInterruptibly
  2. ReentrantReadWriteLock
  3. 可以通过Lock得知线程有没有获得锁,当前有多少线程再等待锁
  4. ReentrantLock可以控制锁的顺序,公平锁

集合

  • List

    • LinkedList
    • ArrayList
    • Vector
      • Stack
  • Map
    • HashTable
    • HashMap
    • TreeMap
    • ConcurrentHashMap
  • Set
    • HashSet
    • LinkedHashSet
    • TreeSet

设计模式六大原则

  • 单一职责原则(SRP)

    一个接口或类只有一个原因引起变化,也就是一个接口或类只有一个职责,它就负责一件事情。单一职责适用于接口、类、同时也适用于方法。一个方法尽可能做一件事情,比如一个方法修改用户密码,不要把这个方法放到”修改用户信息”方法中,这个方法粒度很粗。

  • 里氏替换(LSP)

    只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是反过来就不行,有子类出现的地方,父类未必就能适应。

    1. 子类必须完全实现父类的方法。
      在类中调用其他类时务必要使用父类或接口,如果不能使用父类或接口,则说明类的设计已经违背了LSP原则。
      如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子基础关系,采用依赖、聚集、组合等关系代替继承。
    2. 子类可以有自己的个性
    3. 覆盖或实现父类的方法时输入参数可以放大
      子类中方法的前置条件必须与超类中被覆写的方法的前置条件相同或者更宽松
    4. 覆写或实现父类的方法时输出结果可以被缩小
      如果是覆写,父类和子类的同名方法的输入参数是相同的,两个方法的范围值S小于等于T,这是覆写的要求。如果是重载,则要求方法的输入参数类型或数量不相同,在里式替换原则要求下,就是子类的输入参数宽于或等于父类的输入参数,也就是说你写的这个方法是不会被调用的。
  • 依赖倒置

    模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系时通过接口或抽象类产生的
    接口或抽象类不依赖于实现类
    实现类依赖接口或抽象类

    实践
    每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备
    变量的表面类型尽量是接口或者是抽象类
    任何类都不应该从具体的类派生
    尽量不要覆写基类的方法
    结合里氏替换原则使用

    接口负责定义public属性和方法,并且声明与其他对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确地实现业务逻辑,同时在适当的时候对父类进行细化。
    
  • 接口隔离原则

    接口尽量细化,同时接口中的方法尽量少
    单一职责要求的是类和接口职责单一,注重的是职责,这是业务逻辑上的划分,而接口隔离原则要求接口的方法尽量少

  • 迪米特法则

    最少知识原则。一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少。
    只和朋友交流。朋友类的定义是这样的:出现在成员变量、方法的输入输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类。
    朋友间也是有距离的。一个类公开的public属性或方法越多,修改时涉及的面也就越大,变更引起的风险扩散也就越大。在设计时需要反复衡量:是否可以再减少public方法和属性,是否可以修改为private、package-private、protected等访问权限,是否可以加上final关键字等。
    是自己的就是自己的。如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,就放置在本类中。

  • 开闭原则

    对扩展开发,对修改关闭。
    抽象约束
    元数据控制模块行为
    制定项目章程
    封装变化

单一职责:类或接口只有一个职责
里式替换:父类能出现的地方子类也能出现,反之不行
依赖倒置:依赖抽象,不依赖实现
接口隔离:接口尽量细化,方法尽量少
迪米特:最少知识
开闭:对修改关闭,对扩展开放

SOLLID
单一职责 single responsibility principle
开闭 open closed priciple
里式替换 liskov substitution principle
迪米特 law of demeter
接口隔离 interface segregation principle
依赖倒置 dependence inversion principle

JVM方法调用

方法调用阶段的唯一任务就是确定被调用方法的版本。Class文件在编译过程中不包含传统编译中的连接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址(相当于之前说的直接引用)。
解析

所有方法调用中的目标方法在Class文件里面都是一个常量池中的符号引用,在类加载的解析阶段,会将其中的一部分引用转化为直接引用。调用目标在程序代码写好、编译器进行编译时就必须确定下来。这类方法的调用称为解析。

invokestatic:调用静态方法
invokespecial:调用实例构造器方法、私有方法和父类方法
invokevirtual:调用所有的虚方法
invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象
invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法。在此之前的4条调用指令,分派逻辑是固化在Java虚拟机内部的,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。

只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本,符合这个条件的静态方法、私有方法、实例构造器、父类方法4类,它们在类加载的时候就会把符号引用解析为该方法的直接引用。这些方法可以称为非虚方法,于智相反,其他方法称为虚方法(除去final方法,后文会提到)。

分派

静态分派

Human man = new Man()
Human称为变量的静态类型(Static Type),或者叫做外观类型(Apparent Type),后面的”Man”称为变量的实际类型(Actual Type),静态类型和实际类型在程序中都可以发生一些变化,区别是静态类型的变化仅仅在使用时发生,变量本身的静态类型不会被改变,并且最终的静态类型是在编译期可知的;而实际类型变化的结果在运行期才可确定,编译器在编译程序的时候并不知道一个对象的实际类型是什么。
编译器在重载时通过参数的静态类型而不是实际类型作为判定依据的。并且静态类型是编译期可知的,因此,在编译阶段,javac编译器会根据参数的静态类型决定使用哪个重载版本。
所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。静态分派的典型应用是方法重载。

动态分派

invokevirtual指令运行时解析过程

  1. 找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C
  2. 如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回java.lang.IllegalAccessError异常。
  3. 否则,按照继承关系从下往上依次对C的父类进行第2步的搜索和验证过程
  4. 如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常

JVM内存

  • 程序计数器
  • Java虚拟机栈
  • 本地方法栈
  • Java堆
  • 方法区

    线程共享,用于存储已被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码

  • 运行时常量池

  • 直接内存

重载(override)重写(overload)规则

http://www.runoob.com/java/java-override-overload.html

重写

  1. 参数列表必须完全与被重写方法相同
  2. 返回类型必须完全与被重写方法的返回类型相同
  3. 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected
  4. 父类的成员方法只能被它的子类重写
  5. 声明为final的方法不能被重写
  6. 声明为static的方法不能被重写,但是能够被再次声明
  7. 子类和父类在同一个包中,那么子类可以重写父类的所有方法,除了声明为private和final的方法
  8. 子类和父类不在同一个包中,那么子类只能够重写父类声明为public和protected的非final方法
  9. 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以
  10. 构造方法不能被重写
  11. 如果不能继承一个方法,则不能重写这个方法

重载

被重载的方法必须改变参数列表(参数个数或类型或顺序不一样)
被重载的方法可以改变返回类型
被重载的方法可以改变访问修饰符
被重载的方法可以声明新的或更广的检查异常
方法能够在同一个类中或者在一个子类中被重载
无法以返回值类型作为重载函数的区分标准

区别点 重载 重写
参数列表 不一样 一样
返回类型 可以不一样 一样
异常 可以不一样 可以缩小
访问权限 可以不一样 可以放大

GC过程

https://www.toutiao.com/i6535602088414544397/

新生代,eden space, from space, to space

  1. 绝大多数刚创建的对象会被分配在eden区,其中的大多数对象会很快消亡。Eden区是连续的内存空间,因此在其上分配内存极快。(新生代垃圾回收Minor GC,采用copy算法)
  2. 最初一次,当eden区满的时候,执行Minor GC,将消亡的对象清理掉,并将剩余的对象复制到一个存活区from space。(此时,to space是空白的,两个survivor总有一个是空白的)
  3. 下次eden space满了,再执行Minor GC,将消亡的对象清理掉,将存活的对象复制到to space中,然后清空eden space同时将from space中消亡的对象清理掉,将存活的对象也复制到to space,然后清空from space。交换to和from的角色,保证to space是空的。Minor GC会一直重复这样的过程,直到to space被填满,to space被填满后,会将所有对象移动到老年代中。
  4. 如果没有填满,当两个存活区切换了几次(每进行一次Minor GC,都会在存活的对象做一个标记,加1,当标记的值大于15)之后,仍然存活的对象将被复制到老年代中。

老年代

在新生代经历了N次垃圾回收后仍然存活的对象,就会被放到老年代,该区域中对象存活率高。老年代的垃圾回收(又称Major GC)通常使用“标记-清理”或“标记-整理”算法。整堆包括新生代和老年代的垃圾回收称为Full GC(HotSpot VM里,除了CMS之外,其他能收集老年代的GC都会同时收集整个GC堆,包括新生代)。

当老年代的空间不足时,会触发Major GC/Full GC,速度一般比Minor GC慢10倍以上

持久代

在JDK8之前的HotSpot实现中,类的元数据如方法数据、方法信息(字节码,栈和变量大小)、运行时常量池、已确定的符号引用和虚方法表等被保存在永久代中。

在JDK8的HotSpot中,把永久代从Java堆中移除了,并把类的元数据直接保存在本地内存区域(堆外空间),称为元空间。

元空间并不在虚拟机中,而是使用本地内存。类的元数据放入native memory,字符串池和类的静态变量放入java堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来控制。

虚拟机参数

-Xms 堆初始大小
-Xmx 堆最大值
-Xss 每个线程的栈大小
-XX:SurvivorRatio 年轻代中Eden区与两个Survivor区的比值
-XX:+PrintGCDetails 输出GC日志详情信息
-XX:+PrintGCApplicationStoppedTime 输出垃圾回收期间程序暂停的时间
-Xloggc: 把相关日志信息记录到文件以便分析
-XX:+HeapDumpOnOutOfMemoryError 发生内存溢出时生成heapdump文件
-XX:HeapDumpPath= heapdump文件地址

JDK命令行工具

jps
jstat
jinfo

jmap
jhat
jstack

String, StringBuffer, StringBuilder的区别

String 字符串常量
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(非线程安全)

binlog_format

  • statement,基于SQL语句的复制

    不需要记录每一行数据的变化,减少binlog日志量,节约IO,提高性能。因为它只需要记录在master上所执行的语句细节,以及执行语句时候的上下文信息。

  • row,基于行的复制

    它不记录sql语句的上下文相关信息,仅需要记录那条记录被修改成什么了。
    所有的执行语句当记录到日志中的时候,都将以每行记录的修改来记录,这样可能会产生大量的日志内容。

    新版本的MySQL中对row level模式也被做了优化,并不是所有的修改都会以row level来记录,像遇到表结构变更的时候就会以statement模式来记录,如果sql语句确实就是update或者delete等修改数据的语句,那么还是会记录所有行的变更

  • mixed,混合模式复制

    实际上就是statement与row的结合

    一般的语句修改使用statement格式保存binlog,如一些函数,statement无法完成主从复制的操作,则采用row格式保存binlog,MySQL会根据执行的每一条具体的sql语句来区分对待记录的日志形式,也就是在statement和row之间选择一种。

mysql缓存

https://www.jianshu.com/p/5c3ddf9a454c

  • 设置使用查询缓存的方式,query_cache_type

    • ON:正常缓存。表示在使用SELECT语句查询时,若没指定SQL_NO_CACHE或其他非确定性函数,则一般都会将查询结果缓存下来
    • DEMAND:指定SQL_CACHE才缓存。表示在使用SELECT语句查询时,必须在该SELECT语句中指定SQL_CACHE才会将该SELECT语句的查询结果缓存下来
    • OFF:关闭查询缓存
  • 设置查询缓存的大小

    query_cache_size:查询缓存的总体可用空间

  • 查询缓存相关参数

    1
    show global variables like 'query_cache%'
    • query_cache_limit:MySQL能够缓存的最大查询结果;如果某查询结果大小大于此值,则不会被缓存
    • query_cache_min_res_unit:查询缓存中分配内存的最小单位(注意:此值通常需要调整,此值被调整为接近所有查询结果的平均值是最好的)

      计算单个查询的平均缓存大小:(query_cache_size - Qcache_free_memory) / Qcache_queries_in_cache

    • query_cache_size:查询缓存的总体可用空间,单位为字节;其必须为1024的倍数

    • query_cache_type:查询缓存类型;是否开启缓存功能,开启方式有三种(on|off|demand)
    • query_cache_wlock_invalidate:当其他会话锁定此次查询用到的资源时,是否不能再从缓存中返回数据;OFF表示可以从缓存中返回数据
  • 衡量缓存是否有效

    1
    show global status where variable_name = 'Qcache_hits' or variable_name = 'Com_select'
    • Qcache_hits:缓存命中次数
    • Com_select:非缓存查询次数

      缓存命中率:Qcache_hits / (Qcache_hits + Com_select)

      1
      show global status where variable_name = 'Qcache_hits' or variable_name = 'Qcache_inserts'
- Qcache_hits:缓存命中次数
- Qcache_inserts:向查询缓存中添加缓存记录的条数

“命中和写入”的比率:`Qcache_hits / Qcache_inserts`    
如果此比值大于3:1,说明缓存也是有效的;如果高于10:1,相当理想

kafka乱序

SkipedList原理

redis锁

https://www.cnblogs.com/linjiqin/p/8003838.html

加锁

1
jedis.set(final String key, final String value, final String nxxx, final String expx, final long time)
  • key:使用key来当锁,因为key是唯一的
  • value:传入的requestId。通过给value赋值requestId,我们就知道这把锁是哪个请求加的,在解锁的时候就可以有依据。requestId可以使用UUID.randomUUID().toString()来生成
  • nxxx:这个参数传的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作(如果是XX,表示只有key已经存在时进行set操作)
  • expx:过期时间的单位(EX = seconds, PX = milliseconds)
  • time:过期时间

解锁

1
2
String script = “if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘del’, KEYS[1]) else return 0 end”;
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

我们将Lua代码传到jedis.eval()方法里,并使参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。在eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,redis才会执行其他命令。

单例三种实现

饿汉式

1
2
3
4
5
6
7
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}

有可能会在其他的代理里被调用然后被初始化

懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Public  class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized(Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}

一定会在getInstance()方法中被初始化

内部静态类

1
2
3
4
5
6
7
8
9
Public class Singleton {
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}

由jvm来控制同步

线程池

ThreadPoolExecutor

  • corePoolSize:池中所保存的线程数,包括空间线程
  • maximumPoolSize:池中允许的最大线程数
  • keepAliveTime:当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间
  • unit:keepAliveTime参数的时间单位
  • workQueue:执行前用于保持任务的队列。此队列仅保持由execute方法提交的Runnable任务
  • threadFactory:执行程序创建新线程时使用的工厂
  • handelr:由于超出线程范围和队列容量而使执行被阻塞时所使用的处理任务(拒绝策略有4中)
    • CallerRunsPolicy:线程调用运行该任务的execute本身。
    • AbortPolicy:处理程序遭到拒绝将抛出运行时RejectedExecutionException
    • DiscardPolicy:直接丢弃任务,不抛出异常
    • DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果失败,则重复此过程)

如果BlockingQueue是无界的,那么永远不会触发maximumPoolSize,自然keepAliveTime也就没有了意义。

execute(Runnable command)

如果worker数量小于corePoolSize:调用addWorker增加新的Worker。
如果可以插入到workQueue中:如果不在运行状态则拒绝;否则,如果worker的数量等于0则添加一个worker
如果workQueue已经满了:调用addWorker尝试是否可以在corePoolSize之外另外增加一个worker,如果worker数量已经达到max_pool_size,则拒绝

addWorker(Runnable firstTask, boolean core)

判断worker数量,如果超过了corePoolSize或者maximumPoolSize,则直接返回失败。否则增加一个worker数量,跳出循环

新建一个Worker,将其添加到workers中。启动Worker。

runWorker(Worker w)

循环调用getTask方法获取任务,getTask方法从阻塞队列中获取任务,调用task.run执行任务
如果task为null则跳出循环,执行processWorkerExit方法

getTask()

根据是否需要超时控制(allowCoreThreadTimeOut || wc > corePoolSize),调用workQueue.poll或者workQueue.take从工作队列中获取任务并返回

BlockingQueue

操作 抛出异常 特殊值 阻塞 超时
插入 add(E e) offer(E e) put(E e) offer(E e, long timeout, TimeUnit unit)
移除头部元素 remove() poll() take() poll(long timeout, TimeUnit unit)
获取头部元素(不删除) element() peek()

GET与POST的区别

https://sunshinevvv.coding.me/blog/2017/02/09/HttpGETv.s.POST/

RFC7231里定义了HTTP方法的几个特性:

  1. Safe - 安全

    如果一个方法的语义在本质上是“只读”的,那么这个方法就是安全的。客户端向服务端的资源发起的请求如果使用了是安全的方法,就不应该引起服务端任何的状态变化,因此也是无害的。此RFC定义,GET,HEAD,OPTIONS,TRACE这几个方法是安全的。

  2. Idempotent - 幂等

    幂等的概念是指同一个请求方法执行多次和仅执行一次的效果完全相同。按照RFC规范,PUT,DELETE和安全方法都是幂等的。

  3. Cacheable - 可缓存性

    顾名思义就是一个方法是否可以被缓存,此RFC里GET,HEAD和某些情况下的POST都是可缓存的,当时绝大多数的浏览器的实现里仅仅支持GET和HEAD

  • GET的语义是请求获取指定的资源。GET方法是安全的、幂等、可缓存的(除非有Cache-ControlHeader的约束),GET的报文主体没有任何语义
  • POST的语义是根据请求负荷(报文主体)对指定的资源做出处理,具体的处理方式视资源类型而不同。POST不安全,不幂等,(大部分实现)不可缓存。

TCP三次握手和四次挥手

https://www.jianshu.com/p/bbb6261cb13e
http://blog.csdn.net/qzcsu/article/details/72861891

三次握手

  1. A向B发起建立连接请求:A -> B
  2. B收到A的发送信息,并且向A发送确认信号:B -> A
  3. A收到B的确认信号,并向B发送确认信号:A -> B

通过第一次握手,B知道A能够发送数据。通过第二次握手,A知道B能发送数据。结合第一次握手和第二次握手,A知道B能接受数据。结合第三次握手,B知道A能够接收数据。

四次握手

  1. A向B发起请求,表示A没有数据要发送了:A -> B
  2. B向A发送信号,确认A的断开请求:B -> A
  3. B向A发送信号,请求断开连接,表示B没有数据要发送了:B -> A
  4. A向B发送确认信号,同意断开:A -> B

B收到确认信号,断开连接,而A在一段时间内没收到B的信号,表面B已经断开了,于是A也断开了连接。至此完成挥手过程。

挥手次数比握手多一次,是因为握手过程,通信只需要处理连接。而挥手过程,通信需要处理数据+连接

常用熟知端口

应用程序 ftp tftp telnet smtp dns http ssh mysql
熟知端口 21, 20 69 23 25 53 80 22 3306
传输层协议 TCP UDP TCP TCP UDP TCP

TCP与UDP的区别

  1. TCP面向连接(3次握手);UDP是无连接的,即发送数据之前不需要建立连接
  2. TCP提供可靠服务,无差错、不丢失、不重复、按序到达;UDP仅最大努力交付,即不保证可靠交付
  3. TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的。UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低
  4. 每一条TCP连接只能是点到点的;UDP支持一对一、一对多、多对一、多对多的交互通信
  5. TCP首部开销20字节;UDP首部开销小,只有8字节
  6. TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

引用计数法与GC Root可达性分析法区别

引用计数法

给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。

很难解决对象之间相互循环引用的问题

可达性分析算法

通过一系列的称为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

可作为GC Roots的对象包括下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象

引用4种

  • 强引用:只用强引用还存在,垃圾收集器永远不会回收掉被引用的对象
  • 软引用:在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常
  • 弱引用:被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
  • 虚引用:一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

双亲委派机制

3种系统提供的类加载器

  • 启动类加载器(Bootstrap ClassLoader)

    这个类加载器负责将存放在<JAVA_HOME>/lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。

  • 扩展类加载器(Extension ClassLoader)

    这个加载器由sum.misc.Launcher$ExtClassLoader实现,它负载加载<JAVA_HOME>/lib/ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器

  • 应用程序类加载器(Application ClassLoader)

    这个类加载器由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统加载器。它负载加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序默认的类加载器。

双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码。

双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

如果编写一个域rt.jar类库中已有类重名的Java类,将会发现可以正常编译,但永远无法被加载运行。

类加载机制

类加载的时机

虚拟机规范严格规定了有且只有5中情况必须立即对类进行”初始化”(而加载、验证、准备自然需要在此之前开始):

  1. 遇到new, getstatic, putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:

    • 使用new关键字实例化对象的时候
    • 读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候
    • 调用一个类的静态方法的时候
  2. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。

  3. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类
  5. 当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStaticREF_putStaticREF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

通过子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。至于是否要触发子类的加载和验证,在虚拟机规范中并未明确规定,这点取决于虚拟机的具体实现。对于Sun HotSpot虚拟机来说,可通过-XX:+TraceClassLoading参数观察到此操作会导致子类的加载。(结论是,子类会加载)

类加载过程

  1. 加载

    • 通过一个类的全限定名来获取定义此类的二进制字节流
    • 将这个字节流所代表的静态存储存储结构转化为方法区的运行时数据结构(方法区中的数据存储格式由虚拟机实现自行定义,虚拟机规范未规定此区域的具体数据结构)
    • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。(并没有明确规定是在Java堆中,对于HotSpot虚拟机而言,Class对象比较特殊,它虽然是对象,但是存放在方法区里面)。

      数组类本身不通过类加载器创建,它是由Java虚拟机直接创建的。但数组类与类加载器仍然有很密切的关系,因为数组类的元素类型(Element Type,指的是数组去掉所有维度的类型)最终要靠类加载器去创建,一个数组类(下面简称为C)创建过程遵循以下规则:

    • 如果数组的组件类型(Component Type,指的是数组去掉一个维度的类型)是引用类型,那就递归采用本节中定义的加载过程去加载这个组件类型,数组C将在加载该组件类型的类加载器的类名称空间上被标识

    • 如果数组的组件类型不是引用类型(例如int[]数组),Java虚拟机将会把数组C标记为与引导类加载器关联
    • 数组类的可见性与它的组件类型的可见性一致,如果组件类型不是引用类型,那数组类的可见性将默认为public
  2. 验证

    确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

  3. 准备

    准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这个阶段中有两个容易产生混淆的概念需要强调一下,首先,这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。其次,这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量的定义为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static int value = 123;
    ```

    那变量value在准备阶段过后的初始值为0而不是123,因为这时候尚未开始执行任何Java方法,而把value赋值为123的putstaitc指令是程序被编译后,存放于类构造器`<clinit>()`方法之中,所以把value赋值为123的动作将在初始化阶段才会执行。

    如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量value就会被初始化为ConstantValue属性所指定的值,假设上面类变量value定义变为:

    ```java
    public static final int value = 123;
编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123。
  1. 解析

    解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

    • 符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中
    • 直接引用(Direct References):直接引用可以直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在内存中存在。
  2. 初始化

    初始化阶段是执行类构造器<clinit>()方法的过程。

    • <clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句可以赋值,但是不能访问。
    • <clinit>()方法与类的构造函数(或者说实例构造器<init>())方法不同,它不需要显式地调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕。因此在虚拟机中第一个被执行的<clinit>()方法的类肯定是java.lang.Object
    • 由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作
    • <clinit>()方法对于类或接口来说并不是必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生产<clinit>()方法。
    • 接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成<clinit>()方法。但接口与类不同的是,执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法
    • 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。如果在一个类的<clinit>()方法中有耗时很长的操作,就可能造成多个进程阻塞。需要注意的是,其他线程虽然会被阻塞,但如果执行<clinit>()方法的那条线程退出<clinit>()方法后,其他线程唤醒之后不会再次进入<clinit>()方法。同一个类加载器下,一个类型只会初始化一次。

设计模式分类

范围 创建型 结构型 行为型
对象创建 Singleton(单例)
Prototype(原型)
Factory Method(工厂方法)
AbstractFactory(抽象工厂)
Builder(建造者)
接口适配 Adapter(适配器)
Bridge(桥接)
Facade(外观)
对象去耦 Mediator(中介者)
Observer(观察者)
抽象集合 Composite(组合) Iterator(迭代器)
行为扩展 Decorator(装饰) Visitor(访问者)
Chain of Responsibility(职责链)
算法封装 Template Method(模板方法)
Strategy(策略)
Command(命令模式)
性能与对象访问 Flyweight(享元)
Proxy(代理)
对象状态 Memento(备忘录)
State(状态)
其他 Interpreter(解释器)

Object类中的方法

  • getClass()
  • hashCode()
  • equals(Object obj)
  • clone()
  • toString()
  • notify()
  • notifyAll()
  • wait(long timeout)
  • wait(long timeout, int nanos)
  • wait()
  • finalize()

equals与hashCode

  • 若重写了equals(Object obj)方法,则有必要重写hashCode()方法
  • 若两个对象equals(Object obj)返回true,则hashCode()有必要也返回相同的int数
  • 若两个对象equals(Object obj)返回false,则hashCode()不一定返回不同的int数
  • 若两个对象hashCode()返回相同的int数,则equals(Object obj)不一定返回true
  • 若两个对象hashCode()返回不同的int数,则equals(Object obj)一定返回false
  • 同一个对象在执行期间若已经存储在集合中,则不能修改影响hashCode值的相关信息,否则会导致内存泄露问题

equals要满足一下规则:

  • 自反性:x.equals(x)一定是true
  • 对null:x.equals(null)一定是false
  • 对称性:x.equals(y)和y.equals(x)结果一致
  • 传递性:a和b equals,b和c equals,则a和c也一定equals
  • 一致性:在某个运行期间,2个对象状态的改变不会影响equals的决策结果,那么,在这个运行期间,无论调用多少equals,都返回相同的结果

中断与synchronized

http://www.cnblogs.com/duanxz/p/4494420.html

常见排序算法

稳定:

  • 基数排序
  • 冒泡排序
  • 插入排序
  • 归并排序
    不稳定
  • 堆排序
  • 快速排序
  • 希尔排序
  • 选择排序

http://ginobefunny.com/post/java_other_interview_questions/