Java 面试必备

  1. Object有哪些方法?
    clone,实现对象的浅复制。
    getClass,final方法,获得运行时类型。
    toString,该方法用得比较多,一般子类都有覆盖。
    finalize,该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。
    equals,该方法是非常重要的一个方法。一般equals和==是不一样的,但是在Object中两者是一样的。子类一般都要重写这个方法。
    hashCode,该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。一般必须满足obj1.equals(obj2)==true。可以推出obj1.hash- Code()==obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。
    wait,wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
    notify,该方法唤醒在该对象上等待的某个线程。
    notifyAll,该方法唤醒在该对象上等待的所有线程。

  2. 类的初始化顺序?
    Static Field Initial (静态变量)
    Static Patch Initial (静态初始化块)
    Field Initial (变量)
    Field Patch Initial (初始化块)
    Structure Initial (构造器)

  3. Map有哪些实现类?
    HashMap无序的
    LinkedHashMap 有序的
    TreeMap 默认升序。

  4. HashMap的工作原理
    HashMap使用数组和链表的形式存储数据。
    HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。

  5. HashMap、HashTable的区别?
    HashMap是HashTable的轻量级实现(非线程安全的实现),他们都完成了Map接口。主要的区别有:线程安全性,同步(synchronization),以及速度。

  6. 放入HashMap中的类需重写哪些方法?
    使用HashMap,如果key是自定义的类,就必须重写hashcode()和equals()。

  7. ConcurrentHashMap和HashTable的区别?
    HashTable(同一把锁):使用synchronized来保证线程安全,但效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用put添加元素,另一个线程不能使用put添加元素,也不能使用get,竞争会越来越激烈效率越低。
    ConcurrentHashMap(分段锁):(锁分段技术)每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。
    首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。ConcurrentHashMap是由Segment数组结构和HahEntry数组结构组成。Segment是一种可重入锁ReentrantLock,扮演锁的角色。HashEntry用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组。Segment的结构和Hashmap类似,是一种数组和链表结构,一个Segment包含一个HashEntry数组,每个HashEntry是一个链表结构的元素,每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得对应的Segment。

  8. ConcurrentHashMap的并发度是什么?
    ConcurrentHashMap的并发度就是Segment的大小,默认为16。

  9. HashMap、HashTable、ConcurrentHashMap的区别?
    HashMap 和 HashTable的父类不同,HashMap是AbstMap,HashTable是Dictionary类。
    HashMap的初始容量16,HashTable初始容量11。
    HashMap扩容机制必须是2的倍数,HashTable不需要。
    线程安全性,HashMap线程不安全,HashTable线程安全。
    HashMap允许null ,HashTable不允许null。
    遍历方式也不同HashMap使用Iterator,HashTableEnumeration
    计算hash值的方法不同 ConcurrentHashMap也是线程安全的HashMap实现的一种;使用cas操作来修改以保证数据的原子性。

  10. TCP、UDP的区别?
    TCP是面向连接的,UDP是面向报文的(即发送的时候不需要建立连接)。
    TCP是提供可靠服务的,请求有序,UDP尽最大努力交付,不保证服务可靠。
    TCP能有拥塞控制,UDP没有拥塞控制。
    TCP是点对点,UDP可以1对1,1对多,多对1通信。
    TCP首部开销20字节,UDP只有8。

  11. POST、GET的区别?
    GET数据传输是,只允许ASCII,参数位于URL上,URL最大长度限制2k,重复提交无害,可为书签,可以被缓存,所以安全性差。
    POST允许二进制数据使用多重编码,参数位于请求体重,没有最大长度限制,提交有害,不可为书签也不能被缓存,安全性较高。
    POST两次TCP请求,GET一次TCP请求。

  12. 如何高效安全地删除一个List中的一个元素?
    循环删除List中多个元素的,应该使用迭代器Iterator方式。

  13. 字节流和字符流的区别和联系?
    字节流在操作的时候本身是不会用到缓冲区(内存)的,是与文件本身直接操作的,而字符流在操作的时候是使用到缓冲区的。字节流在操作文件时,即使不关闭资源(close方法),文件也能输出,但是如果字符流不使用close方法的话,则不会输出任何内容,说明字符流用的是缓冲区,并且可以使用flush方法强制进行刷新缓冲区,这时才能在不close的情况下输出内容。

  14. 线程安全的含义?
    就是线程同步的意思,就是当一个程序对一个线程安全的方法或者语句进行访问的时候,其他的不能再对他进行操作了,必须等到这次访问结束以后才能对这个线程安全的方法进行访问。
    synchronized修饰的方法或者代码块,所有加上synchronized和块语句,在多线程访问的时候,同一时刻只能有一个线程能够用。
    volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。

  15. Thread和Runnable如何使用写段小代码

    Thread
    public class Example {
    public static void main(String[] args){
    MyThread myThread=new MyThread();
    myThread.start();
    while (true){
    System.out.println(“Main方法在运行”);
    }
    } }

    class MyThread extends Thread{
    public void run(){
    while(true){
    System.out.println(“MyThread类的run()方法在运行”);
    }
    } }

    Runnable
    public class Example {
    public static void main(String[] args){
    MyThread myThread=new MyThread();
    Thread thread=new Thread(myThread);
    thread.start();
    while (true){
    System.out.println(“Main方法在运行”);
    }
    } }
    class MyThread implements Runnable{
    public void run(){
    while(true){
    System.out.println(“MyThread类的run()方法在运行”);
    }
    } }

  16. Synchronized和Lock类的区别?
    Synchronized
    存在层次:是Java的关键字,在jvm层面上。
    锁的释放:以获取锁的线程执行完同步代码,释放锁,线程执行发生异常,jvm会让线程释放锁。
    锁的获取:假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待。
    锁状态:无法判断。
    锁类型:可重入、不可中断、非公平。
    性能:少量同步。
    Lock
    存在层次:是Java的关键字,在jvm层面上;Lock是一个类。
    锁的释放:在finally中必须释放锁,不然容易造成线程死锁。
    锁的获取:分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待。
    锁状态:可以判断。
    锁类型:可重入、可判断、可公平(两者皆可)。
    性能:大量同步。

  17. CountDownLatch和CyclicBarrier的区别?
    CountDownLatch
    减计数方式。
    计算为0时释放所有等待的线程。
    计数为0时,无法重置。
    调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响。
    不可重复利用。
    CyclicBarrier
    加计数方式。
    计数达到指定值时释放所有等待线程。
    计数达到指定值时,计数置为0重新开始。
    调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞。
    可重复利用。

  18. ThreadLocal和Executors的用途、原理、用法?
    ThreadLocal是线程的局部变量,是每一个线程所单独持有的,其他线程不能对其进行访问。当使用ThreadLocal维护变量的时候为每一个使用该变量的线程提供一个独立的变量副本,即每个线程内部都会有一个该变量,这样同时多个线程访问该变量并不会彼此相互影响,因此他们使用的都是自己从内存中拷贝过来的变量的副本,这样就不存在线程安全问题,也不会影响程序的执行性能。
    Executors提供四种线程池:
    newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
    newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
    newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行。
    newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行。

  19. 类的加载过程?
    首先通过一个类的全限定名来获取此类的二进制字节流;其次将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;最后在java堆中生成一个代表这个类的Class对象,作为方法区这些数据的访问入口。
    总的来说就是查找并加载类的二进制数据。

  20. GC常用算法?
    串行收集器(SerialGC)最古老的垃圾回收算法,是以前运行在单核cpu的服务器下的。新生代、老年代都使用串行算法回收。
    并行垃圾回收器(ParallelGC)采用多线程的机制是执行GC操作,所以执行GC过程阻塞时间较短。新生代采用的是并行算法,而老年代依然采用的是串行算法。
    并发标记扫描垃圾回收器(ConcurrentMarkSweep)是应用程序线程和GC线程交替执行。使用的标记-清除算法,并发阶段会降低吞吐量。它经常被用在那些对于响应时间要求十分苛刻的应用之上。
    G1收集器垃圾回收器适用于堆内存很大的情况,他将堆内存分割成不同的区域,并且并发的对其进行垃圾回收。G1也可以在回收内存之后对剩余的堆内存空间进行压缩。并发扫描标记垃圾回收器在STW情况下压缩内存。G1垃圾回收会优先选择第一块垃圾最多的区域。

  21. JVM调优步骤
    第1步:分析GC日志及dump文件,判断是否需要优化,确定瓶颈问题点;
    第2步:确定JVM调优量化目标;
    第3步:确定JVM调优参数(根据历史JVM参数来调整);
    第4步:调优一台服务器,对比观察调优前后的差异;
    第5步:不断的分析和调整,直到找到合适的JVM参数配置;
    第6步:找到最合适的参数,将这些参数应用到所有服务器,并进行后续跟踪。

  22. OOM
    内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。
    内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出
    jstack、jmap可以定位到线程堆栈,根据堆栈信息我们可以定位到具体代码,所以它在JVM性能调优中使用得非常多。

  23. SpringMVC执行流程及原理?
    用户发起请求到前端控制器(DispatcherServlet),该控制器会过滤出哪些请求可以访问Servlet、哪些不能访问。就是url-pattern的作用,并且会加载SpringMVC.xml配置文件。
    前端控制器会找到处理器映射器(HandlerMapping),通过HandlerMapping完成url到controller映射的组件,简单来说,就是将在SpringMVC.xml中配置的或者注解的url与对应的处理类找到并进行存储,用Map这样的方式来存储。
    HandlerMapping有了映射关系,并且找到url对应的处理器,HandlerMapping就会将其处理器(Handler)返回,在返回前,会加上很多拦截器。
    DispatcherServlet拿到Handler后,找到HandlerAdapter(处理器适配器),通过它来访问处理器,并执行处理器。
    执行处理器会返回一个ModelAndView对象给HandlerAdapter。
    通过HandlerAdapter将ModelAndView对象返回给前端控制器(DispatcherServlet)。
    前端控制器请求视图解析器(ViewResolver)去进行视图解析,根据逻辑视图名解析成真正的视图(jsp),其实就是将ModelAndView对象中存放视图的名称进行查找,找到对应的页面形成视图对象,返回视图对象到前端控制器。
    视图渲染,就是将ModelAndView对象中的数据放到request域中,用来让页面加载数据的。

  24. 依赖注入是什么?
    依赖注入(DependencyInjection)和控制反转(InversionofControl)是同一个概念。具体含义是:当某个角色(可能是一个Java实例,调用者)需要另一个角色(另一个Java实例,被调用者)的协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在Spring里,创建被调用者的工作不再由调用者来完成,因此称为控制反转;创建被调用者实例的工作通常由Spring容器来完成,然后注入调用者,因此也称为依赖注入。

  25. 用静态方法写测试用例时如何获取服务的实例
    继承AbstractJUnit4SpringContextTests。
    引入ApplicationContext。

  26. Bean注入后执行初始化代码的途径
    可以在XML配置bean时,指定init-method属性即可。
    需要在需要执行的方法上添加@PostConstruct注解即可。

  27. LeftJoin、RightJoin用法与作用?
    LeftJoin(左连接)返回包括左表中的所有记录和右表中联结字段相等的记录。
    RightJoin(右连接)返回包括右表中的所有记录和左表中联结字段相等的记录。
    InnerJoin(等值连接)只返回两个表中联结字段相等的行。

  28. MySQL执行计划是什么?
    explain

  29. 什么情况会导致索引失效?
    条件中有or。
    对于多列索引,不是使用的第一部分,则不会使用索引。
    like查询是以%开头。
    如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引。

  30. ACID的概念
    原子性(atomicity),就是说这个事务要么不执行,要么全部执行,就是上面2条语句,不允许只执行一条而不执行第二天语句的情况发生。
    一致性(consistency),就是说数据库从一个一致性状态转移到另一个一致性状态,怎么理解呢,原子性是表示不允许只执行一条而不执行第二条的情况发生,那么一致性就是说要么第一条第二条都执行成功(所谓执行成功就是对数据库持久化数据产生了影响),要么就第一条第二条都执行失败(都不对数据库持久化数据产生影响),不允许一条成功,一条失败的情况。
    隔离性(isolation)理解隔离性,就是隔离另一个线程(事务)的操作,比如线程A正在执行这个事务cars_Beijing–,cars_Shanghai++,线程B则正在查询cars_Beijing和cars_Shanghai的值,隔离性就要保证线程B只能查询到事务完全没有执行或者完全成功执行的值,不允许线程B查询到只执行了cars_Beijing–而没有执行cars_Shanghai++的值。
    持久性(durability),这个比较好理解,就是事务一旦提交,所修改的数据就被持久化,即使掉电也不会丢失。

  31. 事务的隔离级别
    未提交读(readuncommitted),就是不做隔离控制,可以读到“脏数据”,比如上面的cars_Beijing–,cars_Shanghai++,这个隔离级别允许其他线程读取到只做了cars_Beijing–而没有做cars_Shanghai++时候的值。显然这个隔离级别没有太大意义,现实中没有人会用,除非这个应用只有读取,没有任何写入。
    提交读(readcommitted),提交读就是不允许读取事务没有提交的数据,简单的说,就是上面的cars_Beijing–,cars_Shanghai++,不允许读取到只做了cars_Beijing–,而没有做cars_Shanghai++的记录。这个隔离级别是大多数数据库(除了mysql)的默认隔离级别。
    可重复读(repeatableread),什么是不可重复读,就是事务A去做cars_Beijing–,cars_Shanghai++之前,事务B启动了,先读取了一次事务A要修改的值,这个时候事务A修改了记录,但是事务B在事务A修改完后又读取了同一记录值,显然,这导致事务B相同的读取操作却读取了不同值,这就是不可重复读。可重复读就是禁止这种情况发生,比如对需要修改的数据加排他锁,事务B需要读取这个记录,那么整个事务B没有完成之前,都允许事务A启动。可重复读的隔离级别是mysql默认的隔离级别。
    可串行化(serialzable),就是多个线程(事务)完全不并发,串行执行,当然不会有任何隔离问题,显而易见效率也最低,一般不采用。

  32. 单例有哪些实现方式?最安全简洁是哪种?
    饿汉模式(线程安全,调用效率高,但是不能延时加载)
    懒汉模式(线程安全,调用效率不高,但是能延时加载)
    双重检测锁模式(由于JVM底层模型原因,偶尔会出问题,不建议使用)
    静态内部类式(线程安全,调用效率高,可以延时加载)
    枚举类(线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用)

  33. Redis有哪些数据结构?
    字符串String、字典Hash、列表List、集合Set、有序集合SortedSet。

  34. 使用过Redis分布式锁么,它是什么回事?
    先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的。

  35. 假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?
    使用keys指令可以扫出指定模式的key列表。redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。

  36. Redis如何做持久化的?
    bgsave做镜像全量持久化,aof做增量持久化。因为bgsave会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要aof来配合使用。在redis实例重启时,会使用bgsave持久化文件重新构建内存,再使用aof重放近期的操作指令来实现完整恢复重启之前的状态。

  37. bgsave的原理是什么?
    fork和cow。fork是指redis通过创建子进程来进行bgsave操作,cow指的是copyonwrite,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。

  38. 是否使用过Redis集群,集群的原理是什么?
    RedisSentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。
    RedisCluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。

  39. Redis的同步机制?
    Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,复制节点接受完成后将rdb镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。

  40. Dubbo默认使用的是什么通信框架?
    Netty

  41. Dubbo的序列化?
    dubbo序列化,阿里尚不成熟的java序列化实现。
    hessian2序列化:hessian是一种跨语言的高效二进制的序列化方式,但这里实际不是原生的hessian2序列化,而是阿里修改过的hessianlite,它是dubboRPC默认启用的序列化方式。
    json序列化:目前有两种实现,一种是采用的阿里的fastjson库,另一种是采用dubbo中自已实现的简单json库,一般情况下,json这种文本序列化性能不如二进制序列化。
    java序列化:主要是采用JDK自带的java序列化实现,性能很不理想。

  42. Dubbo的调用关系?
    提供者启动时,向注册中心注册自己提供的服务。
    消费者启动时,向注册中心订阅自己所需的服务。
    注册中心返回提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
    消费者,从远程接口列表中,调用远程接口,dubbo会基于负载均衡算法,选一台提供者进行调用,如果调用失败则选择另一台。
    消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

  43. Dubbo中ZooKeeper做注册中心,如果注册中心集群都挂掉,发布者和订阅者之间还能通信么?
    注册中心对等集群,任意一台宕掉后,会自动切换到另一台。
    注册中心全部宕掉,服务提供者和消费者仍可以通过本地缓存通讯。
    服务提供者无状态,任一台宕机后,不影响使用。
    服务提供者全部宕机,服务消费者会无法使用,并无限次重连等待服务者恢复。

  44. Dubbo在安全机制方面是如何解决的?
    Dubbo通过Token令牌防止用户绕过注册中心直连,然后在注册中心上管理授权。Dubbo还提供服务黑白名单,来控制服务所允许的调用方。

  45. Dubbo超时和重连机制?
    Dubbo启动时默认有重试机制和超时机制。
    超时机制的规则是如果在一定的时间内,provider没有返回,则认为本次调用失败,重试机制在出现调用失败时,会再次调用。如果在配置的调用次数内都失败,则认为此次请求异常,抛出异常。

  46. 几个常用类的区别?
    ArrayList:元素单个,效率高,多用于查询。
    Vector:元素单个,线程安全,多用于查询。
    LinkedList:元素单个,多用于插入和删除。
    HashMap:元素成对,元素可为空。
    HashTable:元素成对,线程安全,元素不可为空。

  47. IO与NIO的区别?
    NIO即NewIO,这个库是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。在JavaAPI中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO。
    IO:面向流,阻塞IO。
    NIO:面向缓冲,非阻塞IO。

  48. TCP/IP协议三次握手?
    第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;SYN:同步序列编号(SynchronizeSequenceNumbers)。
    第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。
    第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
    完成三次握手,客户端与服务器开始传送数据。

  49. SpringBoot是什么?
    用来简化Spring应用的初始搭建以及开发过程,使用特定的方式来进行配置(properties或yml文件)
    创建独立的Spring引用程序main方法运行。
    嵌入的Tomcat无需部署war文件。
    简化Maven配置。
    自动配置Spring添加对应功能Starter自动化配置。

  50. SpringBoot常用的Starter有哪些?
    spring-boot-starter-web:tomcat和web开发需要servlet与jsp支持。
    spring-boot-starter-data-jpa:数据库支持。
    spring-boot-starter-data-redis:redis数据库支持。
    spring-boot-starter-data-solr:solr支持。
    mybatis-spring-boot-starter:第三方的MyBatis集成Starter。

  51. SpringBoot自动配置的原理?
    在Spring程序main方法中添加@SpringBootApplication或者@EnableAutoConfiguration会自动去Maven中读取每个Starter中的spring.factories文件该文件里配置了所有需要被创建Spring容器中的bean。

  52. SpringBoot读取配置文件的方式?
    SpringBoot默认读取配置文件为application.properties或者是application.yml。

  53. SpringBoot集成MyBatis的过程?
    添加MyBatis的依赖:
    <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.2.0</version>
    </dependency>

  54. MySQL数据库引擎MYISAM和INNODB区别?
    InnoDB
    具有事务(commit)、回滚(rollback)。
    支持外键。
    InnoDB中不保存表的具体行数,获取行数全表扫描。
    支持行锁。
    适用于需要事务支持的场景。
    MyISAM
    不具有事务(commit)、回滚(rollback)。
    不支持外键。
    直接获取行数。
    不支持行锁,插入和更新锁全表读多写少,并且不需要事务支持的场景。

  55. 哪些情况FullGC?
    SystemGC。
    老年代空间不足。
    永久代空间不足。
    MinorGC晋升大小大于老年代剩余大小。
    堆中分配大对象,老年代装不下的时候。
    CMS出发FullGC的两种情况,年轻代晋升老年代没有足够的连续空间,或者在CMS并发收集未完成之前老年代就已经满了。

  56. Tomcat调优方案?
    工作方式选择
    为了提升性能,首先就要对代码进行动静分离,让Tomcat只负责jsp文件的解析工作。如采用Apache和Tomcat的整合方式,他们之间的连接方案有三种选择,JK、http_proxy和ajp_proxy。相对于JK的连接方式,后两种在配置上比较简单的,灵活性方面也一点都不逊色。但就稳定性而言不像JK这样久经考验,所以建议采用JK的连接方式。
    Connector连接器的配置
    之前文件介绍过的Tomcat连接器的三种方式:bio、nio和apr,三种方式性能差别很大,apr的性能最优,bio的性能最差。而Tomcat7使用的Connector默认就启用的Apr协议,但需要系统安装Apr库,否则就会使用bio方式。
    配置文件优化
    默认配置下,Tomcat会为每个连接器创建一个绑定的线程池(最大线程数200),服务启动时,默认创建了5个空闲线程随时等待用户请求。
    JVM优化
    Tomcat启动命令行中的优化参数,就是JVM的优化。Tomcat首先跑在JVM之上的,因为它的启动其实也只是一个java命令行,首先我们需要对这个JAVA的启动命令行进行调优。不管是YGC还是FullGC,GC过程中都会对导致程序运行中中断,正确的选择不同的GC策略,调整JVM、GC的参数,可以极大的减少由于GC工作,而导致的程序运行中断方面的问题,进而适当的提高Java程序的工作效率。但是调整GC是以个极为复杂的过程,由于各个程序具备不同的特点,如:web和GUI程序就有很大区别(Web可以适当的停顿,但GUI停顿是客户无法接受的),而且由于跑在各个机器上的配置不同(主要cup个数,内存不同),所以使用的GC种类也会不同。

  57. 有三个线程T1 T2 T3,如何保证他们按顺序执?

    public class TestJoin {
    public static void main(String[] args) throws InterruptedException {
    final Thread t1 = new Thread(new Runnable() {
    public void run() {
    System.out.println(Thread.currentThread().getName() + ” run 1”);
    }
    }, “T1”);
    final Thread t2 = new Thread(new Runnable() {
    public void run() {
    System.out.println(Thread.currentThread().getName() + ” run 2”);
    try {
    t1.join(10);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }, “T2”);
    final Thread t3 = new Thread(new Runnable() {
    public void run() {
    System.out.println(Thread.currentThread().getName() + ” run 3”);
    try {
    t2.join(10);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }, “T3”);
    //method1
    //t1.start();
    //t2.start();
    //t3.start();
    //method 2 使用 单个任务的线程池来实现。保证线程的依次执行
    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.submit(t1);
    executor.submit(t2);
    executor.submit(t3);
    executor.shutdown();
    }
    }

  58. 缓存优化
    缓存雪崩:
    可能是因为数据未加载到缓存中,或者缓存同一时间大面积的失效,从而导致所有请求都去查数据库,导致数据库CPU和内存负载过高,甚至宕机。解决思路:加锁计数(即限制并发的数量,可以用semphore)或者起一定数量的队列来避免缓存失效时大量请求并发到数据库。但这种方式会降低吞吐量。分析用户行为,然后失效时间均匀分布。或者在失效时间的基础上再加1~5分钟的随机数。如果是某台缓存服务器宕机,则考虑做主备。
    缓存穿透:
    指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库中查询。解决思路:如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库。设置一个过期时间或者当有值的时候将缓存中的值替换掉即可。可以给key设置一些格式规则,然后查询之前先过滤掉不符合规则的Key。
    缓存并发:
    如果网站并发访问高,一个缓存如果失效,可能出现多个进程同时查询DB,同时设置缓存的情况,如果并发确实很大,这也可能造成DB压力过大,还有缓存频繁更新的问题。解决思路:对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询。
    缓存预热:
    目的就是在系统上线前,将数据加载到缓存中。解决思路:数据量不大的话,在系统启动的时候直接加载。自己写个简单的缓存预热程序。

  59. 缓存算法
    FIFO算法:First in First out,先进先出。原则:一个数据最先进入缓存中,则应该最早淘汰掉。也就是说,当缓存满的时候,应当把最先进入缓存的数据给淘汰掉。
    LFU算法:Least Frequently Used,最不经常使用算法。
    LRU算法:Least Recently Used,近期最少使用算法。
    LRU和LFU的区别。LFU算法是根据在一段时间里数据项被使用的次数选择出最少使用的数据项,即根据使用次数的差异来决定。而LRU是根据使用时间的差异来决定的。

  60. 高并发、大流量处理及解决方法?
    扩容、动静分离、缓存、服务降级和限流。
    限流的常用算法和实践思路
    令牌桶算法:
    主要限制流量的流入速率,允许出现一定程度的突发流量。
    每秒会有r个令牌按照固定速率放入桶中。桶的容量是固定不变的,如果桶满了再放入令牌,则溢出。若桶中的可用令牌不足,则改请求会被进行限流处理(被抛弃或缓存)。
    漏桶算法:
    主要限制流量的流出速率,并且流出速率是固定不变的
    可以以任意速率向桶中流入水滴。桶的容量是固定不变的,如果桶满了则溢出。按照固定的速率从桶中流出水滴。
    计数器算法:
    生产环境中的商品抢购可以使用,具体不同的sku限流规则配置在配置中心内,支持动态更改。可抢购次数的扣减操作,既可以用redis,也可以用JVM。如果是集群并且选择用JVM,则要根据总并发数量除以集群数量,得出单台机器的并发数。(比如总并发数5000,集群机器10台,则每台机器的并发为5000/10=500)。
    高并发读需求
    对于一件抢购商品的流量来说,因为key是同一个,所以流量必然会都引入到同一个redis缓存节点中,这时就容易出现单点故障。因此有下面两种解决方式:在每个master节点都挂slave从节点,当主节点挂了可以自动顶上。多级Cache方案,多用LocalCache来过滤掉一部分流量。
    本地缓存一般只缓存一些热点商品数据,缓存内容一般是商品详情和商品库存。
    本地缓存跟分布式缓存的同步一般有两种方式:一种是定时主动拉取更新策略。这种会存在一定时间的不一致,要视业务情况而定,例如库存,暂时的不一致导致超卖,单到真正下单的时候还会再进行库存的判断,所以影响较小,可以接受。这种方式要注意关掉缓存的定时失效,防止当用户流量突然过大,都到分布式缓存中拉取数据;第二种方式是每次商品更新,都发布一个消息,订阅此消息的节点监听到后再更新本地缓存的内容。
    热点自动发现方案
    可以将交易系统产生的相关数据,以及在上游系统中埋点上报的相关数据异步写入日志系统中,然后通过实时热点自动发现平台对收集到的日志数据做调用次数统计和热点分析。数据符合热点条件后,就立即通知交易系统做好热点保护。
    Redis使用watch命令实现高并发抢购需求
    一般高并发这里,不用悲观锁,会迅速增加系统资源;而使用队列,容易造成请求堆积,内存效果过快。所以一般使用乐观锁,可以用redis的watch命令实现。watch命令会监视给定的key,当exec时,如果监视的key从调用watch后发生过变化,则事务会失败。注意watch的可以是对整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自动清除。当然exec,discard,unwatch命令都会清除连接中的所有监视。
    协程(纤程)Fiber
    协程概念:一种用户态的轻量级线程,其实就是单线程,指定执行整个函数中到一部分然后就先出去执行别的,等条件满足时,协程下次更新帧到了再继续往下执行。优点是无需线程上下文切换的开销,充分开发了单CPU的能力,资源占用低,适合高并发I/O。缺点也很明显,就是没办法利用多CPU的优势。
    框架:Quasar,调度器使用ForkJoinPool来调度这些fiber。Fiber调度器FiberScheduler是一个高效的、work-stealing、多线程的调度器。
    场景:服务A平时需要调用其他服务,但其他服务在并发高的时候延迟很严重。 一开始可以用httpClient连接池+线程池来处理,但如果调用服务的时候延迟太高或者超时,则会导致服务A的吞吐量会特别差。原因主要是一般一个链接由一个线程来处理,是阻塞的,所以在线程池数有限的情况下,吞吐量肯定上不去。并且当所有线程都I/O阻塞的时候,会很浪费CPU资源,并且CPU会一直做无用的上下文切换。这时候可以考虑协程来替换。
    ForkJoinPool线程池
    Fork/Join框架是Java7提供了的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。
    工作窃取(work-stealing)算法是Fork/Join框架最重要的特性。一般一个线程会对应一个任务队列,当处理较快的线程处理完自己的任务之后,就会窃取另外一个处理比较慢的线程对应的任务,这时候会存在两个线程同时处理一个队列的情况,所以任务队列一般使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。优点是充分利用线程进行并行计算,并减少了线程间的竞争。

  61. 说说秒杀如何实现的?
    核心思路是流量控制和性能优化。
    流量控制
    请求流控:可以通过前端系统进行拦截,限制最终流入系统的请求数量。
    客户端流控:较为合适的做法是屏蔽用户高频请求,比如在网页中设置5s一次访问限制,可以防止用户过度刷接口。
    Web端流控:常见做法是可以通过在内存或缓存服务中加入请求访问信息,来实现访问量限制。
    数据库存在瓶颈,为了应对大量的并发请求,使用redis缓存来拦截大量请求,使用redis原子操作incr和decr来确保库存。
    对此方案优化
    当库存为0时,需要在本地缓存中直接关闭标示位,即可以不去访问Redis。
    当扣减库存之后,可以使用异步操作来创建订单等一系列操作。

猜你喜欢

转载自blog.csdn.net/kaition/article/details/81073066