JAVA常用知识点及面试题总结

时间:2021-07-18 03:24:00

1. String、StringBuffer、StringBuilder三者区别?

(1)三者在执行速率上的比较: String<StringBuffer<StringBuilder
  原因:String是字符串常量,StringBuffer,StringBuilder是字符串变量; 每当用String操作字符串时,实际上是在不断的创建新的对象,而原来的对象就会变为垃圾被GC回收掉;而StringBuffer与StringBuilder是字符串变量,是可改变的对象,用它们对字符串做操作时,实际上是在一个对象上操作的。

(2)对比StringBuffer及StringBuilder
   StringBuilder不是线程安全的,而StringBuffer是线程安全的。单线程应用场景下优先选择StringBuilder,因为速度更快。

2. "=="与HashCode(),equals的区别?

(1)基本用途:
  “==”是运算符,用于比较两个变量是否相等。
  equals是Objec类的方法,用于比较两个对象是否相等,默认Object类的equals方法是比较两个对象的地址,跟==的结果一样;
  hashCode也是Object类的一个方法。返回一个离散的int型整数。在集合类操作中使用,为了提高查询速度。

(2)将对象放入集合中时的操作:
  首先判断要放入对象的hashcode值与集合中的任意一个元素的hashcode值是否相等,如果不相等直接将该对象放入集合中。

  如果hashcode值相等,然后再通过equals方法判断要放入对象与集合中的任意一个对象是否相等,如果equals判断不相等,直接将该元素放入到集合中,否则不放入。

(3)另外注意:覆盖equals时总要覆盖hashCode 。

3. Object类有哪些方法

(1)clone方法
  实现对象的浅复制,只实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。
(2)getClass方法
  用于获得运行时类型。
(3)toString方法
  返回String
(4) finalize方法
  用于释放资源,但很少使用,因为无法确定该方法什么时候被调用。
(5)equals方法
  用于比较两个对象是否相等
(6)hashCode方法
  用于哈希查找,重写了equals方法一般都要重写hashCode方法。
(7)wait方法
  使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。
(8)notify方法
  唤醒在该对象上等待的某个线程。
(9)notifyAll方法
  唤醒在该对象上等待的所线程。

4.  常见的runtimeException:

  NullPointerException - 空指针引用异常
  ClassCastException - 类型强制转换异常。
  IllegalArgumentException - 传递非法参数异常。
  ArithmeticException - 算术运算异常
  ArrayStoreException - 向数组中存放与声明类型不兼容对象异常
  IndexOutOfBoundsException - 下标越界异常
  NegativeArraySizeException - 创建一个大小为负数的数组错误异常
  NumberFormatException - 数字格式异常
  SecurityException - 安全异常
  UnsupportedOperationException - 不支持的操作异常

5. 构造器如何工作?
Java在构造实例时的顺序是这样的:
1) 分配对象空间,并将对象中成员初始化为0或者空,java不允许用户操纵一个不定值的对象   
2) 执行属性值的显式初始化   
3) 执行构造器   
4) 将变量关联到堆中的对象上
 
执行构造器的步骤可以分为以下几步:
1) Bind构造器的参数
2) 如果显式的调用了this,那就递归调用this构造器;
3) 递归调用显式或者隐式的父类构造器,除了Object以外;
4) 执行显式的实例变量初始化

构造器不可被override
构造器不是方法,那么用来修饰方法特性的所有修饰符都不能用来修饰构造器
(并不等与构造器不具备这些特性,虽然不能用static修饰构造器,但它却有静态特性)
构造器只能用 public private protected这个权限修饰符,且不能返回语句。

面试题总结:

一、 基础知识篇

1. 下面这段代码的执行结果是:

public class Test03 {
public static void main(String[] args) {
Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150; System.out.println(f1 == f2);
System.out.println(f3 == f4);
}
}

如果整型字面量的值在-128到127之间,那么不会new新的Integer对象,而是直接引用常量池中的Integer对象,所以上面的面试题中f1==f2的结果是true,而f3==f4的结果是false。

2. 解释内存中的栈(stack)、堆(heap)和静态区(static area)的用法。
  基本数据类型的变量,对象的引用,函数调用使用内存中的栈空间;
  通过new关键字和构造器创建的对象放在堆空间;
  程序中的字面量(literal)如直接书写的100、”hello”和常量都是放在静态区中。
  栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,理论上整个内存没有被其他进程使用的空间甚至硬盘上的虚拟内存都可以被当成堆空间来使用。

3. hashCode 计算使用素数31的原因?
  选择31是因为可以用移位和减法运算来代替乘法,从而得到更好的性能。
  31 * num 等价于(num << 5) – num,左移5位相当于乘以2的5次方再减去自身就相当于乘以31。

