前言:java
分享 Java高级工程师面试阿里,阿里云,天猫,菜鸟,涉及到的知识点,文章有点长,但比较全面,阅读时间15分钟左右,干货满满。面试
1.一、HashMap的实现原理redis
1.1.一、结构算法
HashMap其实是一个“链表散列”的数据结构,即数组和链表的结合体,HashMap底层就是一个数组结构,数组中的每一项又是一个链表。以下图所示:spring
image.pngsql
当新建一个HashMap的时候,就会初始化一个数组。哈希表是由数组+链表组成的,一个长度为16的数组中,每一个元素存储的是一个链表的头结点。这些元素通常状况是经过hash(key)%len的规则存储到数组中,也就是元素的key的哈希值对数组长度取模获得。数据库
1.1.二、核心变量编程
image.png数组
1.1.三、put存储逻辑浏览器
当咱们往HashMap中put元素的时候,先根据key的hashCode从新计算hash值,根据hash值获得这个元素在数组中的位置(即下标), 若是数组该位置上已经存放有其余元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最早加入的放在链尾。若是数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
这里有一个特殊的地方。在JDK1.6中,HashMap采用位桶+链表实现,即便用链表处理冲突,同一hash值的链表都存储在一个链表里。可是当位于一个桶中的元素较多,即hash值相等的元素较多时,经过key值依次查找的效率较低。而JDK1.8中,HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减小了查找时间。
红黑树
红黑树和平衡二叉树(AVL树)相似,都是在进行插入和删除操做时经过特定操做保持二叉查找树的平衡,从而得到较高的查找性能。
红黑树和AVL树的区别在于它使用颜色来标识结点的高度,它所追求的是局部平衡而不是AVL树中的很是严格的平衡。
1.1.四、get读取逻辑
从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,而后经过key的equals方法在对应位置的链表中找到须要的元素。
若是第一个节点是TreeNode,说明采用的是数组+红黑树结构处理冲突,遍历红黑树,获得节点值。
1.1.五、概括
简单地说,HashMap 在底层将 key-value 当成一个总体进行处理,这个总体就是一个 Node 对象。HashMap 底层采用一个 Node<K,V>[] 数组来保存全部的 key-value 对,当须要存储一个 Node 对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当须要取出一个Entry时,也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Node。
1.1.六、HashMap的resize(rehash)
当HashMap中的元素愈来愈多的时候,hash冲突的概率也就愈来愈高,由于数组的长度是固定的。因此为了提升查询的效率,就要对HashMap的数组进行扩容,在对HashMap数组进行扩容时,就会出现性能问题:原数组中的数据必须从新计算其在新数组中的位置,并放进去,这就是resize。
那么HashMap何时进行扩容呢?当HashMap中的元素个数超过数组大小loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认状况下,数组大小为16,那么当HashMap中元素个数超过160.75=12的时候,就把数组的大小扩展为 216=32,即扩大一倍,而后从新计算每一个元素在数组中的位置,而这是一个很是消耗性能的操做,因此若是咱们已经预知HashMap中元素的个数,那么预设元素的个数可以有效的提升HashMap的性能。
1.二、HashMap在并发场景下的问题和解决方案
1.2.一、多线程put后可能致使get死循环
问题缘由就是HashMap是非线程安全的,多个线程put的时候形成了某个key值Entry key List的死循环,问题就这么产生了。
当另一个线程get 这个Entry List 死循环的key的时候,这个get也会一直执行。最后结果是愈来愈多的线程死循环,最后致使服务器dang掉。咱们通常认为HashMap重复插入某个值的时候,会覆盖以前的值,这个没错。可是对于多线程访问的时候,因为其内部实现机制(在多线程环境且未做同步的状况下,对同一个HashMap作put操做可能致使两个或以上线程同时作rehash动做,就可能致使循环键表出现,一旦出现线程将没法终止,持续占用CPU,致使CPU使用率居高不下),就可能出现安全问题了。
为HashMap以链表组形式存在,初始数组16位(为什么16位,又是一堆移位算法,下一篇文章再写吧),若是长度超过75%,长度增长一倍,多线程操做的时候,恰巧两个线程插入的时候都须要扩容,造成了两个链表,这时候读取,size不同,报错了。其实这时候报错都是好事,至少不会陷入死循环让cpu死了,有种状况,假如两个线程在读,还有个线程在写,恰巧扩容了,这时候你死都不知道咋死的,直接就是死循环,假如你是双核cpu,cpu占用率就是50%,两个线程恰巧都进入死循环了,得!中奖了。
1.2.二、多线程put的时候可能致使元素丢失
主要问题出在addEntry方法的new Entry (hash, key, value, e),若是两个线程都同时取得了e,则他们下一个元素都是e,而后赋值给table元素的时候有一个成功有一个丢失。
1.2.三、解决方案
ConcurrentHashMap替换HashMap
Collections.synchronizedMap将HashMap包装起来
1.三、ConcurrentHashMap PK HashTable
ConcurrentHashMap具体是怎么实现线程安全的呢,确定不多是每一个方法加synchronized,那样就变成了HashTable。
从ConcurrentHashMap代码中能够看出,它引入了一个“分段锁”的概念,具体能够理解为把一个大的Map拆分红N个小的HashTable,根据key.hashCode()来决定把key放到哪一个HashTable中。
以空间换时间的结构,跟分布式缓存结构有点像,建立的时候,内存直接分为了16个segment,每一个segment实际上仍是存储的哈希表(Segment其实就是一个HashMap ),写入的时候,先找到对应的segment,而后锁这个segment,写完,解锁,锁segment的时候,其余segment还能够继续工做。
ConcurrentHashMap如此的设计,优点主要在于: 每一个segment的读写是高度自治的,segment之间互不影响。这称之为“锁分段技术”;
2.一、线程的各个状态及切换
Java中的线程的生命周期大致可分为5种状态:新建、可运行、运行、阻塞、死亡。
一、新建(NEW):新建立了一个线程对象。
二、可运行(RUNNABLE):线程对象建立后,其余线程(好比main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
三、运行(RUNNING):可运行状态(runnable)的线程得到了cpu 时间片(timeslice) ,执行程序代码。
四、阻塞(BLOCKED):阻塞状态是指线程由于某种缘由放弃了cpu 使用权,也即让出了cpu timeslice,暂时中止运行。直到线程进入可运行(runnable)状态,才有机会再次得到cpu timeslice 转到运行(running)状态。
阻塞的状况分三种:
1)等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
2)同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
3)其余阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程从新转入可运行(runnable)状态。
五、死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
几个方法的比较:
1)Thread.sleep(long millis),必定是当前线程调用此方法,当前线程进入阻塞,但不释放对象锁,millis后线程自动苏醒进入可运行状态。做用:给其它线程执行机会的最佳方式。
2)Thread.yield(),必定是当前线程调用此方法,当前线程放弃获取的cpu时间片,由运行状态变会可运行状态,让OS再次选择线程。做用:让相同优先级的线程轮流执行,但并不保证必定会轮流执行。实际中没法保证yield()达到让步目的,由于让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会致使阻塞。
3)t.join()/t.join(long millis),当前线程里调用其它线程1的join方法,当前线程阻塞,但不释放对象锁,直到线程1执行完毕或者millis时间到,当前线程进入可运行状态。
4)obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒。
5)obj.notify(),唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的全部线程。
2.二、多线程的实现方式Thread、Runnable、Callable
继承Thread类,实现Runnable接口,实现Callable接口。
这三种方法的介绍和比较:
一、实现Runnable接口相比继承Thread类有以下优点:
1)能够避免因为Java的单继承特性而带来的局限
2)加强程序的健壮性,代码可以被多个线程共享,代码与数据是独立的
3)适合多个相同程序代码的线程去处理同一资源的状况
二、实现Runnable接口和实现Callable接口的区别
1)Runnable是自从java1.1就有了,而Callable是1.5以后才加上去的
2)实现Callable接口的任务线程能返回执行结果,而实现Runnable接口的任务线程不能返回结果
3)Callable接口的call()方法容许抛出异常,而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛
4)加入线程池运行,Runnable使用ExecutorService的execute方法,Callable使用submit方法
注:Callable接口支持返回执行结果,此时须要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取返回结果,当不调用此方法时,主线程不会阻塞
2.三、线程池原理和运行机制
java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类。
在ThreadPoolExecutor类中提供了四个构造方法,主要参数包括下面的参数:
一、int corePoolSize:核心池的大小。
线程池的基本大小,即在没有任务须要执行的时候线程池的大小,而且只有在工做队列满了的状况下才会建立超出这个数量的线程。这里须要注意的是:在刚刚建立ThreadPoolExecutor的时候,线程并不会当即启动,而是要等到有任务提交时才会启动,除非调用了prestartCoreThread/prestartAllCoreThreads事先启动核心线程。再考虑到keepAliveTime和allowCoreThreadTimeOut超时参数的影响,因此没有任务须要执行的时候,线程池的大小不必定是corePoolSize。
二、int maximumPoolSize:线程池最大线程数,它表示在线程池中最多能建立多少个线程,注意与corePoolSize区分。
线程池中容许的最大线程数,线程池中的当前线程数目不会超过该值。若是队列中任务已满,而且当前线程个数小于maximumPoolSize,那么会建立新的线程来执行任务。
三、long keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。
四、TimeUnit unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性。
五、BlockingQueue<Runnable> workQueue:一个阻塞队列,用来存储等待执行的任务。
六、ThreadFactory threadFactory:线程工厂,主要用来建立线程。
七、RejectedExecutionHandler handler:表示当拒绝处理任务时的策略。
还有一个成员变量比较重要:poolSize
线程池中当前线程的数量,当该值为0的时候,意味着没有任何线程,线程池会终止。同一时刻,poolSize不会超过maximumPoolSize。
2.四、线程池对任务的处理
一、若是当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会建立一个线程去执行这个任务;
二、若是当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(通常来讲是任务缓存队列已满),则会尝试建立新的线程去执行这个任务(maximumPoolSize);
三、若是当前线程池中的线程数目达到maximumPoolSize(此时线程池的任务缓存队列已满),则会采起任务拒绝策略进行处理;
任务拒绝策略,一般有如下四种策略:
1)ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
2)ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,可是不抛出异常。
3)ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,而后从新尝试执行任务(重复此过程)
4)ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
四、若是线程池中的线程数量大于 corePoolSize时,若是某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;若是容许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。
2.五、线程池的状态
一、线程池的状态说明:
RUNNING(运行):接受新任务并处理排队任务。
SHUTDOWN(关闭):不接受新任务,会处理队列任务。
STOP(中止):不接受新任务,不处理队列任务,而且中断进程中的任务。
TIDYING(整理):全部任务都已终止,工做计数为零,线程将执行terminated()方法终止线程。
TERMINATED(已终止):terminated()方法已完成。
二、各个状态之间的转换
RUNNING -> SHUTDOWN:调用shutdown()方法。
RUNNING or SHUTDOWN-> STOP:调用shutdownNow()方法。
SHUTDOWN -> TIDYING:当队列和池均都为空时。
STOP -> TIDYING:当池为空时。
TIDYING -> TERMINATED:当terminated()方法已完成。
3.一、JVM的结构
每一个JVM都包含:方法区、Java堆、Java栈、本地方法栈、程序计数器等。
image.png
一、方法区:共享
各个线程共享的区域,存放类信息、常量、静态变量。
二、Java堆:共享
也是线程共享的区域,咱们的类的实例就放在这个区域,能够想象你的一个系统会产生不少实例,所以Java堆的空间也是最大的。若是Java堆空间不足了,程序会抛出OutOfMemoryError异常。
三、Java栈:私有
每一个线程私有的区域,它的生命周期与线程相同,一个线程对应一个Java栈,每执行一个方法就会往栈中压入一个元素,这个元素叫“栈帧”,而栈帧中包括了方法中的局部变量、用于存放中间状态值的操做栈。若是Java栈空间不足了,程序会抛出StackOverflowError异常,想想什么状况下会容易产生这个错误,对,递归,递归若是深度很深,就会执行大量的方法,方法越多Java栈的占用空间越大。
四、本地方法栈:私有
角色和Java栈相似只不过它是用来表示执行本地方法的,本地方法栈存放的方法调用本地方法接口,最终调用本地方法库,实现与操做系统、硬件交互的目的。
五、程序计数器:私有
说到这里咱们的类已经加载了,实例对象、方法、静态变量都去了本身该去的地方,那么问题来了,程序该怎么执行,哪一个方法先执行,哪一个方法后执行,这些指令执行的顺序就是程序计数器在管,它的做用就是控制程序指令的执行顺序。
六、执行引擎固然就是根据PC寄存器调配的指令顺序,依次执行程序指令。
3.二、Java堆的介绍及典型的垃圾回收算法介绍
3.2.一、Java堆的介绍
Java堆是虚拟机管理的最大的一块内存,堆上的全部线程共享一块内存区域,在启动虚拟机时建立。此内存惟一目的就是存放对象实例,几乎全部对象实例都在这里分配,这一点Java虚拟机规范中的描述是:全部对象实例及数组都要在堆上分配。
Java堆是垃圾收集器管理的主要区域,也被称为“GC堆”,因为如今收集器基本都采用分代收集算法,因此Java堆中还能够分为:新生代和老年代。
堆的内存模型大体为:
image.png
默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值能够经过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小,老年代 ( Old ) = 2/3 的堆空间大小。
其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to以示区分。 默认的,Edem : from : to = 8 : 1 : 1 ( 能够经过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,因此不管何时,老是有一块 Survivor 区域是空闲着的。 所以,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。
新生代是 GC 收集垃圾的频繁区域。
当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,在通过一次 Minor GC 后,若是对象还存活,而且可以被另一块 Survivor 区域所容纳 ( 上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另一块 Survivor 区域 ( 即 to 区域 ) 中,而后清理所使用过的 Eden 以及 Survivor 区域 ( 即 from 区域 ),而且将这些对象的年龄设置为1,之后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,能够经过参数 -XX:MaxTenuringThreshold 来设定 ),这些对象就会成为老年代。
但这也不是必定的,对于一些较大的对象 ( 即须要分配一块较大的连续内存空间 ) 则是直接进入到老年代。
From Survivor区域与To Survivor区域是交替切换空间,在同一时间内二者中只有一个不为空。
3.2.二、如何肯定某个对象是可回收的(垃圾)
一、引用计数法
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加1;当引用失效时,计数器值就减1;任什么时候刻计数器都为0的对象就是不可能再被使用的。
这种方式的问题是没法解决循环引用的问题,当两个对象循环引用时,就算把两个对象都设置为null,由于他们的引用计数都不为0,这就会使他们永远不会被清除。
二、根搜索算法(可达性分析)
为了解决引用计数法的循环引用问题,Java使用了可达性分析的方法。经过一系列的“GC roots”对象做为起点搜索。若是在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要通过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。
比较常见的将对象视为可回收对象的缘由:
显式地将对象的惟一强引用指向新的对象。
显式地将对象的惟一强引用赋值为Null。
局部引用所指向的对象(如,方法内对象)。
只有弱引用与其关联的对象。
1)强引用(StrongReference)
强引用是使用最广泛的引用。若是一个对象具备强引用,那垃圾回收器毫不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具备强引用的对象来解决内存不足的问题。 ps:强引用其实也就是咱们平时A a = new A()这个意思。
2)软引用(SoftReference)
若是一个对象只具备软引用,则内存空间足够,垃圾回收器就不会回收它;若是内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就能够被程序使用。软引用可用来实现内存敏感的高速缓存(下文给出示例)。
软引用能够和一个引用队列(ReferenceQueue)联合使用,若是软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
3)弱引用(WeakReference)
弱引用与软引用的区别在于:只具备弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程当中,一旦发现了只具备弱引用的对象,无论当前内存空间足够与否,都会回收它的内存。不过,因为垃圾回收器是一个优先级很低的线程,所以不必定会很快发现那些只具备弱引用的对象。
弱引用能够和一个引用队列(ReferenceQueue)联合使用,若是弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
4)虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其余几种引用都不一样,虚引用并不会决定对象的生命周期。若是一个对象仅持有虚引用,那么它就和没有任何引用同样,在任什么时候候均可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,若是发现它还有虚引用,就会在回收对象的内存以前,把这个虚引用加入到与之 关联的引用队列中。
3.2.三、典型的垃圾回收算法介绍
一、标记-清除算法(Mark-Sweep)
最基础的垃圾回收算法,分为“标注”和“清除”两个阶段:首先标记出全部须要回收的对象,在标记完成后统一回收掉全部被标记的对象。
标记过程:为了可以区分对象是live的,能够为每一个对象添加一个marked字段,该字段在对象建立的时候,默认值是false。
清除过程:去遍历堆中全部对象,并找出未被mark的对象,进行回收。与此同时,那些被mark过的对象的marked字段的值会被从新设置为false,以便下次的垃圾回收。
缺点:效率低,空间问题(产生大量不连续的内存碎片),后续可能发生大对象不能找到可利用空间的问题。
image.png
二、复制算法(Copying)——新生代的收集算法就是这种,可是比例不是1:1,而是(8+1):1
为了解决Mark-Sweep算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为大小相等的两块,每次只使用其中一块。当这一块内存满后将尚存活的对象复制到另外一块上去,把已使用的内存空间一次清理掉。这种算法虽然实现简单,内存效率高,不易产生碎片,可是最大的问题是可用内存被压缩到了本来的一半。且存活对象增多的话,Copying算法的效率会大大下降。
image.png
三、标记-整理算法(Mark-Compact)——老年代的收集算法
结合了以上两个算法,标记阶段和Mark-Sweep算法相同,标记后不是清理对象,而是将全部存活对象移向内存的一端,而后清除端边界外的对象。如图:
image.png
四、分代收集算法(Generational Collection)
分代收集法是目前大部分JVM所采用的方法,其核心思想是根据对象存活的不一样生命周期将内存划分为不一样的域,通常状况下将GC堆划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。
老生代的特色是每次垃圾回收时只有少许对象须要被回收,新生代的特色是每次垃圾回收时都有大量垃圾须要被回收,所以能够根据不一样区域选择不一样的算法。
目前大部分JVM的GC对于新生代都采起复制算法(Copying),由于新生代中每次垃圾回收都要回收大部分对象,即要复制的操做比较少,但一般并非按照1:1来划分新生代。通常将新生代划分为一块较大的Eden空间和两个较小的Survivor空间(From Space, To Space),每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将该两块空间中还存活的对象复制到另外一块Survivor空间中。
老年代中的对象存活率高,没有额外空间对它进行分配,使用“标记-清理”或“标记-整理”算法来进行回收。
3.三、JVM处理FULLGC经验
3.3.一、内存泄漏
一、产生缘由
1)JVM内存太小。
2)程序不严密,产生了过多的垃圾。
二、通常状况下,在程序上的体现为:
1)内存中加载的数据量过于庞大,如一次从数据库取出过多数据。
2)集合类中有对对象的引用,使用完后未清空,使得JVM不能回收。
3)代码中存在死循环或循环产生过多重复的对象实体。
4)使用的第三方软件中的BUG。
5)启动参数内存值设定的太小。
3.3.二、Java内存泄漏的排查案例
一、肯定频繁Full GC现象,找出进程惟一ID
使用jps(jps -l)或ps(ps aux | grep tomat)找出这个进程在本地虚拟机的惟一ID(LVMID,Local Virtual Machine Identifier)
二、再使用“虚拟机统计信息监视工具:jstat”(jstat -gcutil 20954 1000)查看已使用空间站总空间的百分比,能够看到FGC的频次。
image.png
三、找出致使频繁Full GC的缘由,找出出现问题的对象
分析方法一般有两种:
1)把堆dump下来再用MAT等工具进行分析,但dump堆要花较长的时间,而且文件巨大,再从服务器上拖回本地导入工具,这个过程有些折腾,不到万不得已最好别这么干。
2)更轻量级的在线分析,使用“Java内存影像工具:jmap”生成堆转储快照(通常称为headdump或dump文件)。
jmap命令格式:jmap -histo:live 20954
四、一个工具:BTrace,没有使用过
4.一、找出慢SQL的方法
4.1.一、开启慢查询日志
4.1.二、MySQL基准测试方法
4.二、索引的优缺点及实现原理
4.2.一、索引的优缺点
4.2.二、MySQL索引的实现原理
4.三、对于一个SQL的性能优化过程
4.四、MySQL数据库的分库分表方式以及带来的问题处理
4.五、MySQL主从复制数据一致性问题处理
5.一、HTTP请求报文和响应报文
image.png
5.二、HTTPS为何是安全的?HTTPS的加密方式有哪些?
5.2.一、HTTPS的工做原理说明HTTPS是安全的
image.png
客户端在使用HTTPS方式与Web服务器通讯时有如下几个步骤,如图所示。
一、客户使用https的URL访问Web服务器,要求与Web服务器创建SSL链接。
二、Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
三、客户端的浏览器与Web服务器开始协商SSL链接的安全等级,也就是信息加密的等级。
四、客户端的浏览器根据双方赞成的安全等级,创建会话密钥,而后利用网站的公钥将会话密钥加密,并传送给网站。
五、Web服务器利用本身的私钥解密出会话密钥。
六、Web服务器利用会话密钥加密与客户端之间的通讯。
image.png
5.2.二、HTTPS的加密方式有哪些?
一、对称加密
对称加密是指加密和解密使用相同密钥的加密算法。它要求发送方和接收方在安全通讯以前,商定一个密钥。对称算法的安全性依赖于密钥,泄漏密钥就意味着任何人均可以对他们发送或接收的消息解密,因此密钥的保密性对通讯相当重要。
二、更多的加密方式的了解
5.三、TCP三次握手协议,四次挥手
第一次握手:主机A发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,主机B由SYN=1知道,A要求创建联机;
第二次握手:主机B收到请求后要确认联机信息,向A发送ack number=(主机A的seq+1),syn=1,ack=1,随机产生seq=7654321的包;
第三次握手:主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,主机A会再发送ack number=(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则链接创建成功。
完成三次握手,主机A与主机B开始传送数据。
5.四、OAuth协议介绍
image.png
5.五、防盗链Referer
Referer请求头: 表明当前访问时从哪一个网页链接过来的。
当Referer未存在或者是从其余站点访问咱们资源的时候就直接重定向到咱们的主页,这样既能够防止咱们的资源被窃取。
6.一、AOP的实现原理
spring框架对于这种编程思想的实现基于两种动态代理模式,分别是JDK动态代理 及 CGLIB的动态代理,这两种动态代理的区别是 JDK动态代理须要目标对象实现接口,而 CGLIB的动态代理则不须要。下面咱们经过一个实例来实现动态代理,进而帮助咱们理解面向切面编程。
JDK的动态代理要使用到一个类 Proxy 用于建立动态代理的对象,一个接口 InvocationHandler用于监听代理对象的行为,其实动态代理的本质就是对代理对象行为的监听。
6.二、Spring MVC工做原理
Spring的MVC框架主要由DispatcherServlet、处理器映射、处理器(控制器)、视图解析器、视图组成。
6.2.一、SpringMVC原理图
image.png
6.2.二、SpringMVC运行原理
一、客户端请求提交到DispatcherServlet
二、由DispatcherServlet控制器查询一个或多个HandlerMapping,找处处理请求的Controller
三、DispatcherServlet将请求提交到Controller
四、Controller调用业务逻辑处理后,返回ModelAndView
五、DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图
六、视图负责将结果显示到客户端
6.2.三、SpringMVC核心组件
一、DispatcherServlet:中央控制器,把请求给转发到具体的控制类
二、Controller:具体处理请求的控制器
三、HandlerMapping:映射处理器,负责映射中央处理器转发给controller时的映射策略
四、ModelAndView:服务层返回的数据和视图层的封装类
五、ViewResolver:视图解析器,解析具体的视图
六、Interceptors :拦截器,负责拦截咱们定义的请求而后作处理工做
6.2.四、Servlet 生命周期
Servlet 生命周期可被定义为从建立直到毁灭的整个过程。如下是 Servlet 遵循的过程:
一、Servlet 经过调用 init () 方法进行初始化。
二、Servlet 调用 service() 方法来处理客户端的请求。
三、Servlet 经过调用 destroy() 方法终止(结束)。
四、最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。
6.2.五、Spring容器初始化过程
Spring 启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,而后根据这张注册表实例化Bean,装配号Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。
image.png
7.一、分布式下如何保证事务一致性
分布式事务,常见的两个处理办法就是两段式提交和补偿。
7.1.一、两段式提交
分布式事务将提交分红两个阶段:
prepare;
commit/rollback
在分布式系统中,每一个节点虽然能够知晓本身的操做是成功或者失败,却没法知道其余节点的操做的成功或失败。当一个事务跨越多个节点时,为了保持事务的ACID特性,须要引入一个做为协调者的组件来统一掌控全部节点(参与者)的操做结果并最终指示这些节点是否须要把操做结果进行真正的提交。算法步骤以下:
第一阶段:
一、协调者会问全部的参与者,是否能够执行提交操做。
二、各个参与者开始事务执行的准备工做,如:为资源上锁,预留资源。
三、参与者响应协调者,若是事务的准备工做成功,则回应“能够提交”,不然回应“拒绝提交”。
第二阶段:
一、若是全部的参与者都回应“能够提交”。那么协调者向全部的参与者发送“正式提交”的命令。参与者完成正式提交并释放全部资源,而后回应“完成”,协调者收集各节点的“完成”回应后结束这个Global Transaction
二、若是有一个参与者回应“拒绝提交”,那么协调者向全部的参与者发送“回滚操做”,并释放全部资源,而后回应“回滚完成”,协调者收集各节点的“回滚”回应后,取消这个Global Transaction。
7.1.二、三段式提交
三段提交的核心理念是:在询问的时候并不锁定资源,除非全部人都赞成了,才开始锁资源。他把二段提交的第一个段break成了两段:询问,而后再锁资源。最后真正提交。
7.1.二、事务补偿,最终一致性
补偿比较好理解,先处理业务,而后定时或者回调里,检查状态是否是一致的,若是不一致采用某个策略,强制状态到某个结束状态(通常是失败状态)。
8.一、Linux日志分析经常使用命令
8.1.一、查看文件内容
cat
-n 显示行号
8.1.二、分页显示
more
Enter 显示下一行
空格 显示下一页
F 显示下一屏
B 显示上一屏
less
/get 查询"get"字符串并高亮显示
8.1.三、显示文件尾
tail
-f 不退出持续显示
-n 显示文件最后n行
8.1.四、显示头文件
head
-n 显示文件开始n行
8.1.五、内容排序
sort
-n 按照数字排序
-r 按照逆序排序
-k 表示排序列
-t 指定分隔符
8.1.六、字符统计
wc
-l 统计文件中行数
-c 统计文件字节数
-L 查看最长行长度
-w 查看文件包含多少个单词
8.1.七、查看重复出现的行
uniq
-c 查看该行内容出现的次数
-u 只显示出现一次的行
-d 只显示重复出现的行
8.1.八、字符串查找
grep
8.1.九、文件查找
find
which
whereis
8.1.十、表达式求值
expr
8.1.十一、归档文件
tar
zip
unzip
8.1.十二、URL访问工具
curl
wget
8.1.1三、查看请求访问量
页面访问排名前十的IP
cat access.log | cut -f1 -d " " | sort | uniq -c | sort -k 1 -r | head -10
页面访问排名前十的URL
cat access.log | cut -f4 -d " " | sort | uniq -c | sort -k 1 -r | head -10
查看最耗时的页面
cat access.log | sort -k 2 -n -r | head 10
9.一、kafka消息队列
一、避免数据丢失
producer:
加大重试次数
同步发送
对于单条数据过大,要设置可接收的单条数据的大小
对于异步发送,经过回调函数来感知丢消息
block.on.buffer.full = true
consumer:
enable.auto.commit=false 关闭自动提交位移
二、避免消息乱序
假设a,b两条消息,a先发送后因为发送失败重试,这时顺序就会在b的消息后面,能够设置max.in.flight.requests.per.connection=1来避免。
max.in.flight.requests.per.connection:限制客户端在单个链接上可以发送的未响应请求的个数,设置此值是1表示kafka broker在响应请求以前client不能再向同一个broker发送请求,但吞吐量会降低。
三、避免消息重复
使用第三方redis的set
9.二、ZooKeeper的原理
9.三、SOA相关,RPC两种实现方式:基于HTTP和基于TCP
9.四、Netty
image.png
9.五、Dubbo
10.一、系统作了哪些安全防御
一、XSS(跨站脚本攻击)
全称是跨站脚本攻击(Cross Site Scripting),指攻击者在网页中嵌入恶意脚本程序。
XSS防范:
XSS之因此会发生,是由于用户输入的数据变成了代码。所以,咱们须要对用户输入的数据进行HTML转义处理,将其中的“尖括号”、“单引号”、“引号”之类的特殊字符进行转义编码。
二、CSRF(跨站请求伪造)
攻击者盗用了你的身份,以你的名义向第三方网站发送恶意请求。
CSRF的防护:
1)尽可能使用POST,限制GET
2)将cookie设置为HttpOnly
3)增长token
4)经过Referer识别
三、SQL注入
使用预编译语句(PreparedStatement),这样的话即便咱们使用sql语句伪形成参数,到了服务端的时候,这个伪造sql语句的参数也只是简单的字符,并不能起到攻击的做用。
作最坏的打算,即便被’拖库‘('脱裤,数据库泄露')。数据库中密码不该明文存储的,能够对密码使用md5进行加密,为了加大破解成本,因此能够采用加盐的(数据库存储用户名,盐(随机字符长),md5后的密文)方式。
四、DDOS
最直接的方法增长带宽。可是攻击者用各地的电脑进行攻击,他的带宽不会耗费不少钱,但对于服务器来讲,带宽很是昂贵。
云服务提供商有本身的一套完整DDoS解决方案,而且能提供丰富的带宽资源
那如何学习才能快速入门并精通呢?
在此给你们推荐一个Java架构方面的交流学习群:698581634,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系,主要针对Java开发人员提高本身,突破瓶颈,相信你来学习,会有提高和收获。在这个群里会有你须要的内容 朋友们请抓紧时间加入进来吧。