java面试题(上)

1:  自我介绍
自我介绍首先描述自己的基本情况,其次是描述自己的技术亮点,做过的亮点项目或产品。如果没有做过有技术亮点的事,每天都在做增删改查功能或重复性的工作,需要好好反思下,这样下去技术上没有多少增长。如果工作中就是做这个怎么办?可以考虑利用业余时间参与开源项目或自己做一些工具或框架。
    2:  HashMap怎么解决Hash冲突的
HashMap的数据结构是:数组Node[]与链表Node中有next Node.
在put元素时,先根据key的hashCode重新计算出hash值,然后再根据hash值找出这个元素在数组中的位置,如果数组的此处已经存放了数据,那么这个元素会议链表的形式保存,新的在链头,旧的在链尾;如果数组的此处没有存放数据,则直接将该元素保存在数组的此位置上。
    3:  ConcurrentHashMap怎么解决线程安全
Segment分段锁功能,每一个Segment 都想的于小的hash table并且都有自己锁,只要修改不再同一个段上就不会引起并发问题。
 
    4:  常见的排序有没有了解过
排序方法        平均情况        最好情况        最坏情况        辅助空间        稳定性
冒泡排序         O(n^2)           O(n)              O(n^2)            O(1)                稳定
选择排序         O(n^2)          O(n^2)            O(n^2)            O(1)              不稳定
插入排序         O(n^2)           O(n)              O(n^2)            O(1)                稳定
希尔排序O(n*log(n))~O(n^2) O(n^1.3)       O(n^2)            O(1)              不稳定
堆排序          O(n*log(n))     O(n*log(n))    O(n*log(n))       O(1)              不稳定
归并排序       O(n*log(n))     O(n*log(n))    O(n*log(n))       O(n)                稳定
快速排序       O(n*log(n))     O(n*log(n))      O(n^2)            O(1)              不稳定
 
冒泡排序经过优化以后,最好时间复杂度可以达到O(n)。设置一个标志位,如果有一趟比较中没有发生任何交换,可提前结束,因此在正序情况下,时间复杂度为O(n)。
选择排序在最坏和最好情况下,都必须在剩余的序列中选择最小(大)的数,与已排好序的序列后一个位置元素做交换,依次最好和最坏时间复杂度均为O(n^2)。
插入排序是在把已排好序的序列的后一个元素插入到前面已排好序(需要选择合适的位置)的序列中,在正序情况下时间复杂度为O(n)。
堆是完全二叉树,因此树的深度一定是log(n)+1,最好和最坏时间复杂度均为O(n*log(n))。
归并排序是将大数组分为两个小数组,依次递归,相当于二叉树,深度为log(n)+1,因此最好和最坏时间复杂度都是O(n*log(n))。
快速排序在正序或逆序情况下,每次划分只得到比上一次划分少一个记录的子序列,用递归树画出来,是一棵斜树,此时需要n-1次递归,且第i次划分要经过n-i次关键字比较才能找到第i个记录,因此时间复杂度是\sum_{i=1}^{n-1}(n-i)=n(n-1)/2,即O(n^2)。
    5:  一堆基本有序的数组,用哪种排序效率最高
归并排序
 
    6:  JDK1.6到JDK1.8 GC上面最大做了什么变化
在JDK8之前的HotSpot实现中,类的元数据如方法数据、方法信息(字节码,栈和变量大小)、运行时常量池、已确定的符号引用和虚方法表等被保存在永久代中,32位默认永久代的大小为64M,64位默认为85M,可以通过参数-XX:MaxPermSize进行设置,一旦类的元数据超过了永久代大小,就会抛出OOM异常。
        虚拟机团队在JDK8的HotSpot中,把永久代从Java堆中移除了,并把类的元数据直接保存在本地内存区域(堆外内存),称之为元空间。
 
GC动作发生之前,需要确定堆内存中哪些对象是存活的,一般有两种方法:引用计数法和可达性分析法。
 
1、引用计数法
 
        在对象上添加一个引用计数器,每当有一个对象引用它时,计数器加1,当使用完该对象时,计数器减1,计数器值为0的对象表示不可能再被使用。
引用计数法实现简单,判定高效,但不能解决对象之间相互引用的问题。
 
2、可达性分析法
 
        通过一系列称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,搜索路径称为 “引用链”,以下对象可作为GC Roots:
        本地变量表中引用的对象
        方法区中静态变量引用的对象
        方法区中常量引用的对象
        Native方法引用的对象
收集算法
 
垃圾收集算法主要有:标记-清除、复制和标记-整理。
 
 
 
1、标记-清除算法
 
对待回收的对象进行标记。
算法缺点:效率问题,标记和清除过程效率都很低;空间问题,收集之后会产生大量的内存碎片,不利于大对象的分配。
 
2、复制算法
 
复制算法将可用内存划分成大小相等的两块A和B,每次只使用其中一块,当A的内存用完了,就把存活的对象复制到B,并清空A的内存,不仅提高了标记的效率,因为只需要标记存活的对象,同时也避免了内存碎片的问题,代价是可用内存缩小为原来的一半。
 
3、标记-整理算法
 
在老年代中,对象存活率较高,复制算法的效率很低。在标记-整理算法中,标记出所有存活的对象,并移动到一端,然后直接清理边界以外的内存。
 
 
对象标记过程
 
在可达性分析过程中,为了准确找出与GC Roots相关联的对象,必须要求整个执行引擎看起来像是被冻结在某个时间点上,即暂停所有运行中的线程,不可以出现对象的引用关系还在不断变化的情况。
 
如何快速枚举GC Roots?
 
GC Roots主要在全局性的引用(常量或类静态属性)与执行上下文(本地变量表中的引用)中,很多应用仅仅方法区就上百兆,如果进行遍历查找,效率会非常低下。
 
    7:  CMS怎么进行垃圾收集的
 
CMS(Concurrent Mark Sweep)是一种以获取最短回收停顿时间为目标的收集器,工作在老年代,基于“标记-清除”算法实现,整个过程分为以下4步:
 
  1. 初始标记:这个过程只是标记以下GC Roots能够直接关联的对象,但是仍然会Stop The World;
  2. 并发标记:进行GC Roots Tracing的过程,可以和用户线程一起工作。
  3. 重新标记:用于修正并发标记期间由于用户程序继续运行而导致标记产生变动的那部分记录,这个过程会暂停所有线程,但其停顿时间远比并发标记的时间短;
  4. 并发清理:可以和用户线程一起工作。
 
CMS收集器的缺点:
 
  1. 对CPU资源比较敏感,在并发阶段,虽然不会导致用户线程停顿,但是会占用一部分线程资源,降低系统的总吞吐量。
  2. 无法处理浮动垃圾,在并发清理阶段,用户线程的运行依然会产生新的垃圾对象,这部分垃圾只能在下一次GC时收集。
  3. CMS是基于标记-清除算法实现的,意味着收集结束后会造成大量的内存碎片,可能导致出现老年代剩余空间很大,却无法找到足够大的连续空间分配当前对象,不得不提前触发一次Full GC。
 
JDK1.5实现中,当老年代空间使用率达到68%时,就会触发CMS收集器,如果应用中老年代增长不是太快,可以通过-XX:CMSInitiatingOccupancyFraction参数提高触发百分比,从而降低内存回收次数提高系统性能。
 
JDK1.6实现中,触发CMS收集器的阈值已经提升到92%,要是CMS运行期间预留的内存无法满足用户线程需要,会出现一次”Concurrent Mode Failure”失败,这是虚拟机会启动Serial Old收集器对老年代进行垃圾收集,当然,这样应用的停顿时间就更长了,所以这个阈值也不能设置的太高,如果导致了”Concurrent Mode Failure”失败,反而会降低性能,至于如何设置这个阈值,还得长时间的对老年代空间的使用情况进行监控。
 
 
    8:  G1怎么进行垃圾收集的
G1(Garbage First)是JDK1.7提供的一个工作在新生代和老年代的收集器,基于“标记-整理”算法实现,在收集结束后可以避免内存碎片问题。
 
G1优点:
 
  1. 并行与并发:充分利用多CPU来缩短Stop The World的停顿时间;
  2. 分代收集:不需要其他收集配合就可以管理整个Java堆,采用不同的方式处理新建的对象、已经存活一段时间和经历过多次GC的对象获取更好的收集效果;
  3. 空间整合:与CMS的”标记-清除”算法不同,G1在运行期间不会产生内存空间碎片,有利于应用的长时间运行,且分配大对象时,不会导致由于无法申请到足够大的连续内存而提前触发一次Full GC;
  4. 停顿预测:G1中可以建立可预测的停顿时间模型,能让使用者明确指定在M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
 
使用G1收集器时,Java堆的内存布局与其他收集器有很大区别,整个Java堆会被划分为多个大小相等的独立区域Region,新生代和老年代不再是物理隔离了,都是一部分Region(不需要连续)的集合。G1会跟踪各个Region的垃圾收集情况(回收空间大小和回收消耗的时间),维护一个优先列表,根据允许的收集时间,优先回收价值最大的Region,避免在整个Java堆上进行全区域的垃圾回收,确保了G1收集器可以在有限的时间内尽可能收集更多的垃圾。
 