4. 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?
  是值传递。Java语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的。

5. 重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?

  重载(Overload):编译时多态, 重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同);
  重写(Override): 运行时多态,发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。
  重载对返回类型没有特殊的要求。

6. 为什么不能根据返回类型来区分重载?
  编译器可以通过方法的参数类型和个数来区分它们,但返回值和异常时不能作为区分标志的。

7. char 型变量中能不能存贮一个中文汉字,为什么?
  char类型可以存储一个中文汉字,因为Java中使用的编码是Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一个char类型占2个字节(16比特),所以放一个中文是没问题的。

8. 抽象类(abstract class)和接口(interface)有什么异同?
  抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用。一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类。
  接口比抽象类更加抽象,因为抽象类中可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其中的方法全部都是抽象方法。抽象类中的成员可以是private、default、protected、public的,而接口中的成员全都是public的。抽象类中可以定义成员变量,而接口中定义的成员变量实际上都是常量。有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法。

9. 抽象的(abstract)方法是否可同时是静态的(static),是否可同时是本地方法(native),是否可同时被synchronized修饰?
  都不能。
  抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的。
  本地方法是由本地代码(如C代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。
  synchronized和方法的实现细节有关,抽象方法不涉及实现细节,因此也是相互矛盾的。

10. 类加载机制?

  参见博文:

  http://www.cnblogs.com/hunterCecil/p/6381808.html   JVM类加载机制---类加载器

  http://www.cnblogs.com/hunterCecil/p/6379197.html   JVM类加载机制---类加载的过程

11. 垃圾回收机制?

  参见博文: http://www.cnblogs.com/hunterCecil/p/7284148.html       深入理解JAVA虚拟机之JVM性能篇---垃圾回收

12. 接口是否可继承(extends)接口?抽象类是否可实现(implements)接口?抽象类是否可继承具体类(concrete class)?
  接口可以继承接口,而且支持多重继承。

  抽象类可以实现(implements)接口,

  抽象类可继承具体类也可以继承抽象类。

13. Java 中的final关键字有哪些用法?
  (1)修饰类:表示该类不能被继承;
  (2)修饰方法:表示方法不能被重写;
  (3)修饰变量:表示变量只能一次赋值以后值不能被修改(常量)。

14. 浅复制和深复制的区别及实现?

  浅拷贝是指在拷贝对象时,对于基本数据类型的变量会重新复制一份,而对于引用类型的变量只是对引用进行拷贝,没有对引用指向的对象进行拷贝。

  深拷贝是指在拷贝对象时,同时会对引用指向的对象进行拷贝。

  区别就在于是否对  对象中的引用变量所指向的对象进行拷贝。

  两者都可以通过复写Clonable()接口的clone()方法实现:  

  浅拷贝实现:

public class CloneTest implements Cloneable{
public Object clone() {
UserBean user = null;
try{
user = (UserBean) super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return user;
}
}

   这时: 如果 B = A.clone(), 则只是复制了一个对象的引用,指向A对象

  深拷贝实现:

 public Object clone() {
UserBean user = null;
try{
user = (UserBean) super.clone();
if(user != null){
user.addr = (Address)addr.clone();
}
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return user;
} 

  这时, 如果 B = A.clone(), 则是实现了对象及引用的拷贝。

15. 如何实现字符串的反转及替换?
  可以使用String或StringBuffer/StringBuilder中的方法。有一道很常见的面试题是用递归实现字符串反转,代码如下所示:

 public static String reverse(String originStr) {
      if(originStr == null || originStr.length() <= 1)
          return originStr;
      return reverse(originStr.substring(1)) + originStr.charAt(0);
  } 

16. List、Map、Set三个接口存取元素时,各有什么特点?
  List以特定索引来存取元素,可以有重复元素。
  Set不能存放重复元素(用对象的equals()方法来区分元素是否重复)。
  Map保存键值对(key-value pair)映射,映射关系可以是一对一或多对一。
  Set和Map容器都有基于哈希存储和排序树的两种实现版本,基于哈希存储的版本理论存取时间复杂度为O(1),而基于排序树版本的实现在插入或删除元素时会按照元素或元素的键(key)构成排序树从而达到排序和去重的效果

17. Collection各种容器的区别及特性?

  参见博文:http://www.cnblogs.com/hunterCecil/p/7593555.html   JAVA常用知识点总结---集合篇

18. TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素?

  TreeSet要求存放的对象所属的类必须实现Comparable接口,该接口提供了比较元素的compareTo()方法,当插入元素时会回调该方法比较元素的大小。
  TreeMap要求存放的键值对映射的键必须实现Comparable接口从而根据键对元素进行排序。
  Collections工具类的sort方法有两种重载的形式,第一种要求传入的待排序容器中存放的对象比较实现Comparable接口以实现元素的比较;
  第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator接口的子类型(需要重写compare方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java中对函数式编程的支持)。

19. 线程的sleep()方法和yield()方法有什么区别?
  ① sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
  ② 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;
  ③ sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
  ④ sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。

20. 请说出与线程同步以及线程调度相关的方法。
  wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
  sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
  notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
  notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

21. 编写多线程程序的3中方式?

  参见博文:http://www.cnblogs.com/hunterCecil/p/6501757.html  JAVA并发编程学习笔记------线程的三种创建方式

22. Java中如何实现序列化,有什么意义?
  序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决对象流读写操作时可能引发的问题(如果不进行序列化可能会存在数据乱序的问题)。
  要实现序列化,需要让一个类实现Serializable接口,该接口是一个标识性接口,标注该类对象是可被序列化的,然后使用一个输出流来构造一个对象输出流并通过writeObject(Object)方法就可以将实现对象写出(即保存其状态);
  如果需要反序列化则可以用一个输入流建立对象输入流,然后通过readObject方法从流中读取对象。序列化除了能够实现对象的持久化之外,还能够用于对象的深度克隆。

23. 编程实现文件拷贝。(这个题目在笔试的时候经常出现,下面的代码给出了两种实现方案)

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; public final class MyUtil { private MyUtil() {
throw new AssertionError();
} public static void fileCopy(String source, String target) throws IOException {
try (InputStream in = new FileInputStream(source)) {
try (OutputStream out = new FileOutputStream(target)) {
byte[] buffer = new byte[4096];
int bytesToRead;
while((bytesToRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesToRead);
}
}
}
} public static void fileCopyNIO(String source, String target) throws IOException {
try (FileInputStream in = new FileInputStream(source)) {
try (FileOutputStream out = new FileOutputStream(target)) {
FileChannel inChannel = in.getChannel();
FileChannel outChannel = out.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(4096);
while(inChannel.read(buffer) != -1) {
buffer.flip();
outChannel.write(buffer);
buffer.clear();
}
}
}
}
}

24. 什么是DAO模式?
  DAO(Data Access Object)顾名思义是一个为数据库或其他持久化机制提供了抽象接口的对象,在不暴露底层持久化方案实现细节的前提下提供了各种数据访问操作。在实际的开发中,应该将所有对数据源的访问操作进行抽象化后封装在一个公共API中。用程序设计语言来说,就是建立一个接口,接口中定义了此应用程序中将会用到的所有事务方法。在这个应用程序中,当需要和数据源进行交互的时候则使用这个接口,并且编写一个单独的类来实现这个接口,在逻辑上该类对应一个特定的数据存储。DAO模式实际上包含了两个模式,一是Data Accessor(数据访问器),二是Data Object(数据对象),前者要解决如何访问数据的问题,而后者要解决的是如何用对象封装数据。

25. 数据库的事务隔离级别?

  参见博文:http://www.cnblogs.com/hunterCecil/p/7040324.html 数据库的事务隔离(转)
  需要说明的是,事务隔离级别和数据访问的并发性是对立的,事务隔离级别越高并发性就越差。所以要根据具体的应用来确定合适的事务隔离级别,这个地方没有万能的原则。

26. 什么是ORM?
  对象关系映射(Object-Relational Mapping,简称ORM)是一种为了解决程序的面向对象模型与数据库的关系模型互不匹配问题的技术;简单的说,ORM是通过使用描述对象和数据库之间映射的元数据(在Java中可以用XML或者是注解),将程序中的对象自动持久化到关系数据库中或者将关系数据库表中的行转换成Java对象,其本质上就是将数据从一种形式转换到另外一种形式。

27. 获得一个类的类对象有哪些方式?

1)类型.class,例如:String.class
2)对象.getClass(),例如:”hello”.getClass()
3)Class.forName(),例如:Class.forName(“java.lang.String”)

