面试知识点一(针对有经验的人)

面临重新找工作,梳理下比较常问的知识点。

JVM 和调优 (对于有2.3年工作经验的人来说这是个亮点。) 

线程池(这必须懂。)

远程调用(加分的亮点。)

MQ (有问的)

数据库优化 (天天问)

集合源码  (这还用说吗?不懂能出去找工作)

框架原理 (同上)

高并发  (亮点,明白了。你还需要找工作?)

NOSQL  和缓存 (这不懂都不太好意思出去)

 

 

 

list   set  map  底层原理

 

list;

可变化的数组。当定义存储大小=实际大小+1时,新建数组大小变为初始大小的2倍,拷贝数组。

Collection子接口之一:Set接口

HashSet   LinkedHashSet   TreeSet

Collection子接口之二: List接口

ArrayList  LinkedList  Vector

Map接口

HashMap  TreeMap  Hashtable

 

 

Set

HashSet 最低层是一个封装 的HashMap,所以的值为0;

源码

 public HashSet() {

        map = new HashMap<>();

    }

 

 

map;

“你用过HashMap吗?” “什么是HashMap?你为什么用到它?”

1.譬如HashMap可以接受null键值和值,而HashTable则不能;

2.HashMap是非synchronized;HashMap很快,以及HashMap储存的是键值对等等。

 

“你知道HashMap的工作原理吗?” “你知道HashMapget()方法的工作原理吗?”

 

Hashmap 是冲突链表方式。Hascode决定放在链表的位置下标。equals()决定是否为同一对象。

HashMap是基于hashing的原理,我们使用put(key,value)存储对象到HashMap中,使用get(key)HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象。”

HashMap是在bucket中储存键对象和值对象,

 

“当两个对象的hashcode相同会发生什么?”

equals()hashCode()两个方法,并告诉他们两个对象就算hashcode相同,但是它们可能并不相等。

因为hashcode相同,所以它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用LinkedList存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在LinkedList中。

 

“如果两个键的hashcode相同,你如何获取值对象?”     

 

   当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,然后获取值对象。面试官提醒他如果有两个值对象储存在同一个bucket

将会遍历LinkedList直到找到值对象。面试官会问因为你并没有值对象去比较,你是如何确定确定找到值对象的?除非面试者直到HashMapLinkedList中存储的是键值对。找到bucket位置之后,会调用keys.equals()方法去找到LinkedList中正确的节点。

  许多情况下,面试者会在这个环节中出错,因为他们混淆了hashCode()equals()方法。因为在此之前hashCode()屡屡出现,而equals()方法仅仅在获取值对象的时候才出现。一些优秀的开发者会指出使用不可变的、声明作final的对象,并且采用合适的equals()hashCode()方法的话,将会减少碰撞的发生,提高效率。不可变性使得能够缓存不同键的hashcode,这将提高整个获取对象的速度,

 

“如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?”

默认的负载因子大小为0.75,也就是说,当一个map填满了75%bucket时候,

和其它集合类(ArrayList)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,

并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。

 

“你了解重新调整HashMap大小存在什么问题吗?”

当多线程的情况下,可能产生条件竞争(race condition)

 

  当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在LinkedList中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在LinkedList的尾部,而是放在头部,这是为了避免尾部遍历(tailtraversing)。如果条件竞争发生了,那么就死循环了。

 

为什么String, Interger这样的wrapper类适合作为键?

 String,Interger这样的wrapper类作为HashMap的键是再适合不过了,而且String最为常用。因为String是不可变的,也是final的,而且已经重写了equals()hashCode()方法了。其他的wrapper类也有这个特点。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。不可变性还有其他的优点如线程安全。如果你可以仅仅通过将某个field声明成final就能保证hashCode是不变的,那么请这么做吧。因为获取对象的时候要用到equals()hashCode()方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这样就能提高HashMap的性能。

我们可以使用自定义的对象作为键吗?

 这是前一个问题的延伸。当然你可能使用任何对象作为键,只要它遵守了equals()hashCode()方法的定义规则,并且当对象插入到Map中之后将不会再改变了。如果这个自定义对象时不可变的,那么它已经满足了作为键的条件,因为当它创建之后就已经不能改变了。