不过问题来了:使用G1收集器,一个对象分配在某个Region中,可以和Java堆上任意的对象有引用关系,那么如何判定一个对象是否存活,是否需要扫描整个Java堆?其实这个问题在之前收集器中也存在,如果回收新生代的对象时,不得不同时扫描老年代的话,会大大降低Minor GC的效率。
 
针对这种情况,虚拟机提供了一个解决方案:G1收集器中Region之间的对象引用关系和其他收集器中新生代与老年代之间的对象引用关系被保存在Remenbered Set数据结构中,用来避免全堆扫描。G1中每个Region都有一个对应的Remenbered Set,当虚拟机发现程序对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于相同的Region中,如果不是,则通过CardTable把相关引用信息记录到被引用对象所属Region的Remenbered Set中。
 
    9:  G1相比于CMS有哪些优势
与CMS的”标记-清除”算法不同,G1在运行期间不会产生内存空间碎片,有利于应用的长时间运行,且分配大对象时,不会导致由于无法申请到足够大的连续内存而提前触发一次Full GC;
与CMS的“标记--清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。
    10: 哪些情况会导致Full GC
1、System.gc()方法的调用
2、老年代代空间不足
3、永生区空间不足
4、CMS GC时出现promotion failed和concurrent mode failure
5、统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间
6、堆中分配很大的对象
 
    11: 新new的对象放在哪里
->堆内存是用来存放由new创建的对象和数组,即动态申请的内存都存放在堆内存
-->栈内存是用来存放在函数中定义的一些基本类型的变量和对象的引用变量
 
    12: 哪些东西放在栈区
1.寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制;
1. 栈:存放基本类型 的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new出来的对象)或者常量池中(字符串常量对象存放的常量池中),局部变量【注意:(方法中的局部变量使用final修饰后,放在堆中,而不是栈中)】
2.堆:存放使用new创建的对象,全局变量
3. 静态域:存放静态成员(static定义的);
4. 常量池:字符串常量和基本类型常量(public static final)。有时,在嵌入式系统中,常量本身会和其他部分分割离开(由于版权等其他原因),所以在这种情况下,可以选择将其放在ROM中 ;
5. 非RAM存储:硬盘等永久存储空间
 
    13: 双亲委派模型, 有什么好处
亲委派模型要求除顶层启动类加载器外其余类加载器都应该有自己的父类加载器;类加载器之间通过复用关系来复用父加载器的代码。
双亲委派模型的优点:
Java类伴随其类加载器具备了带有优先级的层次关系,确保了在各种加载环境的加载顺序。  
保证了运行的安全性,防止不可信类扮演可信任的类。
    14: wait和sleep有什么区别
 
1、这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类。
sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。
 
2、最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。
Thread.Sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”。
 
3、使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用 
   synchronized(x){ 
      x.notify() 
     //或者wait() 
   }
 
4、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
 
    15: 线程池几个参数
 
 
  1. ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(corePoolSize,// 核心线程数  
  2.                         maximumPoolSize, // 最大线程数  
  3.                         keepAliveTime, // 闲置线程存活时间  
  4.                         TimeUnit.MILLISECONDS,// 时间单位  
  5.                         new LinkedBlockingDeque<Runnable>(),// 线程队列  
  6.                         Executors.defaultThreadFactory(),// 线程工厂  
  7.                         new AbortPolicy()// 队列已满,而且当前线程数已经超过最大线程数时的异常处理策略  
  8.                 );  
线程池任务执行流程:
  1. 当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
  2. 当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
  3. 当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
  4. 当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
  5. 当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
  6. 当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭
    16: 怎么评估线程数大小 
如果线程池中的线程在执行任务时,密集计算所占的时间比重为P(0<P<=1),而系统一共有C个CPU,为了让CPU跑满而又不过载,线程池的大小经验公式 T = C / P。在此,T只是一个参考,考虑到P的估计并不是很准确,T的最佳估值可以上下浮动50%。
这个经验公式的原理很简单,T个线程,每个线程占用P的CPU时间,如果刚好占满C个CPU,那么必有 T * P = C。
 
假设C = 8,P = 1.0,线程池的任务完全是密集计算,那么T = 8。只要8个活动线程就能让8个CPU饱和,再多也没用了,因为CPU资源已经耗光了。
假设C = 8,P = 0.5,线程池的任务有一半是计算,有一半在等IO上,那么T = 16.考虑操作系统能灵活,合理调度sleeping/writing/running线程,那么大概16个“50%繁忙的线程”能让8个CPU忙个不停。启动更多的线程并不能提高吞吐量,反而因为增加上下文切换的开销而降低性能。
如果P < 0.2,这个公式就不适用了,T可以取一个固定值,比如 5*C。另外公式里的C不一定是CPU总数,可以是“分配给这项任务的CPU数目”,比如在8核机器上分出4个核来做一项任务,那么C=4
 
 
    17: 几个线程访问同一个东西,怎么保证安全
1、同步代码块: 
2、同步方法 
3、同步锁-锁机制lock 
一般说来,确保线程安全的方法有这几个:竞争与原子操作、同步与锁、可重入、过度优化。
 
 
    18: Spring几个特点说下
1--核心容器
核心容器提供spring框架的基本功能,核心容器的主要组件是BeanFactory, 他是工厂模式的实现. 
BeanFactory使用控制反转(IOC)模式将应用程序的配置和依赖性与实际的应用程序代码分开
2--Spring上下文
是一个配置文件,该配置文件向spring框架提供上下文信息
3--Spring AOP
通过配置管理特性,Spring AOP 模块直接将面向切面(方面)编程功能集成到spring框架中
4--spring DAO
JDBC DAO抽象层提供了有意义的已成层次结构, 可用该结构管理异常处理和不同数据库抛出的错误信息,极大的降低了异常代码数量
5--Spring ORM
spring框架插入了若干个ORM框架, 从而提供了ORM的对象工具,其中包括了Hibernate, Mybatis
6--Spring Web
web上下文模块建立在应用程序上下文模块之上,为基于web的应用程序提供上下文
7--Spring MVC
该框架是一个全功能的构建web应用程序的MVC实现. 通过策略接口,MVC框架变成高度可配置的. MVC容纳了大量视图技术. 其中包括JSP、Velocity和POI
Spring 框架的好处
spring是最大的工厂
 spring负责业务逻辑组件的框架和生成, 并管理业务逻辑组件的生命周期 
spring可以生产所有实例, 从控制器、 业务逻辑组件、 持久层组件
Spring特点
1--降低了组件之间的耦合性, 实现了软件各个层之间的解耦
2--可以使用spring容器提供的服务, 如: 事务管理, 消息服务
3--容器提供单例模式支持
4--容器提供AOP技术, 利用它很容易实现权限拦截, 运行期监控
5--容器提供了众多的辅助类, 能加快应用的开发(org.springframework.jdbc.core.JDBCTemplate 等)
6--spring对主流的应用框架提供了集成支持, 例如: hibernate,JPA, Struts, Mybatis(IBatis)
7--Spring属于低侵入式设计, 代码污染度极低
8--独立于各种应用服务器
9--spring的DI机制降低了业务对象替换的复杂性
10--spring的高度开发性, 并不强制应用完全依赖于spring, 开发者可以自由选择spring的部分或者全部
    19: CGLib有没有了解过
CGlib是什么? 
CGlib是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。 
当然这些实际的功能是asm所提供的,asm又是什么?Java字节码操控框架,具体是什么大家可以上网查一查,毕竟我们这里所要讨论的是cglib, 
cglib就是封装了asm,简化了asm的操作,实现了在运行期动态生成新的class。 
可能大家还感觉不到它的强大,现在就告诉你。 
实际上CGlib为spring aop提供了底层的一种实现;为hibernate使用cglib动态生成VO/PO (接口层对象)。 
CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择。
 
CGLIB原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。
CGLIB底层:使用字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
CGLIB缺点:对于final方法,无法进行代理。
 
 
    20: Spring支持哪几种切片
1.前置增强:org.springframework.aop.BeforeAdvice代表前置增强,因为Spring只支持
方法级的增强,所以MethodBeforeAdvice是目前可的的前置增强,表示在目标方法执行前
实施增强,而BeforeAdvice是为了将来版本扩展需要而定义的;
2.后置增强:org.springframework.aop.AfterReturningAdvice代表后增强,表示在目标
方法执行后实施增强;
3.环绕增强:org.aopalliance.intercept.MethodInterceptor代表环绕增强,表示在目标
方法执行前后实施增强;
4:异常抛出增强:org.springframework.aop.ThrowsAdvice代表抛出异常增强,表示在目
标方法抛出异常后实施增强;
5.引介增强:org.springframework.aop.InteoductionInterceptor代表引介增强,表示在
目标类中添加一些新的方法和属性;
这些增强接口都有一些方法,通过实现这些接口方法,在接口方法中这义横切逻辑,就可以
将它们织入到目标类的方法的相应连接点的位置。
 
    21: SpringBoot和Spring有什么区别