28. 如何通过反射创建对象?
1)通过类对象调用newInstance()方法,例如:String.class.newInstance()
2)通过类对象的getConstructor()或getDeclaredConstructor()方法获得构造器(Constructor)对象并调用其newInstance()方法创建对象,例如:String.class.getConstructor(String.class).newInstance(“Hello”);

二、MyBatis篇:

  参考: http://www.cnblogs.com/hunterCecil/p/8869672.html  

  

三、Spring篇:

  参考:http://www.cnblogs.com/hunterCecil/p/8653113.html

四、 分布式缓存Redis篇

1. 使用Redis有哪些好处?
  (1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1) 
  (2) 支持丰富数据类型,支持string,list,set,sorted set,hash 
  (3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行 
  (4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

2. redis相比memcached有哪些优势?
  (1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型 
  (2) redis的速度比memcached快很多 
  (3) redis可以持久化其数据

3. redis常见性能问题和解决方案:
  (1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件 
  (2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次 
  (3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内 
  (4) 尽量避免在压力很大的主库上增加从库 
  (5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3… 
    这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

4. MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据
  redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。redis 提供 6种数据淘汰策略:
    voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
    volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
    volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
    allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
    allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
    no-enviction(驱逐):禁止驱逐数据

5. Memcache与Redis的区别都有哪些?
  1) 存储方式
    Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。Redis有部份存在硬盘上,这样能保证数据的持久性。
  2) 数据支持类型
    Memcache对数据类型支持相对简单。Redis有复杂的数据类型。
  3)使用底层模型不同
    它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
  4)value大小
    redis最大可以达到1GB,而memcache只有1MB

6. Redis 常见的性能问题都有哪些?如何解决?
  1).Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。
  2).Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。
  3).Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。
  4). Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内