我们可以使用CocurrentHashMap来代替HashTable吗?

这是另外一个很热门的面试题,因为ConcurrentHashMap越来越多人用了。我们知道HashTablesynchronized的,但是ConcurrentHashMap同步性能更好,因为它仅仅根据同步级别对map的一部分进行上锁。ConcurrentHashMap当然可以代替HashTable,但是HashTable提供更强的线程安全性。

 

我们能否让HashMap同步?

 

HashMap可以通过下面的语句进行同步:

Map m = Collections.synchronizeMap(hashMap);

 

 

Spring MVC Spring原理  (重点)

 

Spring工作流程描述

      1. 用户向服务器发送请求,请求被Spring前端控制Servelt DispatcherServlet捕获;

      2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;

      3. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(...)方法)

       4.  提取Request中的模型数据,填充Handler入参,开始执行HandlerController)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:

      HttpMessageConveter: 将请求消息(如Jsonxml等数据)转换成一个对象,将对象转换为指定的响应信息

      数据转换:对请求消息进行数据转换。如String转换成IntegerDouble

      数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等

      数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResultError

      5.  Handler执行完成后,向DispatcherServlet返回一个ModelAndView对象;

      6.  根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet

      7. ViewResolver 结合ModelView,来渲染视图

      8. 将渲染结果返回给客户端.

Spring工作流程描述

    为什么Spring只使用一个Servlet(DispatcherServlet)来处理所有请求?

     详细见J2EE设计模式-前端控制模式

    Spring为什么要结合使用HandlerMapping以及HandlerAdapter来处理Handler?

    符合面向对象中的单一职责原则,代码架构清晰,便于维护,最重要的是代码可复用性高。如HandlerAdapter可能会被用于处理多种Handler   

Spring 原理

  动态反射。 获取XML等节点,动态反射获取方法实例。

 

 

高并发() 大数据量处理

  从低成本、高性能和高扩张性的角度来说有如下处理方案:

  1HTML静态化

  2、图片服务器分离

  3、数据库集群和库表散列

  4、缓存

  5、镜像 6、负载均衡;一个典型的使用负载均衡的策略就是,在软件或者硬件四层交换的基础上搭建squid集群,这种思路在很多大型网站包括搜索引擎上被采用,这样的架构低成本、高性能还有很强的扩张性,随时往架构里面增减节点都非常容易。

 

  

  

JVM虚拟机、GC回收 和 处理流程

 

JVM虚拟机

方法区(Method Area)用于存储类结构信息的地方,包括常量池、静态变量、构造函数等。虽然JVM规范把方法区描述为堆的一个逻辑部分, 但它却有个别名non-heap(非堆),所以大家不要搞混淆了。方法区还包含一个运行时常量池。所有java线程共享的

java(Heap)存储java实例或者对象的地方。这块是GC的主要区域(后面解释)。从存储的内容我们可以很容易知道,方法区和堆是被所有java线程共享的

java(Stack)java栈总是和线程关联在一起,每当创建一个线程时,JVM就会为这个线程创建一个对应的java栈。在这个java栈中又会包含多个栈帧,每运行一个方法就创建一个栈帧,用于存储局部变量表、操作栈、方法返回值等。每一个方法从调用直至执行完成的过程,就对应一个栈帧在java栈中入栈到出栈的过程。所以java栈是线程私有的

程序计数器(PC Register)用于保存当前线程执行的内存地址。由于JVM程序是多线程执行的(线程轮流切换),所以为了保证线程切换回来后,还能恢复到原先状态,就需要一个独立的计数器,记录之前中断的地方,可见程序计数器也是线程私有的

本地方法栈(Native Method Stack)java栈的作用差不多,只不过是为JVM使用到的native方法服务的。

 

GC回收

垃圾检测

引用计数法:给一个对象添加引用计数器,每当有个地方引用它,计数器就加1;引用失效就减1

好了,问题来了,如果我有两个对象AB,互相引用,除此之外,没有其他任何对象引用它们,实际上这两个对象已经无法访问,即是我们说的垃圾对象。但是互相引用,计数不为0,导致无法回收,所以还有另一种方法:

可达性分析算法:以根集对象为起始点进行搜索,如果有对象不可达的话,即是垃圾对象。这里的根集一般包括java栈中引用的对象、方法区常良池中引用的对象

回收算法

1.标记-清除(Mark-sweep

算法和名字一样,分为两个阶段:标记和清除。标记所有需要回收的对象,然后统一回收。这是最基础的算法,后续的收集算法都是基于这个算法扩展的。

不足:效率低;标记清除之后会产生大量碎片。

2.复制(Copying

此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。

3.标记-整理(Mark-Compact

此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。

4.分代收集算法

这是当前商业虚拟机常用的垃圾收集算法。分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。

 

处理流程

 

加载:虚拟机需要完成以下三件事情:

 

a) 通过一个类的权限定名来获取此类定义的二进制字节流

b) 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构

c) java堆中生成一个代表这个类的java.lang.Class对象,作为访问方法区的入口

验证:

 

连接阶段的第一步,这一阶段的目的是为了确保 Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全(备注:虽然在java语言是相对安全的,但是在字节码层面, 上述java代码无法做到的事情都是可以实现的,至少在语义上是可以表达出来的。所以对字节流进行验证是相当必要的)

准备

 

准备阶段是正式为变量分配内存并设置变量初始值的阶段,这些内存都将在方法区中进行分配。

解析

 

解析过程就是在类型的常量池中寻找类、接口、字段和方法的符号引用,把这些符号引用替换成直接引用的过程。(至于什么是符号引用,什么是直接引用,参考这篇博客:http://blog.csdn.net/lantian0802/article/details/9152657

ps:验证、准备、解析统称为连接)

 

初始化:

 

为类的静态变量赋予正确的初始值,当然也包括执行静态代码块的内容。

 

 

加载器

 

 

1Bootstrap ClassLoader (启动类加载器)

负责加载$JAVA_HOMEjre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类

2Extension ClassLoader (扩展类加载器)