Spring Boot是最近这几年才火起来的,那么它到底与Spring有啥区别呢?
想了解区别,其实就是Spring Boot提供了哪些特征:
  1. Spring Boot可以建立独立的Spring应用程序;
  2. 内嵌了如Tomcat,Jetty和Undertow这样的容器,也就是说可以直接跑起来,用不着再做部署工作了。
  3. 无需再像Spring那样搞一堆繁琐的xml文件的配置;
  4. 可以自动配置Spring;
  5. 提供了一些现有的功能,如量度工具,表单数据验证以及一些外部配置这样的一些第三方功能;
  6. 提供的POM可以简化Maven的配置;
    22: SpringBoot和Spring启动有什么区别
1、IDEA启动 SpringBootApplication
2、命令行启动首先将命令行位置跳转到当前项目的根目录下,再输入“mvn spring-boot:run”命令,初次操作maven需要下载插件等待几分钟。
3、命令行编译为jar启动首先命令行在当前项目根目录运行编译命令“mvn install”,之后跳转到当前项目的target文件夹下(cd target)多出两个文件
 
 
spring
  1. 首先,对于一个web应用,其部署在web容器中,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的spring IoC容器提供宿主环境;
  2. 其次,在web.xml中会提供有contextLoaderListener。在web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其contextInitialized方法会被调用,在这个方法中,spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是XmlWebApplicationContext。这个就是spring的IoC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。在这个IoC容器初始化完毕后,spring以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取;
  3. 再次,contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个servlet可以配置多个,以最常见的DispatcherServlet为例,这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文,用以持有spring mvc相关的bean。在建立DispatcherServlet自己的IoC上下文时,会利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文。有了这个parent上下文之后,再初始化自己持有的上下文。这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化处理器映射、视图解析等。这个servlet自己持有的上下文默认实现类也是mlWebApplicationContext。初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换,具体可自行查看源码)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文(第2步中初始化的上下文)定义的那些bean。
 
    23: Spring启动生命周期 
 
 
 
    24: Spring注解@Resource和@Autowired区别对比  => 优先级不一样
1、@Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上。 
2、@Autowired默认按类型装配(这个注解属于Spring),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用,如下: 
@Autowired() @Qualifier("baseDao")     
private BaseDao baseDao;    
 3、@Resource(这个注解属于J2EE),默认按照名称进行装配,名称可以通过name属性进行指定, 
如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。 当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
@Resource(name="baseDao")     
private BaseDao baseDao;    
 
我喜欢用 @Resource注解在字段上,且这个注解是属于J2EE的,减少了与spring的耦合。最重要的这样代码看起就比较优雅。
 
 
    25: spring @service @controller @componet 三者区别
@controller用来定义控制层的组件 
         @service用来定义业务层的组件
         @repository用来定义持久层的组件
         @ component用来定义不在上述范围内的一般性组件
上面组件的名称默认是类名的首字母小写,如果要重命名,则这样@controller("beanName")
当在spring中配置了<context:annotation-config/> 和<context:component-scan base-package="*">时,上述四种注解的组件都会由spring容器来创建为bean并由自己来管理.
 
那么创建了上面这些组件后,又是如何来注入的呢,这时就由@autowired来配置了。
只需要在private的属性上加上@autowired就可以自动把接口属性的实现类的bean注入,注意不需要setter,getter方法
 
上面如果一个接口属性有两个实现类,怎么办,这时就要用@qualifier来特别说明要注入哪个bean了。
    26: Http和Https协议有什么区别,证书了解不
HTTPS和HTTP的区别:
https协议需要到ca申请证书,一般免费证书很少,需要交费。
http是 超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议
http和https使用的是完全不同的连接方式用的端口也不一样,前者是80,后者是443。
http的连接很简单,是无状态的
HTTPS协议是由SSL+ HTTP协议构建的可进行加密传输、身份认证的网络协议 要比 http协议安全
 
通俗来讲:
HTTPS是经过加密的,在通信过程中,他人无法截获你们之间的通信信息。
需要证书才能访问
是一串身份信息按照指定的格式排列后,包含证书颁发机构进行数字签名的数据。常见的数字证书格式X.509 V3,存储到文件上一般一种为不含私钥的DER格式,以cer或者crt为文件后缀名,另一种为包含私钥的PKCS格式,以pfx或者p12为后缀。
数字证书中包括(不是全部):
(1)数字证书持有者身份信息 网站就包括所对应的URL或IP
(2)该证书拥有者对应的该证书公钥
(3)证书颁发者信息
(4)证书颁发者用私钥对证书持有者身份信息和公钥的签名 使用的摘要和签名算法
 
    27: 介绍下Redis设计实现
Redis是基于内存、可持久化的日志型、Key-Value 数据库 高性能存储系统,
 
  
 
 
    28: Redis的细节源码看过没有
1. 支持5种数据结构
支持strings, hashes, lists, sets, sorted sets 
string是很好的存储方式,用来做计数存储。sets用于建立索引库非常棒;
2. K-V 存储 vs K-V 缓存
新浪微博目前使用的98%都是持久化的应用,2%的是缓存,用到了600+服务器 
Redis中持久化的应用和非持久化的方式不会差别很大: 
非持久化的为8-9万tps,那么持久化在7-8万tps左右; 
当使用持久化时,需要考虑到持久化和写性能的配比,也就是要考虑redis使用的内存大小和硬盘写的速率的比例计算;
3. 社区活跃
Redis目前有3万多行代码, 代码写的精简,有很多巧妙的实现,作者有技术洁癖 
Redis的社区活跃度很高,这是衡量开源软件质量的重要指标,开源软件的初期一般都没有商业技术服务支持,如果没有活跃社区做支撑,一旦发生问题都无处求救;
Redis基本原理
redis持久化(aof) append online file: 
写log(aof), 到一定程度再和内存合并. 追加再追加, 顺序写磁盘, 对性能影响非常小
1. 单实例单进程
Redis使用的是单进程,所以在配置时,一个实例只会用到一个CPU; 
在配置时,如果需要让CPU使用率最大化,可以配置Redis实例数对应CPU数, Redis实例数对应端口数(8核Cpu, 8个实例, 8个端口), 以提高并发: 
单机测试时, 单条数据在200字节, 测试的结果为8~9万tps;
2. Replication
过程: 数据写到master–>master存储到slave的rdb中–>slave加载rdb到内存。 
存储点(save point): 当网络中断了, 连上之后, 继续传. 
Master-slave下第一次同步是全传,后面是增量同步;、
3. 数据一致性
长期运行后多个结点之间存在不一致的可能性; 
开发两个工具程序: 
1.对于数据量大的数据,会周期性的全量检查; 
2.实时的检查增量数据,是否具有一致性;
对于主库未及时同步从库导致的不一致,称之为延时问题; 
对于一致性要求不是那么严格的场景,我们只需要要保证最终一致性即可; 
对于延时问题,需要根据业务场景特点分析,从应用层面增加策略来解决这个问题; 
例如: 
1.新注册的用户,必须先查询主库; 
2.注册成功之后,需要等待3s之后跳转,后台此时就是在做数据同步。
    29: Redis分布式缓存