7. redis 最适合的场景
  Redis最适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed的功能,跟传统意义上的持久化有比较大的差别.
  (1)会话缓存(Session Cache):Redis提供持久化.
  (2)全页缓存(FPC):除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降。
  (3)队列:Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。
  (4)排行榜/计数器:Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。
  (5)发布/订阅

五、 其他篇

1. 分布式Session的几种实现方式
  1)基于数据库的Session共享
  2)基于NFS共享文件系统
  3)基于memcached 的session
  4)基于resin/tomcat web容器本身的session复制机制
  5)基于TT/Redis 或 jbosscache 进行 session 共享。
  6)基于cookie 进行session共享

2. Session和Cookie的区别和联系以及Session的实现原理
  1) session保存在服务器,客户端不知道其中的信息;cookie保存在客户端,服务器能够知道其中的信息。   
  2) session中保存的是对象,cookie中保存的是字符串。   
    3) session不能区分路径,同一个用户在访问一个网站期间,所有的session在任何一个地方都可以访问到。而cookie中如果设置了路径参数,那么同一个网站中不同路径下的cookie互相是访问不到的。   
    4) session需要借助cookie才能正常工作,如果客户端完全禁止cookie,session将失效。
http是无状态的协议,客户每次读取web页面时,服务器都打开新的会话,而且服务器也不会自动维护客户的上下文信息,那么要怎么才能实现网上商店中的购物车呢,session就是一种保存上下文信息的机制,它是针对每一个用户的,变量的值保存在服务器端,通过SessionID来区分不同的客户,session是以cookie或URL重写为基础的,默认使用cookie来实现,系统会创造一个名为JSESSIONID的输出cookie,我们叫做session cookie,以区别persistent cookies,也就是我们通常所说的cookie。

3. session的机制?保存session id有几种方式?session什么情况下被创建?何时被删除?
 1)session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。但程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否包含了一个session标识-称为session id,如果已经包含一个session id则说明以前已经为此客户创建过session,服务器就按照session id把这个session检索出来使用(如果检索不到,可能会新建一个,这种情况可能出现在服务端已经删除了该用户对应的session对象,但用户人为地在请求的URL后面附加上一个JSESSION的参数)。如果客户请求不包含session id,则为此客户创建一个session并且生成一个与此session相关联的session id,这个session id将在本次响应中返回给客户端保存。
2)保存session id的几种方式
  a)采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器。
  b) cookie被人为的禁止时,可以采用的URL重写,就是把session id附加在URL路径的后面,附加的方式也有两种,一种是作为URL路径的附加信息,另一种是作为查询字符串附加在URL后面。网络在整个交互过程中始终保持状态,就必须在每个客户端可能请求的路径后面都包含这个session id。
  c) 另一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。
3) session的创建
 server端程序(如Servlet)调用HttpServletRequest.getSession(true)这样的语句时才会被创建。
4)session的删除:session在下列情况下被删除:
  a) 程序调用HttpSession.invalidate()
  b) 距离上一次收到客户端发送的session id时间间隔超过了session的最大有效时间
  c) 服务器进程被停止
  再次注意关闭浏览器只会使存储在客户端浏览器内存中的session cookie失效,不会使服务器端的session对象失效。

参考博文:
http://www.importnew.com/22083.html#comment-628933
https://www.cnblogs.com/huajiezh/p/6415388.html
http://blog.csdn.net/yajlv/article/details/73467865

http://blog.csdn.net/u014352080/article/details/51764311