负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOMEjre/lib/*.jar-Djava.ext.dirs指定目录下的jar

3App ClassLoader (引用类加载器)

负责记载classpath中指定的jar包及目录中class

4Custom ClassLoader  (自定义类加载器)

属于应用程序根据自身需要自定义的ClassLoader,如tomcatjboss都会根据j2ee规范自行实现ClassLoader

加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoaderBootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

 

单例 和算法  

单例

public class Singleton {  

      

    private Singleton(){}  

      

    private static Singleton singleton = null;  

      

    //懒汉式1:在方法上面加入同步块,每次都得同步,影响性能  

    public static synchronized Singleton getInstance(){  

          

        if(singleton == null){  

            singleton = new Singleton();  

        }  

        return singleton;  

    }  

    1、单例类只能有一个实例。(私有的构造函数)

2、单例类必须自己自己创建自己的唯一实例。 (私有的实例)

3、单例类必须给所有其他对象提供这一实例。 (一个对外的接口)

冒泡

public class BubbleSort

{

    public void sort(int[] a)

    {

        int temp = 0;

        for (int i = a.length - 1; i > 0; --i)

        {

            for (int j = 0; j < i; ++j)

            {

                if (a[j + 1] < a[j])

                {

                    temp = a[j];

                    a[j] = a[j + 1];

                    a[j + 1] = temp;

                }

            }

        }

    }

}

memcached原理

 

理一下MemCache一次写缓存的流程:

 

1、应用程序输入需要写缓存的数据

 

2APIKey输入路由算法模块,路由算法根据KeyMemCache集群服务器列表得到一台服务器编号

 

3、由服务器编号得到MemCache及其的ip地址和端口号

 

4API调用通信模块和指定编号的服务器通信,将数据写入该服务器,完成一次分布式缓存的写操作

 

 

一致性Hash算法

1、余数Hash

字符串str对应的HashCode50、服务器的数目是3,取余数得到1str对应节点Node1,所以路由算法把str路由到Node1服务器上。由于HashCode随机性比较强,所以使用余数Hash路由算法就可以保证缓存数据在整个MemCache服务器集群中有比较均衡的分布。

 

服务器集群的伸缩性(什么是伸缩性,请参见大型网站架构学习笔记),那么余数Hash算法几乎可以满足绝大多数的缓存路由需求,但是当分布式缓存集群需要扩容的时候,就难办了。数据命中率降低。当大部分被缓存了的数据因为服务器扩容而不能正确读取时,这些数据访问的压力就落在了数据库的身上,这将大大超过数据库的负载能力,严重的可能会导致数据库宕机。

 

2、一致性Hash算法

一致性Hash算法通过一个叫做一致性Hash环的数据结构实现Key到缓存服务器的Hash映射

先构造一个长度为232的整数环(这个环被称为一致性Hash环),根据节点名称的Hash值(其分布为[0,232-1])将缓存服务器节点放置在这个Hash环上,然后根据需要缓存的数据的Key值计算得到其Hash值(其分布也为[0,232-1]),然后在Hash环上顺时针查找举例这个Key值的Hash值最近的服务器节点,完成Key到服务器的映射查找。集群中缓存服务器节点越多,增加节点带来的影响越小,

 

1、访问数据的速度比传统的关系型数据库要快,因为OracleMySQL这些传统的关系型数据库为了保持数据的持久性,数据存放在硬盘中,IO操作速度慢

 

2MemCache的数据存放在内存中同时意味着只要MemCache重启了,数据就会消失

 

3、既然MemCache的数据存放在内存中,那么势必受到机器位数的限制,这个之前的文章写过很多次了,32位机器最多只能使用2GB的内存空间,64位机器没有上限

 

1FIFOFirst In First Out,先进先出

2LRULeast Recently Used,最近最少使用

3LFULeast Frequently Used,最不经常使用

 

多线程

 

多线程安全问题。根本是多个线程对类成员变量和实例变量的使用,线程中的变量共享。

 

偏向锁:

轻量级锁:

重量级锁:互斥锁。一个线程拿到锁,其他线程等待。阻塞

 

IO

BIO;传统的阻塞式同步流

NIO ;非阻塞式同步流  服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。用户进程也需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问。

AIO;非阻塞式异步流   用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了  

BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。

NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。

AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

 

 

Nginx:

nginxupstream目前支持4种方式的分配

1、轮询(默认)

每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。

2weight

指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。 例如:

upstream bakend {

server 192.168.0.14 weight=10;

server 192.168.0.15 weight=10;}

2ip_hash

每个请求按访问iphash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。 例如:

upstream bakend {

ip_hash;

server 192.168.0.14:88;

server 192.168.0.15:80;}

3fair(第三方)

按后端服务器的响应时间来分配请求,响应时间短的优先分配。

upstream backend {

server server1;

server server2;

fair;}

4url_hash(第三方)

按访问urlhash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。

例:在upstream中加入hash语句,server语句中不能写入weight等其他的参数,hash_method是使用的hash算法

upstream backend {

server squid1:3128;

server squid2:3128;

hash $request_uri;

hash_method crc32;}

tips:

upstream bakend{#定义负载均衡设备的Ip及设备状态

ip_hash;

server 127.0.0.1:9090 down;

server 127.0.0.1:8080 weight=2;

server 127.0.0.1:6060;

server 127.0.0.1:7070 backup;}

在需要使用负载均衡的server中增加

proxy_pass http://bakend/;

每个设备的状态设置为:

1.down 表示单前的server暂时不参与负载2.weight 默认为1.weight越大,负载的权重就越大。3.max_fails :允许请求失败的次数默认为1.当超过最大次数时,返回proxy_next_upstream模块定义的错误 4.fail_timeout:max_fails次失败后,暂停的时间。5.backup: 其它所有的非backup机器down或者忙的时候,请求backup机器。所以这台机器压力会最轻。

nginx支持同时设置多组的负载均衡,用来给不用的server来使用。

· client_body_in_file_only设置为On 可以讲client post过来的数据记录到文件中用来做debug

· client_body_temp_path设置记录文件的目录 可以设置最多3层目录

location URL进行匹配.可以进行重定向或者进行新的代理 负载均衡

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值