1.架构设计
由于redis是单点,项目中需要使用,必须自己实现分布式。基本架构图如下所示:
 
 
2.分布式实现
通过key做一致性哈希,实现key对应redis结点的分布。
一致性哈希的实现:
l        hash值计算:通过支持MD5与MurmurHash两种计算方式,默认是采用MurmurHash,高效的hash计算。
l        一致性的实现:通过java的TreeMap来模拟环状结构,实现均匀分布
3.client的选择
对于jedis修改的主要是分区模块的修改,使其支持了跟据BufferKey进行分区,跟据不同的redis结点信息,可以初始化不同的ShardInfo,同时也修改了JedisPool的底层实现,使其连接pool池支持跟据key,value的构造方法,跟据不同ShardInfos,创建不同的jedis连接客户端,达到分区的效果,供应用层调用
4.模块的说明
l        脏数据处理模块,处理失败执行的缓存操作。
l        屏蔽监控模块,对于jedis操作的异常监控,当某结点出现异常可控制redis结点的切除等操作。
整个分布式模块通过hornetq,来切除异常redis结点。对于新结点的增加,也可以通过reload方法实现增加。(此模块对于新增结点也可以很方便实现)
对于以上分布式架构的实现满足了项目的需求。另外使用中对于一些比较重要用途的缓存数据可以单独设置一些redis结点,设定特定的优先级。另外对于缓存接口的设计,也可以跟据需求,实现基本接口与一些特殊逻辑接口。对于cas相关操作,以及一些事物操作可以通过其watch机制来实现。(参考我以前写的 redis事物介绍
    30: 线程在频繁的Full GC 怎么排查
我们知道Full GC的触发条件大致情况有以下几种情况: 
1. 程序执行了System.gc() //建议jvm执行fullgc,并不一定会执行 
2. 执行了jmap -histo:live pid命令 //这个会立即触发fullgc 
3. 在执行minor gc的时候进行的一系列检查
a、执行Minor GC的时候,JVM会检查老年代中最大连续可用空间是否大于了当前新生代所有对象的总大小。b、如果大于,则直接执行Minor GC(这个时候执行是没有风险的)。c、如果小于了,JVM会检查是否开启了空间分配担保机制,如果没有开启则直接改为执行Full GC。d、如果开启了,则JVM会检查老年代中最大连续可用空间是否大于了历次晋升到老年代中的平均大小,如果小于则执行改为执行Full GC。e、如果大于则会执行Minor GC,如果Minor GC执行失败则会执行Full GC
a、使用了大对象 //大对象会直接进入老年代
b、在程序中长期持有了对象的引用 //对象年龄达到指定阈值也会进入老年代
对于我们的情况,可以初步排除a,b两种情况,最有可能是d和e这两种情况。为了进一步排查原因,我们在线上开启了 -XX:+HeapDumpBeforeFullGC。
JVM在执行dump操作的时候是会发生stop the word事件的,也就是说此时所有的用户线程都会暂停运行。 为了在此期间也能对外正常提供服务,建议采用分布式部署,并采用合适的负载均衡算法
 
 
1)FULL GC前后Java堆大小有变化;经研究发现是由于Java应用JVM参数XMS设置为默认值,在我们的系统环境下,Hotspot的Xms默认值为50M(-Xms默认是物理内存的1/64);每次GC时,JVM会根据各种条件调节Java堆的大小,Java堆的取值范围为[Xms, Xmx]。根据以上分析,修改Xms值与Xmx相等,这样就不会因为所使用的Java堆不够用而进行调节,经过测试后发现FULL GC次数从四位数减少至个位数。
 
2)关键词“System”让我想到了System.gc调用,System.gc调用只是建议JVM执行年老代GC,而年老代GC触发FULL GC,JVM会根据系统条件决定是否执行FULL GC,正因为系统条件不好判断,所以很难构造System.gc调用触发FULL GC,几经周折终于成功,当System.gc触发FULL  GC时都会有关键词“(System)”,而 JVM自动触发的FULL GC却不带关键词“(System)”,可以断定是Java应用存在“System.gc”代码。经过本次测试我也发现System.gc的真正含义,通俗言之,“System.gc” 就是FULL GC触发的最后一根稻草。 
 
从本次分析中,我们可以得出如下的经验: 
1)Java应用的jvm参数Xms与Xmx保持一致,避免因所使用的Java堆内存不够导致频繁full gc以及full gc中因动态调节Java堆大小而耗费延长其周期。 
2)建议不要调用System.gc或者Runtime.getRuntime().gc,否则本次调用可能会成为“压死骆驼的最后一根稻草”。当然我们可以通过设置jvm参数禁止这种调用生效,但是除非特别有把握该参数有必要添加,否则不推荐这么设置。
    31: JVM一些工具,jps, jmap
 -v 输出传递给JVM的参数
$> jps -v
23789 BossMain
28802 Jps -Denv.class.path=/data/aoxj/bossbi/twsecurity/java/trustwork140.jar:/data/aoxj/bossbi/twsecurity/java/:/data/aoxj/bossbi/twsecurity/java/twcmcc.jar:/data/aoxj/jdk15/lib/rt.jar:/data/aoxj/jdk15/lib/tools.jar -Dapplication.home=/data/aoxj/jdk15 -Xms8m
23651 Resin -Xss1m -Dresin.home=/data/aoxj/resin -Dserver.root=/data/aoxj/resin -Djava.util.logging.manager=com.caucho.log.LogManagerImpl -Djavax.management.builder.initial=com.caucho.jmx.MBeanServerBuilderImpl
 
jmap
JVM Memory Map命令用于生成heap dump文件,如果不使用这个命令,还可以使用-XX:+HeapDumpOnOutOfMemoryError参数来让虚拟机出现OOM的时候自动生成dump文件。 jmap不仅能生成dump文件,还可以查询finalize执行队列、Java堆和永久代的详细信息,如当前使用率、当前使用的是哪种收集器等。【内存分析】
  
 
32:  海量日志数据,提取出某日访问百度次数最多的那个IP?

此题,在我之前的一篇文章算法里头有所提到,当时给出的方案是:IP的数目还是有限的,最多2^32个,所以可以考虑使用hash将ip直接存入内存,然后进行统计。

  再详细介绍下此方案:首先是这一天,并且是访问百度的日志中的IP取出来,逐个写入到一个大文件中。注意到IP是32位的,最多有个2^32个IP。同样可以采用映射的方法,比如模1000,把整个大文件映射为1000个小文件,再找出每个小文中出现频率最大的IP(可以采用hash_map进行频率统计,然后再找出频率最大的几个)及相应的频率。然后再在这1000个最大的IP中,找出那个频率最大的IP,即为所求。  

   
34:  垃圾回收的原理
     优点:a.不需要考虑内存管理, b.可以有效的防止内存泄漏,有效的利用可使用的内存, c.由于有垃圾回收机制,Java中的对象不再有"作用域"的概念,只有对象的引用才有"作用域"
     原理:垃圾回收器是作为一个单独的低级别的线程运行,在不可知的情况下对内存堆中已死亡的或者长期没有使用的对象回收,但是不能实时的对某一对象或者所有对象进行垃圾回收。
     垃圾回收机制:分代复制垃圾回收、标记垃圾回收、增量垃圾回收
    35:  你写过Java的Web系统
 
    36:  简单介绍一下你的项目
 
    37:  两个有序的数组,合成一个有序的数组,怎么合并效率高 
归并排序
    38:  淘宝的登陆页面,怎么保证他安全

使用哈希加盐法来为密码加密
      解决的办法是将密码加密后再存储进数据库,比较常用的加密方法是使用哈希函数(Hash Function)。哈希函数的具体定义,大家可以在网上或者相关书籍中查阅到,简单地说,它的特性如下:  
     (1)原始密码经哈希函数计算后得到一个哈希值  
     (2)改变原始密码,哈希函数计算出的哈希值也会相应改变 
     (3) 同样的密码,哈希值也是相同的 
     (4) 哈希函数是单向、不可逆的。也就是说从哈希值,你无法推算出原始的密码是多少 
    最简单、常见的破解方式当属字典破解(Dictionary Attack)和暴力破解(Brute Force Attack)方式。这两种方法说白了就是猜密码。

    字典破解和暴力破解都是效率比较低的破解方式。如果你知道了数据库中密码的哈希值,你就可以采用一种更高效的破解方式,查表法(Lookup Tables)。还有一些方法,比如逆向查表法(Reverse Look     up Tables)、彩虹表(Rainbow Tables)等,都和查表法大同小异。现在我们来看一下查表法的原理。

     查表法不像字典破解和暴力破解那样猜密码,它首先将一些比较常用的密码的哈希值算好,然后建立一张表,当然密码越多,这张表就越大。当你知道某个密码的哈希值时,你只需要在你建立好的表中查找       该哈希值,如果找到了,你就知道对应的密码了。

      从上面的查表法可以看出,即便是将原始密码加密后的哈希值存储在数据库中依然是不够安全的。那么有什么好的办法来解决这个问题呢?答案是加盐。

      盐(Salt)是什么?就是一个随机生成的字符串。我们将盐与原始密码连接(concat)在一起(放在前面或后面都可以),然后将concat后的字符串加密。采用这种方式加密密码,查表法就不灵了(因为盐是随机生成的)。 

     单单使用哈希函数来为密码加密是不够的,需要为密码加盐来提高安全性,盐的长度不能过短,并且盐的产生应该是随机的。

 
    39:  你有最新半年用户的订单,每天的用户订单量有上亿,预测下未来一周哪些商品最容易被购买
数据建模-分析
   40: 你有啥问题
“入职后有没有培训活动?”
“公司对我的期望是什么?”
“这个部门或团队有多少人?主要是负责哪方面的?”
 
1、synchronized关键字原理?
    原理:synchronized底层是通过一个monitor的对象阻塞和获取。
    对代码同步:指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。 
    对方法同步:常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。
    重量级锁:Mutex Lock 监视器锁monitor本质就是依赖于底层的操作系统的Mutex Lock来实现的。
    
 
2、hashMap底层实现。
  • HashMap 的基本组成成员
        首先,HashMap 是 Map 的一个实现类,它代表的是一种键值对的数据存储形式。Key 不允许重复出现,Value 随意。jdk 8 之前,其内部是由数组+链表来实现的,而 jdk 8 对于链表长度超过 8 的链表将转储为红黑树。大致的数据存储形式如下:
    
  从上图中可以看出,HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。
   源码如下:
Java代码  
  1. /** 
  2.  * The table, resized as necessary. Length MUST Always be a power of two. 
  3.  */  
  4. transient Entry[] table;  
  5.   
  6. static class Entry<K,V> implements Map.Entry<K,V> {  
  7.     final K key;  
  8.     V value;  
  9.     Entry<K,V> next;  
  10.     final int hash;  
  11.     ……  
  12. }  
   可以看出,Entry就是数组中的元素,每个 Map.Entry 其实就是一个key-value对,它持有一个指向下一个元素的引用,这就构成了链表。
 
  • put 方法的具体实现
Java代码  
  1. public V put(K key, V value) {  
  2.     // HashMap允许存放null键和null值。  
  3.     // 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。  
  4.     if (key == null)  
  5.         return putForNullKey(value);  
  6.     // 根据key的keyCode重新计算hash值。  
  7.     int hash = hash(key.hashCode());  
  8.     // 搜索指定hash值在对应table中的索引。  
  9.     int i = indexFor(hash, table.length);  
  10.     // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。  
  11.     for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  12.         Object k;  
  13.         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  14.             V oldValue = e.value;  
  15.             e.value = value;  
  16.             e.recordAccess(this);  
  17.             return oldValue;  
  18.         }  
  19.     }  
  20.     // 如果i索引处的Entry为null,表明此处还没有Entry。  
  21.     modCount++;  
  22.     // 将key、value添加到i索引处。  
  23.     addEntry(hash, key, value, i);  
  24.     return null;  
  25. }  
      当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
    
    
 
  • remove 方法的具体实现。
        采用迭代器遍历,不仅适用于HashMap,对其它类型的容器同样适用。
   采用这种方法的遍历,可以用下文提及的方式安全地对HashMap内的元素进行修改,并不会对后续的删除操作造成影响。
  如果使用foreach遍历方法删除HashMap中的元素,Java很有可能会在运行时抛出异常。
    为什么呢?
    1、使用iterator迭代删除时没有问题的,在每一次迭代时都会调用hasNext()方法判断是否有下一个,是允许集合中数据增加和减少的。
    2、使用forEach删除时,会报错ConcurrentModificationException,因为在forEach遍历时,是不允许map元素进行删除和增加。
    所以,遍历删除map集合中元素时,必须使用迭代iterator
 
 
 
 
  • 为什么时HashMap的容量总是2的n次方?
    如果不是2的2次幂,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!而当数组长度为16时,即为2的n次方时,2n-1得到的二进制数的每个位上的值都为1,这使得在低位上&时,得到的和原hash的低位相同,加之hash(int h)方法对key的hashCode的进一步优化,加入了高位计算,就使得只有相同的hash值的两个值才会被放到数组中的同一个位置上形成链表。
    
    
    
 
  • 其他一些基本方法的基本介绍
 
   HashMap 包含如下几个构造器:
   HashMap():构建一个初始容量为 16,负载因子为 0.75 的 HashMap。
   HashMap(int initialCapacity):构建一个初始容量为 initialCapacity,负载因子为 0.75 的 HashMap。
   HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的负载因子创建一个 HashMap。
   HashMap的基础构造器HashMap(int initialCapacity, float loadFactor)带有两个参数,它们是初始容量initialCapacity和加载因子loadFactor。
   initialCapacity:HashMap的最大容量,即为底层数组的长度。
   loadFactor:负载因子loadFactor定义为:散列表的实际元素数目(n)/ 散列表的容量(m)。
 
 
  • 什么是红黑树
红黑树本质上是一种二叉查找树,但它在二叉查找树的基础上额外添加了一个标记(颜色),同时具有一定的规则。这些规则使红黑树保证了一种平衡,插入、删除、查找的最坏时间复杂度都为 O(logn)。
 
 
 
3、TCP与UDP的区别
  1、基于连接与无连接
  2、TCP要求系统资源较多,UDP较少; 
  3、UDP程序结构较简单 
  4、流模式(TCP)与数据报模式(UDP); 
  5、TCP保证数据正确性,UDP可能丢包 
  6、TCP保证数据顺序,UDP不保证 
 
 
4、TCP三次握手说一下。
 
简单说,让双方都证实对方能发收。
知道对方能收是因为收到对方的因为收到而发的回应。
具体:
1:A发,B收, B知道A能发
2:B发,A收, A知道B能发收
3:A发,B收, B知道A能收
 
5、看你项目用到线程池,说一下线程池工作原理,任务拒接策略有哪几种?
一个线程从被提交(submit)到执行共经历以下流程:
  • 线程池判断核心线程池里是的线程是否都在执行任务,如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下一个流程
  • 线程池判断工作队列是否已满。如果工作队列没有满,则将新提交的任务储存在这个工作队列里。如果工作队列满了,则进入下一个流程。
  • 线程池判断其内部线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已满了,则交给饱和策略来处理这个任务。
 
任务拒接策略?
 有4种内置的实现策略和一个用户自定义拒绝策略。
AbortPolicy       为java线程池默认的阻塞策略,不执行此任务,而且直接抛出一个运行时异常,切记ThreadPoolExecutor.execute需要try catch,否则程序会直接退出。 
DiscardPolicy        直接抛弃,任务不执行,空方法 。
DiscardOldestPolicy   从队 列里面抛弃head的一个任务,并再次execute 此task。
CallerRunsPolicy        在调用execute的线程里面执行此command,会阻塞入口 。 
用户自定义拒绝策略   实现RejectedExecutionHandler,并自己定义策略模式。
 
再次需要注意的是,ThreadPoolExecutor.submit() 函数,此方法内部调用的execute方法,并把execute执行完后的结果给返回,但如果任务并没有执行的话(被拒绝了),则submit返回的future.get()会一直等到。
future 内部其实还是一个runnable,并把command给封装了下,当command执行完后,future会返回一个值。
 
 
6、进程和线程的区别?
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
 
1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
2) 线程的划分尺度小于进程,使得多线程程序的并发性高。
3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
 
 
7、ArrayList与LinkedList的区别?
  1、ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。 (LinkedList是双向链表,有next也有previous)
  2、对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。 
  3、对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。 
 
 
 
8、线程安全与非线程安全集合说一下,底层怎么实现的(hashmap,concurrenthashmap)?
     Hashmap本质是数组加链表。根据key取得hash值,然后计算出数组下标,如果多个key对应到同一个下标,就用链表串起来,新插入的在前面。
     ConcurrentHashMap:在hashMap的基础上,ConcurrentHashMap将数据分为多个segment,默认16个(concurrency level),然后每次操作对一个segment加锁,避免多线程锁的几率,提高并发效率。
    
 
9、Hashtable、ConcurrentHashMap、TreeMap、HashMap的key,value都是不为空的吗?
 
HashMap的key和value都允许为空,treeMap的value允许为空。
 
10、单例模式  
  •  有几种实现方式 ?
        5种  
1.饿汉模式(调用效率高,但是不能延时加载):
2.懒汉模式(调用效率不高,但是能延时加载):
3.双重检测锁模式(由于JVM底层模型原因,偶尔会出问题,不建议使用):
4.静态内部类式(线程安全,调用效率高,可以延时加载):
5.枚举类(线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用):
如何选用:
-单例对象 占用资源少,不需要延时加载,枚举 好于 饿汉
-单例对象 占用资源多,需要延时加载,静态内部类 好于 懒汉式 
 
  • 单例模线程安全吗?  
  不安全 。
  • 一般如何保证它线程安全 ?  
      double-check 双重检查锁定  。
  • 修饰符 volatile  有什么作用 ?
        能保证被它修饰的成员变量可以被多个线程正确的处理。Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。它在某些情况下比synchronized的开销更小
 
11、 判断一个字符在一个字符串中出现的次数  ?
  StringUtils.countMatches(str, t);
 
12、HashMap是不是有序的?
 
   不是有序的.
 
  • 有没有有顺序的Map实现类? 
有TreeMap和LinkedHashMap。
 
  • TreeMap和LinkedHashMap是如何保证它的顺序的?
          LinkedHashMap 是根据元素增加或者访问的先后顺序进行排序,而 TreeMap是基于元素的固有顺序 (由 Comparator 或者 Comparable 确定)。
                                 
 
  • 哪个的有序实现比较好?
      TreeMap TreeMap则实现了 SortedMap 接口。          
  • 你觉得还有没有比它更好或者更高效的实现方式?
           参照TreeMap的value排序,我们一样的也可以实现HashMap的排序。
 
13、实现所有的线程一起等待某个事件的发生,当某个事件发生时,所有线程一起开始往下执行的话,有什么好的办法吗?
 
栅栏(Java的并发包中的CyclicBarrier)  CountDownLatch  CyclicBarrier  Semaphore
  • CountDownLatch (N个线程数量count减为0 主程序或一组程序开始执行)
        CountDownLatch是一个计数器闭锁,主要的功能就是通过await()方法来阻塞住当前线程,然后等待计数器减少到0了,再唤起这些线程继续执行。 
        这个类里主要有两个方法,一个是向下减计数器的方法:countdown(),如果取得当前的状态为0,说明这个锁已经结束,直接返回false;
        如果没有结束,然后去设置计数器减1,如果compareAndSetState不成功,则继续循环执行。 而其中的一直等待计数器归零的方法是await()。 
  • CyclicBarrier(N个线程,他们之间任何一个没有完成,所有的线程都必须等待) 
        CyclicBarrier是加计数方式,计数达到构造方法中参数指定的值时释放所有等待的线程。
 
  • Semaphore(Semaphore 是只允许一定数量的线程同时执行一段任务。)
            Semaphore,每次semaphore.acquire(),获取一个资源,每次semaphore.acquire(n),获取n个资源,
            当达到semaphore 指定资源数量时就不能再访问线程处于阻塞,必须等其它线程释放资源,semaphore.relase()每次资源一个资源,
            semaphore.relase(n)每次资源n个资源。
            
你知道它的实现原理吗?
继续问,你还知道其它的实现方式吗?
 
继续问,你觉得这些方式里哪个方式更好?
 
如果让你来写的话,你觉得还有比它更好的实现方式吗?
 
14、IO包和NIO包 熟悉吗?
  • NIO模型 其中的selector 职责和实现原理
传统的socket IO中,需要为每个连接创建一个线程,当并发的连接数量非常巨大时,线程所占用的栈内存和CPU线程切换的开销将非常巨大。使用NIO,不再需要为每个线程创建单独的线程,可以用一个含有限数量线程的线程池,甚至一个线程来为任意数量的连接服务。由于线程数量小于连接数量,所以每个线程进行IO操作时就不能阻塞,如果阻塞的话,有些连接就得不到处理,NIO提供了这种非阻塞的能力。
1、增加了一个角色,要有一个专门负责收集客人需求的人。NIO里对应的就是Selector。
2、由阻塞服务方式改为非阻塞服务了,客人吃着的时候服务员不用一直侯在客人旁边了。传统的IO操作,比如read(),当没有数据可读的时候,线程一直阻塞被占用,直到数据到来。NIO中没有数据可读时,read()会立即返回0,线程不会阻塞。
     NIO中,客户端创建一个连接后,先要将连接注册到Selector,相当于客人进入餐厅后,告诉前台你要用餐,前台会告诉你你的桌号是几号,然后你就可能到那张桌子坐下了,SelectionKey就是桌号。当某一桌需要服务时,前台就记录哪一桌需要什么服务,比如1号桌要点菜,2号桌要结帐,服务员从前台取一条记录,根据记录提供服务,完了再来取下一条。这样服务的时间就被最有效的利用起来了。
  • NIO的核心是什么 (Selector)
Selector类是NIO的核心类,Selector能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的响应处理。这样一来,只是用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,并且避免了多线程之间的上下文切换导致的开销。
  与Selector有关的一个关键类是SelectionKey,一个SelectionKey表示一个到达的事件,这2个类构成了服务端处理业务的关键逻辑。
 
15、虚拟机JVM 组成部分
    
程序计数器
指示当前程序执行到了哪一行,执行JAVA方法时纪录正在执行的虚拟机字节码指令地址;执行本地方法时,计数器值为undefined
虚拟机栈
用于执行JAVA方法。栈帧存储局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。程序执行时栈帧入栈;执行完成后栈帧出栈
本地方法栈
用于执行本地方法,其它和虚拟机栈类似
着重说一下虚拟机栈中的局部变量表,里面存放了三个信息:
  • 各种基本数据类型(boolean、byte、char、short、int、float、long、double)
  • 对象引用(reference)
  • returnAddress地址
这个returnAddress和程序计数器有什么区别?前者是指示JVM的指令执行到哪一行,后者则是你的代码执行到哪一行。
私有内存区伴随着线程的产生而产生,一旦线程中止,私有内存区也会自动消除,因此讨论的内存回收主要是针对共享内存区。
JAVA堆
既然GC主要发生在堆内存中,这部分我们会对堆内存进行比较详细的描述。
堆内存是由存活和死亡的对象组成的。存活的对象是应用可以访问的,不会被垃圾回收。死亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象。一直到垃圾收集器把这些对象回收掉之前,他们会一直占据堆内存空间。堆是应用程序在运行期请求操作系统分配给自己的向高地址扩展的数据结构,是不连续的内存区域。用一句话总结堆的作用:程序运行时动态申请某个大小的内存空间。 
 
新生代:刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1。S0和Eden被清空,然后下一轮S0与S1交换角色,如此循环往复。如果对象的复制次数达到16次,该对象就会被送到老年代中。
  • 为什么新生代要分出两个survivor区?
        设置两个Survivor区最大的好处就是解决了碎片化
老年代:如果某个对象经历了几次垃圾回收之后还存活,就会被存放到老年代中。老年代的空间一般比新生代大。
 
GC名称
介绍
Minor GC
发生在新生代,频率高,速度快(大部分对象活不过一次Minor GC)
Major GC
发生在老年代,速度慢
Full GC
清理整个堆空间
  • java中垃圾回收机制?
JAVA 并没有给我们提供明确的代码来标注一块内存并将其回收。或许你会说,我们可以将相关对象设为 null 或者用 System.gc()。然而,后者将会严重影响代码的性能,因为一般每一次显式的调用 system.gc() 都会停止所有响应,去检查内存中是否有可回收的对象。这会对程序的正常运行造成极大的威胁。另外,调用该方法并不能保证 JVM 立即进行垃圾回收,仅仅是通知 JVM 要进行垃圾回收了,具体回收与否完全由 JVM 决定。这样做是费力不讨好。
 
  • 垃圾回收算法概述
    1、追踪回收算法(tracing collector) 
    从根结点开始遍历对象的应用图。同时标记遍历到的对象。遍历完成后,没有被标记的对象就是目前未被引用,可以被回收。
    2、压缩回收算法(Compacting Collector) 
    把堆中活动的对象集中移动到堆的一端,就会在堆的另一端流出很大的空闲区域。这种处理简化了消除碎片的工作,但可能带来性能的损失。
    3、复制回收算法(Coping Collector) 
把堆均分成两个大小相同的区域,只使用其中的一个区域,直到该区域消耗完。此时垃圾回收器终端程序的执行,通过遍历把所有活动的对象复制到另一个区域,复制过程中它们是紧挨着布置的,这样也可以达到消除内存碎片的目的。复制结束后程序会继续运行,直到该区域被用完。 
但是,这种方法有两个缺陷:
对于指定大小的堆,需要两倍大小的内存空间,
需要中断正在执行的程序,降低了执行效率
    4、按代回收算法(Generational Collector) 
    为什么要按代进行回收?这是因为不同对象生命周期不同,每次回收都要遍历所有存活对象,对于整个堆内存进行回收无疑浪费了大量时间,对症下药可以提高垃圾回收的效率。主要思路是:把堆分成若搞个子堆,每个子堆视为一代,算法在运行的过程中优先收集“年幼”的对象,如果某个对象经过多次回收仍然“存活”,就移动到高一级的堆,减少对其扫描次数。
  •     垃圾回收器有哪些?
    串行回收器(serial collector)
    并行回收器
    CMS回收器
    G1回收器
    
    
    JAVA性能优化
    真正影响JAVA程序性能的,就是碎片化。碎片是JAVA堆内存中的空闲空间,可能是TLAB剩余空间,也可能是被释放掉的具有较长生命周期的小对象占用的空间。
  1. 减少new对象。每次new对象之后,都要开辟新的内存空间。这些对象不被引用之后,还要回收掉。因此,如果最大限度地合理重用对象,或者使用基本数据类型替代对象,都有助于节省内存;
  2. 多使用局部变量,减少使用静态变量。局部变量被创建在栈中,存取速度快。静态变量则是在堆内存;
  3. 避免使用finalize,该方法会给GC增添很大的负担;
  4. 如果是单线程,尽量使用非多线程安全的,因为线程安全来自于同步机制,同步机制会降低性能。例如,单线程程序,能使用HashMap,就不要用HashTable。同理,尽量减少使用synchronized
  5. 用移位符号替代乘除号。eg:a*8应该写作a<<3
  6. 对于经常反复使用的对象使用缓存;
  7. 尽量使用基本类型而不是包装类型,尽量使用一维数组而不是二维数组;
  8. 尽量使用final修饰符,final表示不可修改,访问效率高
  9. 单线程情况下(或者是针对于局部变量),字符串尽量使用StringBuilder,比StringBuffer要快;
  10. String为什么慢?因为String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象。如果不能保证线程安全,尽量使用StringBuffer来连接字符串。这里需要注意的是,StringBuffer的默认缓存容量是16个字符,如果超过16,apend方法调用私有的expandCapacity()方法,来保证足够的缓存容量。因此,如果可以预设StringBuffer的容量,避免append再去扩展容量。如果可以保证线程安全,就是用StringBuilder。
 
 
 
16、ArrayList遍历时正确删除元素?
 
删除元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。
 
17、对一个List的进行subList后,原有list进行增、删、改,再操作subList会怎样?
 
     子 List 的元素和原 List 中的后一部分是重合的, 而子 List 还在遍历过程中时, 向原 List 中新增元素, 这样给子 List 的遍历过程造成了干扰甚至困扰, 于是就抛出了并发修改异常将会抛出java.util.ConcurrentModificationException
 
 
18、web应用安全问题?
    1、跨站脚本攻击(CSS or XSS, Cross Site Scripting) 
     方案:输入或输出时对其进行字符过滤或转义处理。
  2、SQL注入攻击(SQL injection)
方案:输入输出都是过滤、合法性检查和长度限制等通用方法。
  3、远程命令执行(Code execution,个人觉得译成代码执行并不确切) 
     方案:严格限制运行Web服务的用户权限。
  4、目录遍历(Directory traversal) 
    方案:1、同样是限制Web应用在服务器上的运行  2、进行严格的输入验证,控制用户输入非法路径。
  5、文件包含(File inclusion)
    方案:对文件来源进行审查
  6、脚本代码暴露(Script source code disclosure) 
  7、Http请求头的额外的回车换行符注入(CRLF injection/HTTP response splitting)
  8、跨帧脚本攻击(Cross Frame Scripting)
  9、PHP代码注入(PHP code injection)
  10、XPath injection
  11、Cookie篡改(Cookie manipulation)
  12、URL重定向(URL redirection)
  13、Blind SQL/XPath injection for numeric/String inputs
  14、Google Hacking
15、表单、AJAX提交必须执行CSRF安全过滤。
16、URL外部重定向传入的目标地址必须执行白名单过滤。
 
19、简单介绍下spring的ioc和aop?
  •  控制反转(Inversion of Control,英文缩写为IOC);
    ioc就是典型的工厂模式,通过sessionfactory去注入实例。依赖注入    。
  自己实现用什么方式?    反射原理  
其实就是通过解析xml文件,通过反射创建出我们所需要的bean,再将这些bean挨个放到集合中,然后对外提供一个getBean()方法,以便我们获得这bean。
通俗来讲就如同婚姻介绍所,只需要告诉它找个什么样的女朋友,然后婚介就会按照我们的要求,提供一个mm,如果婚介给我们的人选不符合要求,我们就会抛出异常。
 
  •  面向切面编程(Aspect Oriented Programming,英文缩写为AOP)
    AOP就是典型的代理模式的体现。 实现拦截器  日志 统一权限校验 。
    spring的IoC容器是spring的核心,spring AOP是spring框架的重要组成部分。
    
 实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码.简单点解释,比方说你想在你的biz层所有类中都加上一个打印‘你好’的功能,这时就可以用aop思想来做.你先写个类写个类方法,方法经实现打印‘你好’,然后Ioc这个类 ref=“biz.*”让每个类都注入即可实现。
 
20、并发问题 
丢失更新  用户A把6改成2  用户B把2改成6  则用户A丢失了更新
脏读问题  用户A,B 看到的都是6  用户B把6改为2  则用户A读的值仍然是6
 
21、乐观锁  悲观锁
 
悲观  屏蔽一切违反数据操作完整性
乐观  只是在提交的时候检查是否违反数据完整性
 
22、sql优化
    • 复杂sql避免模糊匹配
    • 索引问题 唯一索引  和普通索引
    • 复杂操作
    • 在可以使用UNION ALL的语句里,使用了UNION  
    • 字段长度小于5000用varchar,超过用TEXT,独立一张表,用主键来对应。
    • 在varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索引。
 
24、介绍下使用的持久层框架? 为什么要选择这个(有什么好处)?
MyBatis 是支持普通 SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。
MyBatis 使用简单的 XML或注解用于配置和原始映射,将接口和 Java 的POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
 
25、mybatis  $和#的区别
1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by "111", 如果传入的值是id,则解析成的sql为order by "id". 
        2. $将传入的数据直接显示生成在sql中。如:order by $user_id$,如果传入的值是111,那么解析成sql时的值为order by user_id,  如果传入的值是id,则解析成的sql为order by id. 
        3. #方式能够很大程度防止sql注入。 
        4.$方式无法防止Sql注入。 
        5.$方式一般用于传入数据库对象,例如传入表名.  
        6.一般能用#的就别用$. 
 
 
 
 
26、HashMap和Hashtable有什么区别?
 
     HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。但是,他们有以下不同点:
     HashMap允许键和值是null,而Hashtable不允许键或者值是null。
     Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。
     HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。另一方面,Hashtable提供了对键的列举(Enumeration)。
     一般认为Hashtable是一个遗留的类。
 
27、hashCode()和equals()方法的重要性体现在什么地方?
 
    通过hashCode和equals方法保证元素的唯一性,当重写equals方法时,必须重写hashCode方法,因为如果不重写这两个方法,就会默认使用Object的方法,一般是不相同的,所以就会导致存储了重复值,与hashset、hashmap等性质冲突。
 
 
28、Vector、ArrayList和LinkedList有什么区别?
 
ArrayList和LinkedList都实现了List接口,他们有以下的不同点:
 
ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。
 
相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
 
LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。
 
也可以参考ArrayList vs. LinkedList。 
 
 
29、数据库事务及隔离级别说一下。
  •     数据库事务的几个特性:
    原子性(Atomicity )、一致性( Consistency )、隔离性或独立性( Isolation)和持久性(Durabilily),简称就是ACID。
  •     数据库事务怎么保证一致性?
    数据库进行任何写入操作的时候都是要先写日志的,同样的道理,我们在执行事务的时候数据库首先会记录下这个事务的redo操作日志,然后才开始真正操作数据库,
    在操作之前,首先会把日志文件写入磁盘,那么当突然断电的时候,即使操作没有完成,在重新启动数据库时候,数据库会根据当前数据的情况进行undo回滚或者是redo前滚,
    这样就保证了数据的强一致性。
  •     数据库隔离级别:
    ① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
  ② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
  ③ Read committed (读已提交):可避免脏读的发生。
  ④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。
 
30、synchronized和lock区别,可重入锁与非可重入锁的区别
  •     synchronized和lock区别:
  1. Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
  2. synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
  3. Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
  4. 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
  5. Lock可以提高多个线程进行读操作的效率。
  6. 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
  •     可重入锁与非可重入锁的区别:
     可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁。可重入锁最大的作用是避免死锁。
 
 
31、aop代理模式
AOP 全称 Aspect Oriented Programming,面向切面编程,和 OOP 一样也是一种编程思想。AOP 出现的原因是为了解决 OOP 在处理 侵入性业务上的不足。
代理模式分为静态代理和动态代理两种。
静态代理:通常用于对原有业务逻辑的扩充。创建一个代理类实现和方法相同的方法,通过让代理类持有真实对象,然后在原代码中调用代理类方法,来达到添加我们需要业务逻辑的目的。
动态代理:动态代理底层是使用反射实现的,是在程序运行期间动态的创建接口的实现。
 
32、jdk1.8新特性
1. 速度更快 – 红黑树 
HashMap中的红黑树
HashMap中链长度大于8时采取红黑树的结构存储。
红黑树,除了添加,效率高于链表结构。
 
2. 代码更少 – Lambda 
    • Lambda表达式的基础语法:Java8引入了一个新的操作符“->”,该操作符成为箭头操作符或者Lambda操作符,箭头操作符将Lambda表达式拆分成两部分
    • 左侧:Lambda表达式的参数列表 
    • 右侧:Lambda表达式中所需执行的功能,即Lambda体。
3. 强大的Stream API – Stream 
一系列流水线式的中间操作。
流是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
注意: 
①Stream自己不会存储元素。 
②Stream不会改变源对象。相反,会返回持有新结果的新Stream。 
③Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
 
4. 便于并行 – Parallel 
        在必要的情况下,将一个大任务进行必要的拆分Fork成若干个小任务,再将小任务的运算结果进行Join汇总。
5. 最大化减少空指针异常 – Optional
         是一个容器类,代表一个值存在或不存在,原来用null 表示一个值不存在,现在Optional 可以更好的表达这个概念。并且可以避免空指针异常。
6、ConcurrentHashMap
    • Jdk1.7时隔壁级别CocnurrentLevel(锁分段机制)默认为16。
    • JDK1.8采取了CAS算法
    • Jdk1.8没有永久区,取而代之的是MetaSpace元空间,用的是物理内存。
 
33、java的4种引用 强软弱虚
  • 强引用
        new一个对象,强引用不会被GC回收。
  • 软引用(SoftReference)、
        如果一个对象只具有软引用,那就类似于可有可物的生活用品。
  • 弱引用(WeakReference)
        如果一个对象只具有弱引用,那就类似于可有可物的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。
        在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
  • 虚引用(PhantomReference)
        虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。
        当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中
 
34、分布式服务 解决了哪些问题,自己设计一个分布式框架 会用到哪些技术?
        需要拆分应用进行服务化,以提高开发效率,调优性能,节省关键竞争资源
        当服务越来越多时,服务的URL地址信息就会爆炸式增长,配置管理变得非常困难,F5硬件负载均衡器的单点压力也越来越大。 
        当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。 
        接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器?等等…
            
        用到哪些技术: dubbo(分布式框架), zookeeper(开源的分布式协调服务) ,redis(缓存), ssdb, nsq, nginx(负载均衡), Kafka,hessian ,RPC,netty。
            
35、Zookeeper服务的注册和发现?
        1. init获取Zookeeper的服务注册信息,并缓存在service_repos
        2. get_service_repos方法获取实例变量service_repos
        3. get_service_endpoint根据init构建好的service_repos,以及lb_strategy提供的负载均衡策略返回某个服务的URL地址
        4. update_service_repos通过Zookeeper的Watcher机制来实时更新本地缓存service_repos
        5. heartbeat_monitor是一个心跳检测线程,用来进行服务提供者的健康存活检测,如果出现问题,将该服务提供者从该服务的提供者列表中移除;
        反之,则加入到服务的提供者列表中LoadBalanceStrategy定义了根据服务提供者的信息返回对应的服务Host和IP,即决定由那台主机+端口来提供服务。
 
36、主流的分布式框架?
 dubbo,dubbox, spring-cloudfinaglethrift
 
37、redis的原理 和存储结构   持久化和非持久
 
    Redis存储机制分成两种Snapshot和AOF。无论是那种机制,Redis都是将数据存储在内存中。
    Snapshot工作原理: 是将数据先存储在内存,然后当数据累计达到某些设定的伐值的时候,就会触发一次DUMP操作,将变化的数据一次性写入数据文件(RDB文件)。
    AOF 工作原理: 是将数据也是先存在内存,但是在存储的时候会使用调用fsync来完成对本次写操作的日志记录,这个日志揭露文件其实是一个基于Redis网络交互协议的文本文件。
    
38、类加载过程
JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。
 
1. 虚拟机在首次加载Java类时,会对静态代码块、静态成员变量、静态方法进行一次初始化(静态间按顺序执行)。
2. 只有在调用new方法时才会创建类的实例。
3. 类实例创建过程:父子继承关系,先父类再子类。父类的静态->子类的静态->父类的初始化块->父类的构造方法->子类的初始化块->子类的构造方法
4. 类实例销毁时候:首先销毁子类部分,再销毁父类部分。
 
 
 
39、String,StringBuffer,StringBuilder有什么不同?
40、String和StringBuffer的实现?
41、Volatile关键字作用?除了保证数据可见性,还有其他什么使用方式?  
42、ThreadLocal做什么的?如何使用?源码是如何实现的?get()方法?  
43、ConcurrentHashMap源码?JDK1.6,1.7,1.8中分别有什么不同?
44、分布式程序调用链
全链路性能监控从整体维度到局部维度展示各项指标,将跨应用的所有调用链性能信息集中展现,可方便度量整体和局部性能,并且方便找到故障产生的源头,生产上可极大缩短故障排除时间。
Google Dapper
45、生产环境如何定位内存溢出?CPU使用率过高?Linux命令?
    1、通常来说,分析堆内存快照(Heap Dump)是一个很好的定位手段 ,开启了dump的参数: 
    2、使用Jprofile打开dump文件
    3、发现导致内存溢出的是几个PreparedStateMent,查看其内容,发现时插入业务日志的sql
    4、最终发现是代码for循环层级关系错乱导致的.
46、Netty 
Netty 是一个基于NIO的客户、服务器端编程框架。
Netty是什么?
1)本质:JBoss做的一个Jar包
2)目的:快速开发高性能、高可靠性的网络服务器和客户端程序
3)优点:提供异步的、事件驱动的网络应用程序框架和工具
通俗的说:一个好使的处理Socket的东东
47、kafka 事务 性能
48、内存屏障 
     Java内存模型中volatile变量在写操作之后会插入一个store屏障,在读操作之前会插入一个load屏障。一个类的final字段会在初始化后插入一个store屏障,来确保final字段在构造函数初始化完成并可被使用时可见。
 
49、redis面试题
  • Redis有哪些数据结构?
    字符串String、字典Hash、列表List、集合Set、有序集合SortedSet。如果你是Redis中高级用户,还需要加上下面几种数据结构HyperLogLog、Geo、   Pub/Sub。Redis Module,像BloomFilter,RedisSearch,Redis-ML。
    
  • 使用过Redis分布式锁么,它是什么回事?
    先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。
    
  • 如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?
    这个锁就永远得不到释放了。set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的! 
    
  • 假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?
    使用keys指令可以扫出指定模式的key列表。
    
  •     如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
        redis关键的一个特性:redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。
        这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,
        在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。
        
  • 使用过Redis做异步队列么,你是怎么用的?
    一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。
    
  • 可不可以不用sleep呢?
    list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。
    
  • 能不能生产一次消费多次呢?
    使用pub/sub主题订阅者模式,可以实现1:N的消息队列。
    
  •     pub/sub有什么缺点?
    在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。
    
  • redis如何实现延时队列?
    使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。 
    
  • 如果有大量的key需要设置同一时间过期,一般需要注意什么?
    如果大量的key过期时间设置的过于集中,到过期的那个时间点,redis可能会出现短暂的卡顿现象。
    一般需要在时间上加一个随机值,使得过期时间分散一些。
    
  • Redis如何做持久化的?
    bgsave做镜像全量持久化,aof做增量持久化。
    因为bgsave会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要aof来配合使用。
    在redis实例重启时,会使用bgsave持久化文件重新构建内存,再使用aof重放近期的操作指令来实现完整恢复重启之前的状态。
    
  • 如果突然机器掉电会怎样?
    取决于aof日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。
    但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。
    
  • bgsave的原理是什么?
    fork和cow。fork是指redis通过创建子进程来进行bgsave操作,cow指的是copy on write,
    子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。
    
  • Pipeline有什么好处,为什么要用pipeline?
    可以将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。
    使用redis-benchmark进行压测的时候可以发现影响redis的QPS峰值的一个重要因素是pipeline批次指令的数目。
    
  • Redis的同步机制了解么?
    Redis可以使用主从同步,从从同步。
    第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,
    复制节点接受完成后将rdb镜像加载到内存。
    加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。
    
  • 是否使用过Redis集群,集群的原理是什么?
    Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。
    Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。
    
    
    
50、动态代理有几种,Jdk与Cglib区别
 
51、数据库三大范式
 
52、左连接和右连接说一下,内连接呢
53、数据库索引有几种
54、数据库引擎你认识几种,innodb 和myisam 区别,你的项目用到哪个引擎
55、若hashcode方法永远返回1会产生什么结果
    所有对象都出现hash冲突,而hashCode()本身的性能也会降级。
56、Error与RuntimeException的区别
二者的不同之处:
Exception:
1.可以是可被控制(checked) 或不可控制的(unchecked) 
2.表示一个由程序员导致的错误 
3.应该在应用程序级被处理
 
Error:
1.总是不可控制的(unchecked) 
2.经常用来用于表示系统错误或低层资源的错误 
3.如何可能的话,应该在系统级被捕捉
 
57、引用计数法与GC Root可达性分析法区别
58、双亲委派机制说一下
某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
59、算法题:找出一个数组中第100个小的数字(堆思想解决)  
60、看你项目用到策略模式和工厂模式,说一下区别
61、模板方法模式
定义一个模板结构,将具体内容延迟到子类去实现。
解决的问题
  • 提高代码复用性 
将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中
  • 实现了反向控制 
通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 & 符合“开闭原则”
 
62、开闭原则懂吗,说一下
它是Java世界里最基础的设计原则,它指导我们如何建立一个稳定的、灵活的系统。开闭原则的定义是:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是,对于修改是封闭的。
 
63、NIO说一下你的理解
64、AtomicInteger底层原理
AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减。
int value使用了volatile关键字,volatile在这里可以做到的作用是使得多个线程可以共享变量
65、CAS机制是什么?有什么缺点,会出现什么问题?
CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
CAS的缺点:
1.CPU开销较大
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
2.不能保证代码块的原子性
CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。
3.ABA问题
这是CAS机制最大的问题所在。
  • 什么是ABA问题?
 引用原书的话:如果在算法中的节点可以被循环使用,那么在使用“比较并交换”指令就可能出现这种问题,在CAS操作中将判断“V的值是否仍然为A?”,并且如果是的话就继续执行更新操作,在某些算法中,如果V的值首先由A变为B,再由B变为A,那么CAS将会操作成功。
  • 怎么避免ABA问题?
Java中提供了AtomicStampedReference和AtomicMarkableReference来解决ABA问题。 
 
66、本地缓存过期策略怎么设置,一致性怎么保证?

猜你喜欢

转载自www.cnblogs.com/ws563573095/p/10117924.html