Java 面试题归类汇总

Java基础

1. String 类为什么是 final 的

final 的出现就是为了为了不想改变,而不想改变的理由有两点:设计(安全)或者效率。

final 修饰的类是不被能继承的,所以 final 修饰的类是不能被篡改的。

1、从设计安全上讲:

  • 确保它们不会在子类中改变语义。String 类是 final 类,这意味着不允许任何人定义String 的子类。换言之,如果有一个 String 的引用,它引用的一定是一个 String 对象,而不可能是其他类的对象。
  • String 一旦被创建是不能被修改的,因为 Java 设计者将 String 为可以共享的

2、从效率上讲:

  • 设计成 final,JVM 才不用对相关方法在虚函数表中查询,而直接定位到 String 类的相关方法上,提高了执行效率。
  • Java 设计者认为共享带来的效率更高。

总而言之,就是要保证 java.lang.String 引用的对象一定是 java.lang.String 的对象,而不是引用它的子孙类,这样才能保证它的效率和安全。

2. HashMap 的源码,实现原理,底层结构。

HashMap 基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

值得注意的是 HashMap 不是线程安全的,如果想要线程安全的 HashMap,可以通过 Collections 类的静态方法 synchronizedMap 获得线程安全的 HashMap。

具体请看:http://www.cnblogs.com/ITtangtang/p/3948406.html

3. 说说你知道的几个Java集合类:list、set、queue、map 实现类。
(1)各种 Collection 和各种 Map 都可以在你向其中添加更多的元素时,自动调整其尺寸。容器不能持有基本类型,但是自动包装机制会仔细地执行基本类型到容器中的所持有的包装器类型之间的双向转换。

(2)ArrayList:大量的随机访问;LinkedList:经常从表中间插入或删除元素;

(3)各种 Queue 以及栈的行为,有 LinkedList 提供支持;

(4)Map 是一种将对象(而非数字)与对象相关联的设计。HashMap:快速访问;TreeMap:保持“键”始终处于排序状态,没有 HashMap 快;LinkedHashMap:保持元素的插入顺序,但也通过散列提供了快速访问能力。

(5)Set 不接受重复元素。HashSet:提供最快的查询速度;TreeSet:保持元素处于排序状态;LinkedHashSet:以插入的顺序保存元素。

(6)Map 与 Collection 之间的唯一重叠就是 Map 可以使用 entrySet() 和 values() 方法来产生 Collection。

具体请看:http://blog.csdn.net/javaxiaojian/article/details/46679411

4. 描述一下 ArrayList 和 LinkedList 各自实现和区别

一般大家都知道 ArrayList 和 LinkedList 的大致区别:

(1)ArrayList 是实现了基于动态数组的数据结构,LinkedList 基于链表的数据结构。

(2)对于随机访问 get 和 set,ArrayList 觉得优于 LinkedList,因为 LinkedList 要移动指针。

(3)对于新增和删除操作 add 和 remove,LinedList 比较占优势,因为 ArrayList 要移动数据。

具体请看:http://www.cnblogs.com/Alexander11/p/5734552.html

5. Java 中的队列都有哪些,有什么区别。

阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来,如从队列中移除一个或者多个元素,或者完全清空队列.

  • ArrayDeque 双向队列
  • LinkedBlockingDeque 阻塞双端队列
  • ArrayBlockingQueue 双向并发阻塞队列
  • LinkedBlockingQueue FIFO队列
  • ConcurrentLinkedQueue 基于链接节点的无界线程安全队列
  • PriorityBlockingQueue 带优先级的无界阻塞队列

还有很多很多,可以看看 AbstractQueue, Deque 有哪些实现类。 推荐ArrayBlockingQueue 。并发性能还不错。

6. 反射中,Class.forName 和 Classloader 的区别。

(1)Class.forName(className)方法,内部实际调用的方法是 Class.forName(className,true,classloader);第2个 boolean 参数表示类是否需要初始化, Class.forName(className) 默认是需要初始化。一旦初始化,就会触发目标对象的 static 块代码执行,static 参数也也会被再次初始化。forName("") 得到的 class 是已经初始化完成的

(2)ClassLoader.loadClass(className) 方法,内部实际调用的方法是 ClassLoader.loadClass(className,false);第2个 boolean 参数,表示目标对象是否进行链接,false 表示不进行链接,由上面介绍可以,不进行链接意味着不进行包括初始化等一些列步骤,那么静态块和静态对象就不会得到执行。loadClass("") 得到的 class 是还没有连接的。

java使用JDBC连接数据库时候,我们首先需要加载数据库驱动。

Class.forName("com.MySQL.jdbc.Driver");//通过这种方式将驱动注册到驱动管理器上
    Connection conn = DriverManager.getConnection("url","userName","password");//通过驱动管理器获得相应的连接

查看com.mysql.jdbc.Driver源码:

public class Driver extends NonRegisteringDriver
  implements java.sql.Driver
{
    //注意,这里有一个static的代码块,这个代码块将会在class初始化的时候执行
  static
  {
    try
    {
        //将这个驱动Driver注册到驱动管理器上
      DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
      throw new RuntimeException("Can't register driver!");
    }
  }
}

Class.forName(“com.mysql.jdbc.Driver”) 方法以后,他会进行 class 的初始化,执行 static 代码块。

也就是说 class 初始化以后,就会将驱注册到 DriverManageer 上,之后才能通过 DriverManager 去获取相应的连接。但是要是我们使用 ClassLoader.loadClass(com.mysql.jdbc.Driver) 的话,不会 link,更也不会初始化 class。相应的就不会将 Driver 注册到 DriverManager 上面,所以肯定不能通过 DriverManager 获取相应的连接。

具体请看:http://blog.csdn.net/xie_xiansheng/article/details/52958012

7. Java7、Java8的新特性

  • 二进制变量的表示,支持将整数类型用二进制来表示,用0b开头。
  • Switch 语句支持 string 类型
  • Try-with-resource 语句
  • Catch 多个异常 说明:Catch 异常类型为 final; 生成 Bytecode 会比多个 catch 小; Rethrow 时保持异常类型

具体请看:http://blog.csdn.net/zhongweijian/article/details/9258997

8. Java 数组和链表两种结构的操作效率,在哪些情况下(从开头开始,从结尾开始,从中间开始),哪些操作(插入,查找,删除)的效率高。

数组效率高

数组开辟的是连续的内存空间,是根据基地址和偏移量来算出地址(有乘法和加法运算),然后访问。

链表前一个数据的地址指向下一个数据地址,如:p = p->next;然后用 *p 访问。按这个说的话,它就一个赋值语句。

具体请看:http://blog.csdn.net/qq_27093465/article/details/52267872

9. Java 内存泄露的问题调查定位:jmap,jstack 的使用等等。

具体请看:http://www.tuicool.com/articles/eu26jeE

10. string、stringbuilder、stringbuffer 区别

  • String:字符串常量
  • StringBuffer:字符串变量
  • StringBuilder:字符串变量

三者在执行速度方面的比较:StringBuilder > StringBuffer > String

StringBuilder:线程非安全的,StringBuffer:线程安全的

具体请看:http://www.cnblogs.com/zhangzongle/p/5912266.html

11. hashtable 和 hashmap 的区别

  • HashMap 不是线程安全的,HashMap 是一个接口 是 map 接口的子接口,是将键映射到值的对象,其中键和值都是对象,并且不能包含重复键,但可以包含重复值。HashMap 允许 null key 和 null value,而 hashtable 不允许。
  • HashTable 是线程安全的一个 Collection。

具体请看:http://www.cnblogs.com/langtianya/archive/2013/03/19/2970273.html

13. 异常的结构,运行时异常和非运行时异常,各举个例子。

(1)运行时异常都是 RuntimeException 类及其子类异常,如 NullPointerException、IndexOutOfBoundsException 等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

(2)非运行时异常是 RuntimeException 以外的异常,类型上都属于 Exception 类及其子类。如 IOException、SQLException 等以及用户自定义的 Exception 异常。对于这种异常,JAVA编译器强制要求我们必需对出现的这些异常进行 catch 并处理,否则程序就不能编译通过。所以,面对这种异常不管我们是否愿意,只能自己去写一大堆 catch 块去处理可能的异常。

具体请看:http://www.tuicool.com/articles/YVZBNfN

14. String 类的常用方法

  • public char charAt(int index) 从一个字符串中取出指定位置的字符。
  • public char[] toCharArray() 将一个字符串变为字符数组
  • public byte[] getBytes() 将一个字符串变为字节数组

具体请看:http://blog.csdn.net/lishiyuzuji/article/details/8135554

15. Java 的引用类型有哪几种

  • 强引用(StrongReference)
  • 软引用(SoftReference)
  • 弱引用(WeakReference)
  • 虚引用(PhantomReference)

具体请看:http://blog.csdn.net/coding_or_coded/article/details/6603549

16. 抽象类和接口的区别

(1)抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。

(2)抽象类要被子类继承,接口要被类实现。

(3)接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现

(4)接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。

(5)抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。

(6)抽象方法只能申明,不能实现,接口是设计的结果 ,抽象类是重构的结果

(7)抽象类里可以没有抽象方法

  * 抽象类中可以没有抽象方法,但有抽象方法的一定是抽象类。所以,java中 抽象类里面可以没有抽象方法。比如HttpServlet类。
  * 抽象类和普通类的区别就在于,抽象类不能被实例化,就是不能被new出来,即使抽象类里面没有抽象方法。
  * 抽象类的作用在于子类对其的继承和实现,也就是多态。
  * 而没有抽象方法的抽象类的存在价值在于:实例化了没有意义,因为类已经定义好了,不能改变其中的方法体,但是实例化出来的对象却满足不了要求,只有继承并重写了他的子类才能满足要求。所以才把它定义为没有抽象方法的抽象类。

(8)如果一个类里有抽象方法,那么这个类只能是抽象类

(9)抽象方法要被实现,所以不能是静态的,也不能是私有的。

(10)接口可继承接口,并可多继承接口,但类只能单根继承。

具体请看:http://www.cnblogs.com/DreamDrive/p/4109571.html

17. java的基础类型和字节大小

Java基本数据类型

  • int 32bit
  • short 16bit
  • long 64bit
  • byte 8bit
  • char 16bit
  • float 32bit
  • double 64bit
  • boolean 1bit
    (boolean 的备注+翻译)
    This data type represents one bit of information, but its “size” isn’t something that’s precisely defined.(ref)
    这种数据类型代表一个比特的信息,但它的“大小”没有明确的定义。(参考)

具体请看:http://blog.csdn.net/qq_27093465/article/details/52262651

18. Hashtable、HashMap、ConcurrentHashMap 底层实现原理与线程安全问题。

  • 线程不安全的 HashMap
  • 效率低下的 HashTable 容器,HashTable 容器使用 synchronized 来保证线程安全
  • ConcurrentHashMap 的锁分段技术

具体请看:http://blog.csdn.net/qq_27093465/article/details/52279473

19. 如果不让你用 Java Jdk 提供的工具,你自己实现一个 Map,你怎么做。说了好久,说了 HashMap 源代码,如果我做,就会借鉴HashMap的原理,说了一通 HashMap 实现。

参照 HashMap 的实现原理,用数组链表实现。

具体请看:http://blog.csdn.net/vking_wang/article/details/14166593

20. Hash 冲突怎么办?哪些解决散列冲突的方法?

  • 开放地址法
  • 拉链法

具体请看:http://blog.csdn.net/u012104435/article/details/47951357

21. HashMap 冲突很厉害,最差性能,你会怎么解决?从O(n)提升到log(n)。

在 Java 8 中使用常量 TREEIFY_THRESHOLD 来控制是否切换到平衡树来存储。目前,这个常量值是 8,这意味着当有超过 8 个元素的索引一样时,HashMap 会使用树来存储它们。这一动态的特性使得 HashMap 一开始使用链表,并在冲突的元素数量超过指定值时用平衡二叉树替换链表。

具体请参考:

(1)http://blog.csdn.net/cpcpcp123/article/details/52744331

(2)http://www.cnblogs.com/zhuyf87/archive/2012/11/09/2763113.html

22. rehash

在介绍 HashMap 的内部实现机制时提到了两个参数,DEFAULT_INITIAL_CAPACITY 和 DEFAULT_LOAD_FACTOR,DEFAULT_INITIAL_CAPACITY 是 table 数组的容量,DEFAULT_LOAD_FACTOR 则是为了最大程度避免哈希冲突,提高 HashMap 效率而设置的一个影响因子,将其乘以 DEFAULT_INITIAL_CAPACITY 就得到了一个阈值 threshold,当 HashMap 的容量达到 threshold 时就需要进行扩容,这个时候就要进行 ReHash 操作了,可以看到下面 addEntry 函数的实现,当 size 达到 threshold 时会调用 resize 函数进行扩容。

具体请看:http://blog.csdn.net/qq_27093465/article/details/52270519

23. hashCode() 与 equals() 生成算法、方法怎么重写。

那为什么在重写 equals 方法时都要重写 hashCode 方法呢:

首先 equals 与 hashcode 间的关系是这样的:

  • 如果两个对象相同(即用 equals 比较返回 true),那么它们的 hashCode 值一定要相同;
  • 如果两个对象的 hashCode 相同,它们并不一定相同(即用 equals 比较返回 false)

自我的理解:由于为了提高程序的效率才实现了 hashcode 方法,先进行 hashcode 的比较,如果不同,那没就不必在进行 equals 的比较了,这样就大大减少了 equals 比较的

具体请看:http://blog.csdn.net/jing_bufferfly/article/details/50868266

Java IO

1. 讲讲 IO 里面的常见类,字节流、字符流、接口、实现类、方法阻塞。

字节流处理单元为 1 个字节,操作字节和字节数组,而字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符、字符数组或字符串。程序中的输入输出都是以流的形式保存的,流中保存的实际上全都是字节文件。所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件(特别是文本文件)时,也是一个字节一个字节地读取以形成字节序列。那么既然磁盘存储都是按字节,内存中处理为何还需要字符流呢?字符流是由Java虚拟机将字节转化为 2 个字节的 Unicode 字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点!

具体请看:http://blog.csdn.net/sinat_26888717/article/details/47999637

2. 讲讲NIO

Java NIO 是一个用来替代标准 Java IO API 的新型数据传递方式,像现在分布式架构中会经常存在他的身影。其比传统的IO更加高效,非阻塞,异步,双向。Java NIO 的主要构成核心就是 Buffer、Channel 和 Selector 这三个。对于 Channel 我想要提醒的是,Channel 中的数据总是要先读到一个 Buffer,或者总是要从一个 Buffer 中写入。使用 Selector,得向 Selector 注册 Channel,然后调用它的 select() 方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件。

具体请看:

3. String 编码 UTF-8 和 GBK 的区别?

  • UTF-8 是用以解决国际上字符的一种多字节编码,它对英文使用8位(即一个字节),中文使用24为(三个字节)来编码。
  • GBK 是国家标准 GB2312 基础上扩容后兼容 GB2312 的标准。GBK 的文字编码是用双字节来表示的,即不论中、英文字符均使用双字节来表示,为了区分中文,将其最高位都设定成1。GBK 包含全部中文字符,是国家编码,通用性比 UTF-8 差,不过 UTF-8 占用的数据库比 GBD 大。GBK 编码能够用来同时表示繁体字和简体字,而 GB2312 只能表示简体字,GBK 是兼容 GB2312 编码的。

具体请看:http://blog.csdn.net/xiongchao2011/article/details/7276834

4. 什么时候使用字节流、什么时候使用字符流?

  • InputStream 和 OutputStream,两个是为字节流设计的,主要用来处理字节或二进制对象。
  • 字节流处理单元为1个字节,操作字节和字节数组。
  • Reader和 Writer,两个是为字符流(一个字符占两个字节)设计的,主要用来处理字符或字符串。
  • 字符流处理的单元为2个字节的 Unicode 字符,操作字符、字符数组或字符串。

具体请看:http://blog.csdn.net/qq_27093465/article/details/52472506

5. 递归读取文件夹下的文件,代码怎么实现?

/**
     * 递归读取文件夹下的文件
     *
     * @param fileDir 文件名或目录名
     */
    private static void recursiveSearchFiles(String fileDir) {
        if (TextUtils.isEmpty(fileDir)) {
            //因为new File(null)会空指针异常,所以要判断下
            return;
        }
        File[] fileList = new File(fileDir).listFiles();
        if (fileList == null || fileList.length <= 0) {
            return;
        }
        for (File file : fileList) {
            if (file.isFile()) {
                Log.d(TAG, "[recursiveSearchFiles] file's name = " + file.getName());
            } else if (file.isDirectory()) {
                Log.d(TAG, "[recursiveSearchFiles] this is a directory");
                recursiveSearchFiles(file.getPath());
            } else {
                Log.d(TAG, "[recursiveSearchFiles] file error");
            }
        }
    }

Java Web

1. session和cookie的区别和联系,session的生命周期,多个服务部署时session管理。

Session 是存放在服务器端的,类似于Session结构来存放用户数据,当浏览器 第一次发送请求时,服务器自动生成了一个Session和一个Session ID用来唯一标识这个Session,并将其通过响应发送到浏览器。当浏览器第二次发送请求,会将前一次服务器响应中的Session ID放在请求中一并发送到服务器上,服务器从请求中提取出Session ID,并和保存的所有Session ID进行对比,找到这个用户对应的Session。

cookie 和 session 的区别:

(1)cookie 数据存放在客户的浏览器上,session 数据放在服务器上。

(2)cookie 不是很安全,别人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗
考虑到安全应当使用 session。

(3)session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能
考虑到减轻服务器性能方面,应当使用 COOKIE。

(4)单个 cookie 保存的数据不能超过4K,很多浏览器都限制一个站点最多保存 20 个 cookie。

当服务器发现 Session 超过了它的生命周期,就会释放该 Session 所占用的内存空间。

Session的销毁只有两种情况:

  • session 调用了 session.invalidate() 方法。
  • 前后两次请求超出了 session 指定的生命周期时间。

Session 生成后,只要用户继续访问,服务器就会更新 Session 的最后访问时间,并维护该 Session。用户每访问服务器一次,无论是否读写 Session,服务器都认为该用户的 Session "活跃(active)"了一次。

注意:新开的浏览器窗口会生成新的 Session,但子窗口除外。子窗口会共用父窗口的 Session。例如,在链接上右击,在弹出的快捷菜单中选择"在新窗口中打开"时,子窗口便可以访问父窗口的 Session。

多个服务部署时 session 管理有以下几种方式:

  • Session 复制,Web服务器之间同步 session 信息。
  • 负载均衡支持会话亲和,相同的会话请求发送给同一个 Web 服务器。
  • Session 不存在 Web 服务器本地,而是放在缓存服务器如 Redis 上。
  • 可以用 iphash,这样不存在 session 共享问题,跟原来一样用,建议用 memcache 或者 redis(去掉持久化)

2. servlet的一些相关问题

  • Servlet 是 SUN 推出的一套规范,规定了如何用 Java 来开发动态网站。由 javax.servlet 和 javax.servlet.http 两个包中的类来实现。
    通过Servlet,你可以:

    • 接收用户通过 form 表单提交的信息;
    • 查询数据库,包括用户信息、文章内容、页面点击次数等;
    • 生成验证码,防止机器恶意注册。
  • get和post方法的区别是什么?

    • get方法提交的参数会通过url传递,而post提交的内容是放在请求体中的。
    • get传送的数据量比较小,post传送的数据量比较大,通常认为不受限制。
    • get方法不安全因为将传送数据暴露在url上,而post则不存在这个问题。
    • 超链接和表单的默认提交方式都是get。

具体请看:

3. webservice 相关问题

WebService 是一个 SOA(面向服务的编程)的架构,它是不依赖于语言,不依赖于平台,可以实现不同的语言间的相互调用,通过 Internet 进行基于 Http 协议的网络应用间的交互。
WebService 实现不同语言间的调用,是依托于一个标准,webservice 是需要遵守 WSDL(web服务定义语言)/SOAP(简单请求协议)规范的。

WebService=WSDL+SOAP+UDDI(webservice的注册)

Soap 是由 Soap 的 part 和 0 个或多个附件组成,一般只有 part,在 part 中有 Envelope 和 Body。

Web Service 是通过提供标准的协议和接口,可以让不同的程序集成的一种 SOA 架构。

Web Service 的优点:

  • 可以让异构的程序相互访问(跨平台)
  • 松耦合
  • 基于标准协议(通用语言,允许其他程序访问)

Web Service 的基本原理:

  • Service Provider 采用 WSDL 描述服务
  • Service Provider 采用 UDDI 将服务的描述文件发布到 UDDI 服务器(Register server)
  • Service Requestor 在 UDDI 服务器上查询并 获取 WSDL 文件
  • Service requestor 将请求绑定到 SOAP,并访问相应的服务。

问题举例:

1、什么是 WSDL

webservice 定义语言,对应 .wsdl 文档,一个 webservice 会对应一个唯一的 wsdl 文档,定义了客户端与服务端发送请求和响应的数据格式和过程

2、如何发布一个 webservice

定义 SEI @webService @webMethod
定义 SEI 的实现
发布:Endpoint.publish(url, SEIImplObject);

3、如何请求一个 webservice

根据 wsdl 文档生成客户端代码,使用客户代码调用 webservice

还可以看:http://blog.csdn.net/t0404/article/details/52049544

4. jdbc 连接,forname 方式的步骤,怎么声明使用一个事务。

  • 加载数据库驱动程序:各个数据库都会提供 JDBC 的驱动程序开发包,直接把 JDBC 操作所需要的开发包(一般为*.jar或*.zip)直接配置到 classpath 路径即可。
  • 连接数据库:根据各个数据库的不同连接的地址也不同,此连接地址将由数据库厂商提供,一般在使用 JDBC 连接数据库的时候都要求用户输入数据库连接的用户名和密码,用户在取得连接之后才可以对数据库进行查询或更新的操作。
  • 使用语句进行数据库操作:数据库操作分为更新和查询两种操作,除了可以使用标准的SQL语句之外,对于各个数据库也可以使用其自己提供的各种命令。
  • 关闭数据库连接:数据库操作完毕之后需要关闭连接以释放资源。
public class DBHelper {  
    public static final String DBDRIVER = "com.mysql.jdbc.Driver";  
    public static final String DBURL = "jdbc:mysql://localhost:3306/test";  
    public static final String DBUSER = "root";  
    public static final String DBPASS = "1111";  

    public static Connection getConnection(){  
        Connection conn = null;  
        try {  
            Class.forName(DBDRIVER);  
            conn = DriverManager.getConnection(DBURL, DBUSER, DBPASS);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return conn;  
    }   

    public static void save(){  
        Connection con = getConnection();  
        String sql = "insert into Info values(?, ?, ?)";  
        PreparedStatement psmt = null;  
        try {  
            psmt = con.prepareStatement(sql);  
            psmt.setString(1, "123");  
            psmt.setString(2, "12");  
            psmt.setInt(3, 3);  
            int count = psmt.executeUpdate();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }finally{  
            if(psmt != null){  
                try {  
                    psmt.close();  
                } catch (SQLException e) {  
                    e.printStackTrace();  
                }  
            }  
            if(con != null){  
                try {  
                    con.close();  
                } catch (SQLException e) {  
                    e.printStackTrace();  
                }  
            }  
        }  
    }  

    public static void delete(){  
        Connection con = getConnection();  
        String sql = "delete from Info where id=?";  
        PreparedStatement psmt = null;  
        try {  
            psmt = con.prepareStatement(sql);  
            psmt.setString(1, "10330070");  
            int count = psmt.executeUpdate();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }finally{  
            if(psmt != null){  
                try {  
                    psmt.close();  
                } catch (SQLException e) {  
                    // TODO Auto-generated catch block  
                    e.printStackTrace();  
                }  
            }  
            if(con != null){  
                try {  
                    con.close();  
                } catch (SQLException e) {  
                    // TODO Auto-generated catch block  
                    e.printStackTrace();  
                }  
            }  
        }  
    }  

    public static void update(){  
        Connection con = getConnection();  
        String sql = "update Info set age = ? where id = ?";  
        PreparedStatement psmt = null;  
        try {  
            psmt = con.prepareStatement(sql);  
            psmt.setInt(1, 22);  
            psmt.setString(2, "111313");  
            int count = psmt.executeUpdate();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }finally{  
            if(psmt != null){  
                try {  
                    psmt.close();  
                } catch (SQLException e) {  
                    e.printStackTrace();  
                }  
            }  
            if(con != null){  
                try {  
                    con.close();  
                } catch (SQLException e) {  
                    e.printStackTrace();  
                }  
            }  
        }  
    }  

    public static List<Info> query(){  
        List<Info> list = new ArrayList<Info>();  
        Connection con = getConnection();  
        String sql = "select * from Info";  
        PreparedStatement psmt = null;  
        ResultSet rs = null;  

        try {  
            psmt = con.prepareStatement(sql);  
            rs = psmt.executeQuery();  

            while(rs.next()){    //依次取出数据  
                Info info = new Info();  
                info.setId(rs.getString("id"));  
                info.setPass(rs.getString("pass"));  
                info.setAge(rs.getInt("age"));  
                System.out.println(info.getId() +"\t" + info.getPass() + "\t" + info.getAge());  
                list.add(info);  
            }  
        } catch (Exception e) {  
            e.printStackTrace();  
        }finally{  
            if(rs != null){  
                try {  
                    rs.close();  
                } catch (SQLException e) {  
                    e.printStackTrace();  
                }  
            }  
            if(psmt != null){  
                try {  
                    psmt.close();  
                } catch (SQLException e) {  
                    e.printStackTrace();  
                }  
            }  
            if(con != null){  
                try {  
                    con.close();  
                } catch (SQLException e) {  
                    e.printStackTrace();  
                }  
            }  
        }  
        return list;  
    }  

    public static void main(String[] args){  
        System.out.println(query().get(0).getAge());  

    }  
}  

具体请看:http://blog.csdn.net/joywy/article/details/7731305#

5. 无框架下配置web.xml的主要配置内容

  • 启动一个WEB项目的时候,WEB容器会去读取它的配置文件web.xml,读取两个结点。
  • 紧急着,容创建一个ServletContext(servlet上下文),这个web项目的所有部分都将共享这个上下文。
  • 容器将转换为键值对,并交给servletContext。
  • 容器创建中的类实例,创建监听器。

具体请看:https://my.oschina.net/u/1383439/blog/224448

6. jsp 和 servlet 的区别

JSP 是 Servlet 技术的扩展,本质上就是 Servlet 的简易方式。JSP 编译后是 “类servlet”。Servlet 和 JSP 最主要的不同点在于,Servlet 的应用逻辑是在 Java 文件中,并且完全从表示层中的 HTML 里分离开来。而 JSP 的情况是 Java 和 HTML 可以组合成一个扩展名为 .jsp 的文件。JSP 侧重于视图,Servlet 主要用于控制逻辑。

JVM

1. Java的内存模型以及GC算法

按照官方的说法:Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。

JVM主要管理两种类型内存:堆和非堆,堆内存(Heap Memory)是在 Java 虚拟机启动时创建,非堆内存(Non-heap Memory)是在JVM堆之外的内存。

简单来说,堆是 Java 代码可及的内存,留给开发人员使用的;非堆是JVM留给自己用的,包含方法区、JVM内部处理或优化所需的内存(如 JIT Compiler,Just-in-time Compiler,即时编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码。
JVM 内存包含如下几个部分:

  • 堆内存(Heap Memory): 存放 Java 对象
  • 非堆内存(Non-Heap Memory): 存放类加载信息和其它 meta-data
  • 其它(Other): 存放JVM 自身代码等

在 JVM 启动时,就已经保留了固定的内存空间给 Heap 内存,这部分内存并不一定都会被JVM使用,但是可以确定的是这部分保留的内存不会被其他进程使用,这部分内存大小由-Xmx 参数指定。而另一部分内存在 JVM 启动时就分配给JVM,作为JVM的初始 Heap 内存使用,这部分内存是由 -Xms 参数指定。

垃圾回收算法

  • 标记-清除算法(Mark-Sweep)

从根节点开始标记所有可达对象,其余没标记的即为垃圾对象,执行清除。但回收后的空间是不连续的。

  • 复制算法(copying)

将内存分成两块,每次只使用其中一块,垃圾回收时,将标记的对象拷贝到另外一块中,然后完全清除原来使用的那块内存。复制后的空间是连续的。复制算法适用于新生代,因为垃圾对象多于存活对象,复制算法更高效。在新生代串行垃圾回收算法中,将eden中标记存活的对象拷贝未使用的s1中,s0中的年轻对象也进入s1,如果s1空间已满,则进入老年代;这样交替使用s0和s1。这种改进的复制算法,既保证了空间的连续性,有避免了大量的内存空间浪费。

  • 标记-压缩算法(Mark-compact)

适合用于老年代的算法(存活对象多于垃圾对象)。
标记后不复制,而是将存活对象压缩到内存的一端,然后清理边界外的所有对象。

  • 分代回收算法

在 HotSpot 虚拟机中,基于分代的特点(堆内存可进一步分为年轻代、老年代,老年代存放存活时间较长的对象),JVM GC使用分代回收算法。

具体请看:http://blog.csdn.net/kingofworld/article/details/17718587

2. jvm性能调优都做了什么

  • 控制 GC 的行为。GC 是一个后台处理,但是它也是会消耗系统性能的,因此经常会根据系统运行的程序的特性来更改GC行为
  • 控制 JVM 堆栈大小。一般来说,JVM 在内存分配上不需要你修改,(举例)但是当你的程序新生代对象在某个时间段产生的比较多的时候,就需要控制新生代的堆大小。同时,还要需要控制总的 JVM 大小避免内存溢出
  • 控制 JVM 线程的内存分配。如果是多线程程序,产生线程和线程运行所消耗的内存也是可以控制的,需要通过一定时间的观测后,配置最优结果

调优策略

两个基本原则:

  • 将转移到老年代的对象数量降到最少。
  • 减少 Full GC 的执行时间。目标是 Minor GC 时间在 100ms 以内,Full GC 时间在 1s 以内。

主要调优参数:

  • 设定堆内存大小,这是最基本的。

    • -Xms:启动 JVM 时的堆内存空间。
    • -Xmx:堆内存最大限制。
  • 设定新生代大小。新生代不宜太小,否则会有大量对象涌入老年代。

    • -XX:NewRatio:新生代和老年代的占比。
    • -XX:NewSize:新生代空间。
    • -XX:SurvivorRatio:伊甸园空间和幸存者空间的占比。
    • -XX:MaxTenuringThreshold:对象进入老年代的年龄阈值。
  • 设定垃圾回收器

    • 年轻代:-XX:+UseParNewGC。
    • 老年代:-XX:+UseConcMarkSweepGC。

CMS 可以将 STW 时间降到最低,但是不对内存进行压缩,有可能出现“并行模式失败”。比如老年代空间还有 300MB 空间,但是一些 10MB 的对象无法被顺序的存储。这时候会触发压缩处理,但是 CMS GC 模式下的压缩处理时间要比 Parallel GC 长很多。
G1 采用”标记-整理“算法,解决了内存碎片问题,建立了可预测的停顿时间类型,能让使用者指定在一个长度为 M 毫秒的时间段内,消耗在垃圾收集上的时间不得超过N毫秒。

3. 介绍 JVM 中7个区域,然后把每个区域可能造成内存的溢出的情况说明。

器池堆,栈栈区区

三个私有区:指令计数器,线程池,本地线程池

四个共享区:方法区,常量池,直接内存区,本地线程栈

堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的

在JVM中堆之外的内存称为非堆内存(Non-heap memory)

  • Java 堆溢出

参数- XX:+ HeapDumpOnOutOfMemoryError

先通过内存映像分析工具(如 Eclipse Memory Analyzer) 对 Dump 出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,也就是要先分清楚到底是出现了内存泄漏( Memory Leak) 还是内存溢出( Memory Overflow)。

如果是内存泄露,可进一步通过工具查看泄露对象到 GC Roots 的引用链。

如果不存在泄露,换句话说,就是内存中的对象确实都还必须存活着,那就应当检查虚拟机的堆参数(- Xmx 与- Xms)

  • 虚拟机栈和本地方法栈溢出

在 HotSpot 虚拟机中并不区分虚拟机栈和本地方法栈,因此,对于 HotSpot 来说,虽然- Xoss 参数(设置本地方法栈大小)存在,但实际上是无效的,栈容量只由- Xss 参数设定。

如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常。如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出 OutOfMemoryError 异常。

限制于单线程中的操作,尝试了下面两种方法均无法让虚拟机产生 OutOfMemoryError 异常,尝试的结果都是获得 StackOverflowError 异常

在单个线程下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是 StackOverflowError 异常。

2GB 内存容量( 操作系统限制)减去 Xmx( 最大堆容量),再减去 MaxPermSize( 最大方法区容量),程序计数器消耗内存很小,可以忽略掉。如果虚拟机进程本身耗费的内存不计算在内,剩下的内存就由虚拟机栈和本地方法栈“瓜分”了。

如果是建立过多线程导致的内存溢出,在不能减少线程数或者更换 64 位虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。

  • 方法区和运行时常量池溢出

运行时常量池是方法区的一部分

在 JDK 1.6 及之前的版本中,由于常量池分配在永久代内,我们可以通过- XX: PermSize 和- XX: MaxPermSize 限制方法区大小,从而间接限制其中常量池的容量

" PermGen space", 说明运行时常量池属于方法区( HotSpot 虚拟机中的永久代)的一部分。

而使用 JDK 1.7 运行这段程序就不会得到相同的结果, while 循环将一直进行下去。

在 JDK 1.6 中, intern() 方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中这个字符串实例的引用,而由 StringBuilder 创建的字符串实例在 Java 堆上,所以必然不是同一个引用

而 JDK 1.7( 以及部分其他虚拟机,例如 JRockit) 的 intern() 实现不会再复制实例,只是在常量池中记录首次出现的实例引用,因此 intern() 返回的引用和由 StringBuilder 创建的那个字符串实例是同一个。

方法区用于存放 Class 的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。

一个类要被垃圾收集器回收掉,判定条件是比较苛刻的。在经常动态生成大量 Class 的应用中,需要特别注意类的回收状况。

  • 本机直接内存溢出(即系统内存)

DirectMemory 容量可通过- XX: MaxDirectMemorySize 指定,如果不指定,则默认与 Java 堆最大值(- Xmx 指定)一样

如果读者发现 OOM 之后 Dump 文件很小,而程序中又直接或间接使用了 NIO, 那就可以考虑检查一下是不是这方面的原因。

具体请看:

4. 介绍 GC 和 GC Root 不正常引用

GC(Garbage Collector) roots,特指的是垃圾收集器(Garbage Collector)的对象,GC会收集那些不是 GC roots 集合且没有被 GC roots 引用(即不可达对象)的对象。

GC root 集合有几下种:

  • Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的,他们可以以静态字段的方式保存持有其它对象。我们需要注意的一点就是,通过用户自定义的类加载器加载的类,除非相应的 java.lang.Class 实例以其它的某种(或多种)方式成为 roots,否则它们并不是 roots,.
  • Thread - 活着的线程
  • Stack Local - Java 方法的 local 变量或参数
  • JNI Local - JNI 方法的 local 变量或参数
  • JNI Global - 全局 JNI 引用
  • Monitor Used - 用于同步的监控对象
  • Held by JVM - 用于 JVM 特殊目的由 GC 保留的对象,但实际上这个与 JVM 的实现是有关的。可能已知的一些类型是:系统类加载器、一些 JVM 知道的重要的异常类、一些用于处理异常的预分配对象以及一些自定义的类加载器等。然而,JVM 并没有为这些对象提供其它的信息,因此就只有留给分析分员去确定哪些是属于"JVM 持有"的了。

5. 自己从 classload 加载方式,加载机制说开去,从程序运行时数据区,讲到内存分配,讲到 String 常量池,讲到 JVM 垃圾回收机制,算法,hotspot。

(1)加载方式

当一个 JVM 启动的时候,Java 缺省开始使用如下三种类型类装入器:

  • 启动(Bootstrap)类加载器:引导类装入器是用本地代码实现的类装入器,它负责将 <Java_Runtime_Home>/lib 下面的核心类库或 -Xbootclasspath 选项指定的 jar 包加载到内存中。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
  • 扩展(Extension)类加载器:扩展类加载器是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将< Java_Runtime_Home >/lib/ext 或者由系统变量 -Djava.ext.dir 指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
  • 系统(System)类加载器:系统类加载器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径 java -classpath 或 -Djava.class.path 变量所指的目录下的类库加载到内存中。开发者可以直接使用系统类加载器。

除了以上列举的三种类加载器,还有一种比较特殊的类型就是线程上下文类加载器,这个将在后面单独介绍。

在这里,需要着重说明的是,JVM 在加载类时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

(2)运行数据区

Java 运行数据区:https://my.oschina.net/stephenzhang/blog/380106?p=NaN

(3)String 常量池

在 JAVA 中,有六个不同的地方可以存储数据:

  • 寄存器(register)。 这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部。但是寄存器的数量极其有限,所以寄存器由编译器根据需求进行分配。你不能直接控制,也不能在程序中感觉到寄存器存在的任何迹象。

------最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制.

  • 堆栈(stack)。位于通用 RAM 中,但通过它的“堆栈指针”可以从处理器哪里获得支持。堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些 内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序时候,JAVA 编译器必须知道存储在堆栈内所有数据的确切大小和生命周期,因为它必须生成 相应的代码,以便上下移动堆栈指针。这一约束限制了程序的灵活性,所以虽然某些 JAVA 数据存储在堆栈中——特别是对象引用,但是 JAVA 对象不存储其 中。

------存放基本类型的变量数据和对象,数组的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中)

  • 堆(heap)。一种通用性的内存池(也存在于 RAM 中),用于存放所以的 JAVA 对象。堆不同于堆栈的好处是:编译器不需要知道要从堆里分配多少存储区 域,也不必知道存储的数据在堆里存活多长时间。因此,在堆里分配存储有很大的灵活性。当你需要创建一个对象的时候,只需要new写一行简单的代码,当执行 这行代码时,会自动在堆里进行存储分配。当然,为这种灵活性必须要付出相应的代码。用堆进行存储分配比用堆栈进行存储存储需要更多的时间。

------存放所有new出来的对象。

  • 静态存储(static storage)。这里的“静态”是指“在固定的位置”。静态存储里存放程序运行时一直存在的数据。你可用关键字 static 来标识一个对象的特定元素是静态的,但 JAVA 对象本身从来不会存放在静态存储空间里。

------存放静态成员(static 定义的)

  • 常量存储(constant storage)。常量值通常直接存放在程序代码内部,这样做是安全的,因为它们永远不会被改变。有时,在嵌入式系统中,常量本身会和其他部分分割离开,所以在这种情况下,可以选择将其放在ROM中

------存放字符串常量和基本类型常量(public static final)

  • 非 RAM 存储。如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。

------硬盘等永久存储空间 就速度来说,有如下关系:

寄存器 >堆栈 > 堆 > 其它

这里我们主要关心栈,堆和常量池,对于栈和常量池中的对象可以共享,对于堆中的对象不可以共享。

栈中的数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会消失。堆中的对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定,具有很大的灵活性。

对于字符串:其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new 出来的)才能确定的就存储在堆中。对于 equals 相等的字符串,在常量池中永远只有一份,在堆中有多份。

具体请看:

(4)HotSpot

HotSpot 是较新的 Java 虚拟机,用来代替 JIT(Just in Time),可以大大提高 Java 运行的性能。HotSpot 包括一个解释器和两个编译器(client 和 server,二选一的),解释与编译混合执行模式,默认启动解释执行。

具体请看:http://blog.csdn.net/radic_feng/article/details/6929853

6. jvm 如何分配直接内存, new 对象如何不分配在堆而是栈上,常量池解析。

JVM内存分配过程:

  • 1、JVM 会试图为相关 Java 对象在 Eden 中初始化一块内存区域。
  • 2、当 Eden 空间足够时,内存申请结束;否则到下一步。
  • 3、JVM 试图释放在 Eden 中所有不活跃的对象(这属于1或更高级的垃圾回收)。释放后若 Eden 空间仍然不足以放入新对象,则试图将部分 Eden 中活跃对象放入 Survivor 区。
  • 4、Survivor 区被用来作为 Eden 及 Old 的中间交换区域,当 Old 区空间足够时,Survivor 区的对象会被移到 Old 区,否则会被保留在 Survivor 区。
  • 5、当 Old 区空间不够时,JVM 会在 Old 区进行完全的垃圾收集(0级)。
  • 6、完全垃圾收集后,若 Survivor 及 Old 区仍然无法存放从 Eden 复制过来的部分对象,导致 JVM 无法在 Eden 区为新对象创建内存区域,则出现 ”out of memory” 错误。

new 对象如何不分配在堆而是栈上:

  • 栈(stack):局部变量、声明对象的引用名、数组的引用名,定义的类方法中的参数以及局部变量。存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中。)
  • 堆(heap):new 出来的“东西”(如:对象的实体,数组的实体),含成员变量(即所谓的全局变量)
  • 紧接着对象的引用要与对象的实体进行关联:栈中的对象引用中保存了堆中的实体的首地址,因而才可以正常编译、运行。

我们可以通过逃逸分析优化的思路来创建相应的对象。

逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。

栈上分配。我们都知道 Java 中的对象都是在堆上分配的,而垃圾回收机制会回收堆中不再使用的对象,但是筛选可回收对象,回收对象还有整理内存都需要消耗时间。如果能够通过逃逸分析确定某些对象不会逃出方法之外,那就可以让这个对象在栈上分配内存,这样该对象所占用的内存空间就可以随栈帧出栈而销毁,就减轻了垃圾回收的压力。

例子:

class Main {   
  public static void main(String[] args) {     
    example();   
}   
  public static void example() {     
    Foo foo = new Foo(); //alloc     
    Bar bar = new Bar(); //alloc     
    bar.setFoo(foo);   
}}  
class Foo {}  
class Bar {   
  private Foo foo;   
  public void setFoo(Foo foo) {     
    this.foo = foo;   
  }
}

在这个示例中,创建了两个对象(用alloc注释),其中一个作为方法的参数。方法setFoo()接收到foo参数后,保存Foo对象的引用。如果Bar对象保存在堆中,那么Foo的引用将逃逸。但在这种情况下,编译器可以使用逃逸分析确定Bar对象本身并没有逃逸example()的调用。这意味着Foo引用无法逃逸。因此,编译器可以安全地在栈上分配两个对象。

7. 数组多大放在JVM老年代

至于多大放入老年代,有参数可以配置,正常是如果年轻化放不下,直到放老年代

8. 老年代中数组的访问方式

数组访问方式和对象访问一样

9. GC 算法,永久代对象如何 GC , GC 有环怎么处理。

一般的判断对象可回收有2种算法,一个引用计数算法,java是可达性分析算法,解决环的问题

永久代GC的原因:

  • 永久代空间已经满了
  • 调用了System.gc()

注意: 这种GC是full GC 堆空间也会一并被GC一次

具体请看:http://blog.csdn.net/u010833547/article/details/52780438

10. 谁会被 GC ,什么时候 GC。

总体来说是内存使用紧张的时候会进行 GC,即新对象所需内存比剩下的内存大时发生。

  • young GC:当 young gen 中的 eden 区分配满的时候触发。注意 young GC 中有部分存活对象会晋升到 old gen,所以young GC后old gen的占用量通常会有所升高。
  • full GC:当准备要触发一次 young GC 时,如果发现统计数据说之前 young GC 的平均晋升大小比目前 old gen 剩余的空间大,则不会触发 young GC 而是转为触发 full GC(因为HotSpot VM 的 GC 里,除了 CMS 的 concurrent collection 之外,其它能收集 old gen 的 GC 都会同时收集整个 GC 堆,包括 young gen,所以不需要事先触发一次单独的 young GC);或者,如果有 perm gen 的话,要在 perm gen 分配空间但已经没有足够空间时,也要触发一次 full GC;或者 System.gc()、heap dump 带 GC,默认也是触发 full GC。
  • 静态变量属于全局变量,不会被GC回收,它们会一直占用内存。直到程序结束时才会被回收。
  • 在 static 方法中声明的变量应该也属于局部变量,当方法调用完成后,局部变量应该被回收。
  • 局部变量中对象的创建也是堆内存中,在栈内保存一个对象的引用,也会产生垃圾。如果没有此对象没有任何引用的地方才会被回收。
  • GC 对 final 变量的处理与非 final 是一样的。final 只是表示一旦此变量初始化后,就不能再修改了。匿名类使用外部局部变量时,这个变量必须是final类型。

并发 GC 的触发条件就不太一样。以 CMS GC 为例,它主要是定时去检查 old gen 的使用量,当使用量超过了触发比例就会启动一次 CMS GC,对 old gen 做并发收集。

现在来回答下面三个问题

(1)什么时候回收?

上面说到 GC 经常发生的区域是堆区,堆区还可以细分为新生代、老年代,新生代还分为一个 Eden 区和两个 Survivor 区。

  • 对象优先在 Eden 中分配,当 Eden 中没有足够空间时,虚拟机将发生一次 Minor GC,因为 Java 大多数对象都是朝生夕灭,所以 Minor GC 非常频繁,而且速度也很快;

  • Full GC,发生在老年代的GC,当老年代没有足够的空间时即发生 Full GC,发生 Full GC一般都会有一次 Minor GC。大对象直接进入老年代,如很长的字符串数组,虚拟机提供一个 -XX:PretenureSizeThreadhold参数,令大于这个参数值的对象直接在老年代中分配,避免在 Eden 区和两个 Survivor 区发生大量的内存拷贝;

  • 发生 Minor GC 时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则进行一次 Full GC,如果小于,则查看 HandlePromotionFailure 设置是否允许担保失败,如果允许,那只会进行一次 Minor GC,如果不允许,则改为进行一次 Full GC。

(2)哪些内存需要回收

jvm 对不可用的对象进行回收,哪些对象是可用的,哪些是不可用的?Java 并不是采用引用计数算法来判定对象是否可用,而是采用根搜索算法(GC Root Tracing),当一个对象到 GC Roots 没有任何引用相连接,用图论的来说就是从 GC Roots 到这个对象不可达,则证明此对象是不可用的,说明此对象可以被 GC。对于这些不可达对象,也不是一下子就被 GC,而是至少要经历两次标记过程:如果对象在进行根搜索算法后发现没有与 GC Roots 相连接的引用链,那它将会第一次标记并且进行一次筛选,筛选条件是此对象有没有必要执行 finalize() 方法,当对象没有覆盖 finalize() 方法或者 finalize() 方法已经被虚拟机调用执行过一次,这两种情况都被视为没有必要执行 finalize() 方法,对于没有必要执行 finalize() 方法的将会被 GC,对于有必要有必要执行的,对象在 finalize() 方法中可能会自救,也就是重新与引用链上的任何一个对象建立关联即可。

(3)如何回收

选择不同的垃圾收集器,所使用的收集算法也不同。

在新生代中,每次垃圾收集都发现有大批对象死去,只有少量存活,则使用复制算法,新生代内存被分为一个较大的 Eden 区和两个较小的 Survivor 区,每次只使用 Eden 区和一个 Survivor 区,当回收时将 Eden 区和 Survivor 还存活着的对象一次性的拷贝到另一个Survivor区上,最后清理掉 Eden 区和刚才使用过的 Survivor 区,Eden 和 Survivor 的默认比例是 8:1,可以使用 -XX:SurvivorRatio 来设置该比例。

而老年代中对象存活率高,没有额外的空间对它进行分配担保,必须使用“标记-清理”或“标记-整理”算法。

11. 如果想不被 GC 怎么办

只要存在某对象的强引用,就不会GC回收。

12. 如果想在 GC 中生存 1 次怎么办

生存一次,释放掉对象的引用,但是在对象的 finalize 方法中重新建立引用,但是此方法只会被调用一次,所以能在GC中生存 一次

具体请看:http://blog.csdn.net/u010833547/article/details/52780468

开源框架

1. hibernate 和 ibatis 的区别

  • Hibernate 的特点:Hibernate 功能强大,数据库无关性好,O/R 映射能力强,如果你对 Hibernate 相当精通,而且对 Hibernate 进行了适当的封装,那么你的项目整个持久层代码会相当简单,需要写的代码很少,开发速度很快,非常爽。
  • iBATIS 的特点: iBATIS 入门简单,即学即用,提供了数据库查询的自动对象绑定功能,而且延续了很好的 SQL 使用经验,对于没有那么高的对象模型要求的项目来说,相当完美。

hibernate 与 ibatis 的对比:

  • ibatis 非常简单易学,hibernate 相对较复杂,门槛较高。
  • 二者都是比较优秀的开源产品
  • 当系统属于二次开发,无法对数据库结构做到控制和修改,那 ibatis 的灵活性将比 hibernate 更适合
  • 系统数据处理量巨大,性能要求极为苛刻,这往往意味着我们必须通过经过高度优化的 sql 语句(或存储过程)才能达到系统性能设计指标。在这种情况下ibatis会有更好的可控性和表现。
  • ibatis 需要手写 sql 语句,也可以生成一部分,hibernate 则基本上可以自动生成,偶尔会写一些 hql。同样的需求, ibatis 的工作量比 hibernate 要大很多。类似的,如果涉及到数据库字段的修改,hibernate 修改的地方很少,而 ibatis 要把那些 sql mapping 的地方一一修改。
  • 以数据库字段一一对应映射得到的 po 和 hibernte 这种对象化映射得到的po是截然不同的,本质区别在于这种 po 是扁平化的,不像 hibernate 映射的 po 是可以表达立体的对象继承,聚合等等关系的,这将会直接影响到你的整个软件系统的设计思路。
  • hibernate 现在已经是主流 o/r mapping 框架,从文档的丰富性,产品的完善性,版本的开发速度都要强于 ibatis。

具体请看:http://blog.csdn.net/xlgen157387/article/details/44488833

2. 讲讲 mybatis 的连接池

先总结一个原则:mytatis 的连接池最大值 poolMaximumActiveConnections 尽量跟服务器的并发访问量持平以至于大于并发访问量。

原因:在 org.apache.ibatis.datasource.pooled.PooledDataSource 中,popConnection 函数(获取连接)会锁住一个 PoolState 对象,pushConnection 函数(把连接回收到池中,在关闭连接的时候,会调用 PooledConnection 的 invoke 函数<使用的代理模式,invoke 是一个回调函数>,触发 close 函数时调用)也会锁住这个对象。

在 popConnection 的时候:

  • 如果池中有 idle 的,返回之
  • 如果没有,并且池中的 active 连接数小于配置的最大连接数,新建一个连接返回
  • 如果没有 idle 并且连接数已经创建到最大,就不创建新连接。从 acitve connection 列表中返回一个最老的值 state.activeConnections.get(0),看这个连接被取出的时间(check out时间,表示从连接开始使用到目前还未 close)是不是超过 poolMaximumCheckoutTime(配置项,默认是20秒),如果超过,使这个连接失效,并且使用这个连接返回做下一个操作
  • 如果这个连接 check out 时间还未到 poolMaximumCheckoutTime,调用 state 对象的 wait 函数:state.wait(poolTimeToWait);等待被唤醒(在连接 close 的时候会调用 pushConnection 函数,这里会调用 state 对象的 notifyAll,唤醒之后重新进入循环取连接)

源代码比较长就不贴了,有兴趣的同学自己下下来看!

在并发数比连接池的数量大很多的情况下,会导致大量的排除竞争来同步state对象,开销比较大!会直接导致延时大大增加。

具体请看:http://www.xuebuyuan.com/1676551.html

3. spring 框架中需要引用哪些 jar 包,以及这些 jar 包的用途

除了 spring.jar 文件,Spring 还包括有其它13个独立的jar包,各自包含着对应的 Spring 组件,用户可以根据自己的需要来选择组合自己的 jar 包,而不必引入整个 spring.jar 的所有类文件。

具体请看:http://blog.csdn.net/u011074386/article/details/50831126

4. springMVC 的工作原理

springMVC 是一个 MVC 的开源框架,springMVC=struts2+spring,springMVC 就相当于是 Struts2 加上 sring 的整合,但是这里有一个疑惑就是,springMVC 和 spring 是什么样的关系呢?这个在百度百科上有一个很好的解释:意思是说,springMVC 是 spring 的一个后续产品,其实就是spring在原有基础上,又提供了 web 应用的 MVC 模块,可以简单的把 springMVC 理解为是 spring 的一个模块(类似 AOP,IOC 这样的模块),网络上经常会说 springMVC 和 spring 无缝集成,其实 springMVC 就是 spring 的一个子模块,所以根本不需要同 spring 进行整合。

(1)客户端发出一个 http 请求给 web 服务器,web 服务器对 http 请求进行解析,如果匹配 DispatcherServlet 的请求映射路径(在 web.xml 中指定),web 容器将请求转交给 DispatcherServlet.

(2)DipatcherServlet 接收到这个请求之后将根据请求的信息(包括 URL、Http 方法、请求报文头和请求参数 Cookie 等)以及 HandlerMapping 的配置找到处理请求的处理器(Handler)。

(3-4)DispatcherServlet 根据 HandlerMapping 找到对应的 Handler,将处理权交给 Handler(Handler将具体的处理进行封装),再由具体的 HandlerAdapter 对 Handler 进行具体的调用。

(5)Handler 对数据处理完成以后将返回一个 ModelAndView() 对象给 DispatcherServlet。

(6)Handler 返回的 ModelAndView() 只是一个逻辑视图并不是一个正式的视图,DispatcherSevlet 通过 ViewResolver 将逻辑视图转化为真正的视图 View。

(7)Dispatcher 通过 model 解析出 ModelAndView() 中的参数进行解析最终展现出完整的 view 并返回给客户端。

5. springMVC注解的意思

Spring 从2.5版本开始在编程中引入注解,用户可以使用 @RequestMapping, @RequestParam, @ModelAttribute 等等这样类似的注解。到目前为止,Spring 的版本虽然发生了很大的变化,但注解的特性却是一直延续下来,并不断扩展,让广大的开发人员的双手变的更轻松起来,这都离不开 Annotation 的强大作用,今天我们就一起来看看 Spring MVC 4 中常用的那些注解吧。

具体请看:http://aijuans.iteye.com/blog/2160141

6. spring中beanFactory和ApplicationContext的联系和区别

BeanFacotry 是 spring 中比较原始的 Factory。如 XMLBeanFactory 就是一种典型的 BeanFactory。原始的 BeanFactory 无法支持 spring 的许多插件,如 AOP 功能、Web 应用等。

ApplicationContext 接口,它由 BeanFactory 接口派生而来,因而提供 BeanFactory 所有的功能。ApplicationContext 以一种更向面向框架的方式工作以及对上下文进行分层和实现继承, ApplicationContext 包还提供了以下的功能:

  • MessageSource, 提供国际化的消息访问
  • 资源访问,如URL和文件
  • 事件传播
  • 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的 web 层

具体请看:http://blog.csdn.net/hi_kevin/article/details/7325554

7. spring注入的几种方式

  • 接口注入
  • getter,setter方式注入
  • 构造器注入

三种注入方式比较:

(1)接口注入:

  • 接口注入模式因为具备侵入性,它要求组件必须与特定的接口相关联,因此并不被看好,实际使用有限。

(2)Setter 注入:

  • 对于习惯了传统 javabean 开发的程序员,通过 setter 方法设定依赖关系更加直观。
  • 如果依赖关系较为复杂,那么构造子注入模式的构造函数也会相当庞大,而此时设值注入模式则更为简洁。
  • 如果用到了第三方类库,可能要求我们的组件提供一个默认的构造函数,此时构造子注入模式也不适用。

(3)构造器注入:

  • 在构造期间完成一个完整的、合法的对象。
  • 所有依赖关系在构造函数中集中呈现。
  • 依赖关系在构造时由容器一次性设定,组件被创建之后一直处于相对“不变”的稳定状态。
  • 只有组件的创建者关心其内部依赖关系,对调用者而言,该依赖关系处于“黑盒”之中。

具体请看:http://blessht.iteye.com/blog/1162131

8. spring如何实现事物管理的

事务的实现方式共有两种:编码方式;声明式事务管理方式。

基于 AOP 技术实现的声明式事务管理,实质就是:在方法执行前后进行拦截,然后在目标方法开始之前创建并加入事务,执行完目标方法后根据执行情况提交或回滚事务。

声明式事务管理又有两种方式:基于XML配置文件的方式;另一个是在业务方法上进行 @Transactional 注解,将事务规则应用到业务逻辑中。

Spring 的事务管理机制实现的原理,就是通过这样一个动态代理对所有需要事务管理的 Bean 进行加载,并根据配置在 invoke 方法中对当前调用的方法名进行判定,并在 method.invoke 方法前后为其加上合适的事务管理代码,这样就实现了 Spring 式的事务管理。Spring 中的 AOP 实现更为复杂和灵活,不过基本原理是一致的。

具体请看:

9. springIOC

IOC:控制反转也叫依赖注入,IOC 利用 Java 反射机制,AOP 利用代理模式。所谓控制反转是指,本来被调用者的实例是有调用者来创建的,这样的缺点是耦合性太强,IOC 则是统一交给 spring 来管理创建,将对象交给容器管理,你只需要在 spring 配置文件总配置相应的 bean,以及设置相关的属性,让 spring 容器来生成类的实例对象以及管理对象。在 spring 容器启动的时候,spring 会把你在配置文件中配置的 bean 都初始化好,然后在你需要调用的时候,就把它已经初始化好的那些 bean 分配给你需要调用这些 bean 的类。 许多应用都是通过彼此间的相互合作来实现业务逻辑的,如类A要调用类 B 的方法,以前我们都是在类 A 中,通过自身 new 一个类 B,然后在调用类B的方法,现在我们把 new 类 B 的事情交给 spring 来做,在我们调用的时候,容器会为我们实例化。

AOP:面向切面编程。(Aspect-Oriented Programming)
AOP 可以说是对 OOP 的补充和完善。OOP 引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。实现 AOP 的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码,属于静态代理。面向对象编程将程序分解成各个层次的对象,面向切面编程将程序运行过程分解成各个切面。 AOP 从程序运行角度考虑程序的结构,提取业务处理过程的切面,oop 是静态的抽象,aop 是动态的抽象,是对应用执行过程中的步骤进行抽象,,从而获得步骤之间的逻辑划分。

10. spring AOP的原理

切面的意义何在?

首先根据上例,假设我们实现了一个通用的权限检查模块,那么就可以在这层切面上进行统一的集中式权限管理。权限检查模块可以和业务逻辑代码分离,而业务逻辑组件则无需关心权限方面的问题。系统大部分有权限检查模块,用的时候直接拿来用这个切面。也就是说,通过切面,我们可以将系统中各个不同层次上的问题隔离开来,实现统一集约式处理。各切面只需集中于自己领域内的逻辑实现。

这一方面使得开发逻辑更加清晰,专业化分工更加易于进行;另一方面,由于切面的隔离,降低了耦合性,我们就可以在不同的应用中将各个切面组合使用,从而使得代码可重用性大大增强。

具体请看:http://www.cnblogs.com/200911/archive/2012/10/09/2716882.html

11. hibernate中的1级和2级缓存的使用方式以及区别原理(Lazy-Load的理解)

hibernate 的缓存分为一级缓存和二级缓存,一级二级和我们常说的 cpu 的一级二级是不一样的。这里的一级说的是 session 的缓存,是 hibernate 内置的,不能卸载。二级说的是 SessionFactory 中的外置缓存,SessionFactory 的内置缓存是放映射数据和sql语句的,程序不能更改,也不算二级缓存。二级缓存可以配置和更改,并且动态加载和卸载。Hibernate 还为查询结果提供了一个查询缓存,它依赖于第二级缓存。

  • 一级缓存的管理:当应用程序调用 Session 的 save()、update()、savaeOrUpdate()、get() 或 load(),以及调用查询接口的 list()、iterate() 或 filter() 方法时,如果在 Session 缓存中还不存在相应的对象,Hibernate 就会把该对象加入到第一级缓存中。当清理缓存时,Hibernate 会根据缓存中对象的状态变化来同步更新数据库。 Session 为应用程序提供了两个管理缓存的方法: evict(Object obj):从缓存中清除参数指定的持久化对象。 clear():清空缓存中所有持久化对象。
  • Hibernate 二级缓存的管理:
    • Hibernate二级缓存策略的一般过程如下:
      1. 条件查询的时候,总是发出一条 select * from table_name where …. (选择所有字段)这样的 SQL 语句查询数据库,一次获得所有的数据对象。
      2. 把获得的所有数据对象根据ID放入到第二级缓存中。
      3. 当 Hibernate 根据 ID 访问数据对象的时候,首先从 Session 一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。
      4. 删除、更新、增加数据的时候,同时更新缓存。Hibernate 二级缓存策略,是针对于 ID 查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate 提供了针对条件查询的 Query Cache。
    • 什么样的数据适合存放到第二级缓存中?
      1. 很少被修改的数据
      2. 不是很重要的数据,允许出现偶尔并发的数据
      3. 不会被并发访问的数据
      4. 参考数据,指的是供应用参考的常量数据,它的实例数目有限,它的实例会被许多其他类的实例引用,实例极少或者从来不会被修改。
    • 不适合存放到第二级缓存的数据?
      1. 经常被修改的数据
      2. 财务数据,绝对不允许出现并发
      3. 与其他应用共享的数据。

Lazy-Load的理解

通过将 class 的 lazy 属性设置为 true,来开启实体的延迟加载特性。

如果我们运行下面的代码:

User user=(User)session.load(User.class,"1");(1)   
System.out.println(user.getName());                 (2)

当运行到(1)处时,Hibernate 并没有发起对数据的查询,如果我们此时通过一些调试工具(比如 Eclipse 的 Debug 工具),观察此时 user 对象的内存快照,我们会惊奇的发现,此时返回的可能是 User $EnhancerByCGLIB$$bede8986 类型的对象,而且其属性为 null,这是怎么回事?session.load() 方法会返回实体对象的代理类对象,这里所返回的对象类型就是 User 对象的代理类对象。在 Hibernate中通过使用 CGLIB,来实现动态构造一个目标对象的代理类对象,并且在代理类对象中包含目标对象的所有属性和方法,而且所有属性均被赋值为 null。通过调试器显示的内存快照,我们可以看出此时真正的User对象,是包含在代理对象的 CGLIB$CALBACK_0.target 属性中,当代码运行到(2)处时,此时调用 user.getName() 方法,这时通过 CGLIB 赋予的回调机制,实际上调用 CGLIB $CALBACK_0.getName() 方法,当调用该方法时,Hibernate 会首先检查 CGLIB$CALBACK_0.target 属性是否为 null,如果不为空,则调用目标对象的 getName 方法,如果为空,则会发起数据库查询,生成类似这样的 SQL 语句:select * from user where id=’1’;来查询数据,并构造目标对象,并且将它赋值到 CGLIB$CALBACK_0.target 属性中。 这样,通过一个中间代理对象,Hibernate 实现了实体的延迟加载,只有当用户真正发起获得实体对象属性的动作时,才真正会发起数据库查询操作。所以实体的延迟加载是用通过中间代理类完成的,所以只有 session.load() 方法才会利用实体延迟加载,因为只有 session.load() 方法才会返回实体类的代理类对象。

(1)Hibernate 中默认采用延迟加载的情况主要有以下几种:

  • 当调用 Session上的 load() 方法加载一个实体时会采用延迟加载。
  • 当 Session 加载某个实体时,会对这个实体中的集合属性值采用延迟加载。(one-to-many)
  • 当 Session 加载某个实体时,会对这个实体所单端关联(one-to-one, many-to-one)的另一个实体对象采用延迟加载。
  • 第二种和第三种的区别是:第二种情况下取消延时加载的方法是在单方即有set属性的一方的映射文件的set标签后设置懒加载的属性 lazy=“false”;第三种情况则是在多方即有 many-to-one 的一方的映射文件中的 many-to-one 标签后设置 lazy=“false”。

能够懒加载的对象都是被改写过的代理对象,当相关联的 session 没有关闭时,访问这些懒加载对象(代理对象)的属性(getId 和 getClass 除外)hibernate 会初始化这些代理,或用 Hibernate.initialize(proxy) 来初始化代理对象;当相关联的 session 关闭后,再访问懒加载的对象将出现异常。

(2)抓取策略(fetch)

通过配置"抓取策略"来直接影响 session 的 get() 和 load() 方法的查询效果。

单端关联<one-to_one> 上的抓取策略:

可以给单端关联的映射元素添加 fetch 属性。select:延迟加载; join:在同一条 select 语句使用内连接来获得对象的数据和它关联对象的数据,此时关联对象的延迟加载失效。

(3)集合属性上的抓取策略:

select:延迟加载;join:在同一条select语句使用内连接来获得对方的关联集合。此时关联集合上的lazy会失效。subselect:另外发送一条查询语句或子查询抓取。这个策略对HQL的查询也起作用。

12. Hibernate 的原理体系架构,五大核心接口,Hibernate 对象的三种状态转换,事务管理。

五大核心接口

使用 Hibernate 必须会使用的五大接口(Configuration、SessionFactory、Session、Transaction、Query和Criteria)编程

(1)首先我们介绍一下 Configuration 接口:配置 Hibernate,根据其启动 Hibernate,创建 SessionFactory 对象;

SessionFactory sf = new Configuration().configure().buildSessionFactory();
SessionFactory stf = new AnnotationConfiguration().configure().buildSessionFactory();
//当使用注解来实现持久化时则使用AnnotationConfiguration来创建SessionFactory

(2)SessionFactory 接口:初始化 Hibernate,充当数据存储源的代理,创建 session 对象,SessionFactory 是线程安全的,意味着它的同一个实例可以被应用的多个线程共享,是重量级二级缓存;

Session session = sf.openSession();      //创建一个Session

(3)Session接口:负责保存、更新、删除、加载和查询对象,是一个非线程安全的,避免多个线程共享一个session,是轻量级,一级缓存。

session.save(tea);     //具体使用过程中要在前后加上事务,tea为某实体类对象

(4)Transaction接口:管理事务。可以对事务进行提交和回滚;

session.beginTransaction();     //由于Hibernate使用事务所以这里要开启事务
session.getTransaction().commit();   //提交

(5)Query和Criteria接口:执行数据库的查询。Criteria由session创建

Criteria criteria = session.createCriteria(Teacher.class); //参数为被操作的pojo类, 反射机制.
Criteria中可以增加查询条件
criteria.add(Expression.eq("name", "Tom"));
criteria.add(Expression.eq("age", new Integer(20)));
//相当于查询语句:select * from t_user where name='Tom' and sex=20

Hibernate 对象的三种状态转换

Hibernate 对象的三种状态,Hibernate 对象有三种状态,分别是:临时态(Transient)、 持久态(Persistent)、游离态(Detached)。

临时状态:是指从对象通过 new 语句创建到被持久化之前的状态,此时对象不在 Session 的缓存中。

处在此状态的对象具备以下特点:

  • 不在 Session 缓存中,不与任何 Session 实例相关联。
  • 在数据库中没有与之对应的记录。

通常在下列情况下对象会进入临时状态:

  • 通过 new 语句创建新对象。
  • 执行 delete() 方法,对于游离状态的对象,delete() 方法会将其与数据库中对应的记录删除;而对于持久化状态的对象,delete()方法会将其与数据库中对应的记录删除,并将其在 Session 缓存中删除。

例如:Object object = new Object(); 此时 object 为临时状态,数据库中没有对应的数据,Session 缓存中也没有相关联的实例。

持久化状态:是指对象被持久化到 Session 对象被销毁之前的状态,此时对象在 Session 的缓存中。

处在此状态的对象具备以下特点:

  • 在 Session 缓存中,与 Session 实例相关联。
  • 在数据库中有与之对应的记录。
  • Session 在清理缓存的时候,会根据持久化对象的属性变化更新数据库。

通常在下列情况下对象会进入临时状态:

  • 执行 save() 或 saveOrUpdate() 方法,使临时对象转变为持久化对象。
  • 执行 upda() 或 saveOrUpdate() 方法,使游离对象转变为持久化对象。
  • 执行 load() 或 get() 方法,返回的对象都是持久化对象。
  • 执行 find() 方法,返回 List 集合中存放的都是持久化对象。
  • 在允许级联保存的情况下,Session 在清理缓存时会把与持久化对象关联的临时对象转变为持久化对象。

例如:Session session = factory.openSession(); object.setName(“持久化对象”); session.save(object);
此时 object 对象为持久化对象,Session 缓存中有相关联的实例,数据库中有相应的记录。

游离状态:是指从持久化对象的 Session 对象被销毁到该对象消失之前的状态,此时对象不在 Session 的缓存中。

处在此状态的对象具备以下特点:

  • 不在 Session 缓存中,不与任何 Session 实例相关联。
  • 在数据库中有与之对应的记录(前提是没有其他 Session 实例删除该条记录)。

通常在下列情况下对象会进入临时状态:

  • 执行 close() 方法,将 Session 缓存清空,缓存中的所有持久化对象将转变成游离对象。
  • 执行 evict() 方法,能从缓存中删除一个持久化对象,使之转变成游离对象。

例如:session.close();
此时上文的 object 对象为游离对象,Session 缓存中没有有相关联的实例,数据库中有相应的记录。

具体请看:http://blog.csdn.net/liushuijinger/article/details/19051939

事务管理

事务(Transaction)是工作中的基本逻辑单位,可以用于确保数据库能够被正确修改,避免数据只修改了一部分而导致数据不完整,或者在修改时受到用户干扰。作为一名软件设计师,必须了解事务并合理利用,以确保数据库保存正确、完整的数据。数据库向用户提供保存当前程序状态的方法,叫事务提交(commit);当事务执行过程中,使数据库忽略当前的状态并回到前面保存的状态的方法叫事务回滚(rollback)。

具体请看:http://blog.csdn.net/lifaming15/article/details/2564660

多线程

1. Java创建线程之后,直接调用start()方法和run()的区别

两种方法的区别:

(1)start:

用 start 方法来启动线程,真正实现了多线程运行,这时无需等待 run 方法体代码执行完毕而直接继续执行下面的代码。通过调用 Thread 类的 start() 方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到 cpu 时间片,就开始执行 run() 方法,这里方法 run() 称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。

(2)run:

run() 方法只是类的一个普通方法而已,如果直接调用 run 方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待 run 方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。

总结:调用 start 方法方可启动线程,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。这两个方法应该都比较熟悉,把需要并行处理的代码放在 run() 方法中,start() 方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。并且 run() 方法必须是 public 访问权限,返回值类型为 void。

2. 常用的线程池模式以及不同线程池的使用场景

Java 通过 Executors 提供四种线程池,分别为:

  • newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
  • newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

具体请看:http://blog.csdn.net/u011479540/article/details/51867886

3. newFixedThreadPool此种线程池如果线程数达到最大值后会怎么办,底层原理。

创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。

4. 同一个类不同方法都有 synchronized 锁,一个对象是否可以同时访问。或者一个类的 static 构造方法加上 synchronized 之后的锁的影响。

(1)答案是: 不可以!!!

多个线程访问同一个类的 synchronized 方法时,都是串行执行的! 就算有多个 cpu 也不例外! synchronized 方法使用了类java的内置锁,即锁住的是方法所属对象本身。同一个锁某个时刻只能被一个执行线程所获取, 因此其他线程都得等待锁的释放。因此就算你有多余的 cpu 可以执行, 但是你没有锁,所以你还是不能进入 synchronized 方法执行, cpu 因此而空闲。如果某个线程长期持有一个竞争激烈的锁, 那么将导致其他线程都因等待所的释放而被挂起, 从而导致CPU无法得到利用,系统吞吐量低下。因此要尽量避免某个线程对锁的长期占有!

如果想避免对锁的竞争,你可以使用锁分解,锁分段以及减少线程持有锁的时间,如果上诉程序中的 syncMethod1 和 syncMethod2 方法是两个不相干的方法(请求的资源不存在关系),那么这两个方法可以分别使用两个不同的锁。

public class SyncMethod {
    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void syncMethod2() {
        synchronized (lock1) {
            try {
                System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 已经获取内置锁`SyncMethod.this`)");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 即将释放内置锁`SyncMethod.this`)");
        }
    }

    public void syncMethod1() {
        synchronized (lock2) {
            System.out.println("######################## (syncMethod1, 已经获取内置锁`SyncMethod.this`, 并即将退出)");
        }
    }
}

具体请看:http://www.jianshu.com/p/f23a90a79b3a

(2)在 static 方法和非 static 方法前面加 synchronized 到底有什么不同呢?

大家都知道,static 的方法属于类方法,它属于这个 Class(注意:这里的 Class 不是指Class 的某个具体对象),那么 static 获取到的锁,就是当前调用这个方法的对象所属的类(Class,而不再是由这个 Class 产生的某个具体对象了)。而非 static 方法获取到的锁,就是当前调用这个方法的对象的锁了。所以,他们之间不会产生互斥。

pulbic class Something(){
    public synchronized void isSyncA(){}
    public synchronized void isSyncB(){}
    public static synchronized void cSyncA(){}
    public static synchronized void cSyncB(){}
}

那么 Something 类的两个实例 a 与 b 以下面的组合形式调用以上方法,结果会是什么呢?

a. x.isSyncA()与x.isSyncB()
b. x.isSyncA()与y.isSyncA()
c. x.cSyncA()与y.cSyncB()
d. x.isSyncA()与Something.cSyncA()
  • a,都是对同一个实例的 synchronized 域访问,因此不能被同时访问
  • b,是针对不同实例的,因此可以同时被访问
  • c,因为是 static synchronized,所以不同实例之间仍然会被限制,相当于Something.isSyncA() 与 Something.isSyncB() 了,因此不能被同时访问。
      
    那么,第d呢?,书上的 答案是可以被同时访问的,答案理由是 synchronzied 的是实例方法与synchronzied 的类方法由于锁定(lock)不同的原因。个人分析也就是 synchronized 与 static synchronized 相当于两帮派,各自管各自,相互之间就无约束了,可以被同时访问。目前还不是分清楚java内部设计 synchronzied 是怎么样实现的。

结论:

  • A: synchronized static 是某个类的范围,synchronized static cSync{}防止多个线程同时访问这个 类中的 synchronized static 方法。它可以对类的所有对象实例起作用。
  • B: synchronized 是某实例的范围,synchronized isSync(){} 防止多个线程同时访问这个实例中的 synchronized 方法。

具体请看:http://www.cnblogs.com/GYoungBean/p/3589726.html

5. 了解可重入锁的含义,以及 ReentrantLock 和 synchronized 的区别

(1)可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后,内层递归函数仍然有获取该锁的代码,但不受影响。在 JAVA 环境下 ReentrantLock 和 synchronized 都是可重入锁。

(2)如何实现可重入锁?

为每个锁关联一个获取计数器和一个所有者线程,当计数值为 0 的时候,这个负就没有被任何线程持有。当线程请求一个未被持有的锁时,JVM 将记下锁的持有者,并且将获取计数值置为 1,如果同一个线程再次获取这个锁,记数值将递增,退出一次同步代码块,计算值递减,当计数值为 0 时,这个锁就被释放。ReentrantLock 里面有实现。

(3)ReentrantLock 和 synchronized 的区别

synchronized 在修饰代码块的时候需要一个 reference 对象作为锁的对象。在修饰方法的时候默认是当前对象作为锁的对象。在修饰类时候默认是当前类的 Class 对象作为锁的对象。

synchronized 会在进入同步块的前后分别形成 monitorenter 和 monitorexit 字节码指令。在执行 monitorenter 指令时会尝试获取对象的锁,如果此没对象没有被锁,或者此对象已经被当前线程锁住,那么锁的计数器加一,每当 monitorexit 被锁的对象的计数器减一,直到为 0 就释放该对象的锁。由此 synchronized 是可重入的,不会出现自己把自己锁死。

synchronized 在锁定时如果方法块抛出异常,JVM 会自动将锁释放掉,不会因为出了异常没有释放锁造成线程死锁。但是 Lock 的话就享受不到 JVM 带来自动的功能,出现异常时必须在 finally 将锁释放掉,否则将会引起死锁。

在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized 是很合适的。原因在于,编译程序通常会尽可能的进行优化 synchronize,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。

ReentrantLock 提供了多样化的同步,比如有时间限制的同步,可以被 Interrupt 的同步(synchronized 的同步是不能 Interrupt 的)等。在资源竞争不激烈的情形下,性能稍微比synchronized 差点点。但是当同步非常激烈的时候,synchronized 的性能一下子能下降好几十倍。而 ReentrantLock 确还能维持常态。

ReentrantLock 除了 synchronized 的功能,多了三个高级功能:等待可中断,公平锁,绑定多个 Condition。

1)等待可中断

在持有锁的线程长时间不释放锁的时候,等待的线程可以选择放弃等待。tryLock(long timeout, TimeUnit unit)

2)公平锁

按照申请锁的顺序来一次获得锁称为公平锁。synchronized 的是非公平锁,ReentrantLock 可以通过构造函数实现公平锁:new RenentrantLock(boolean fair)

3)绑定多个 Condition

通过多次 newCondition 可以获得多个 Condition 对象,可以简单的实现比较复杂的线程同步的功能,通过 await(),signal()。

6. 同步的数据结构,例如 concurrentHashMap 的源码理解以及内部实现原理,为什么他是同步的且效率高。

通过分析 Hashtable 就知道,synchronized 是针对整张 Hash 表的,即每次锁住整张表让线程独占,ConcurrentHashMap 允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对 hash 表的不同部分进行的修改。ConcurrentHashMap 内部使用段 (Segment) 来表示这些不同的部分,每个段其实就是一个小的 hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。

有些方法需要跨段,比如 size() 和 containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁,在 ConcurrentHashMap 内部,段数组是 final 的,并且其成员变量实际上也是 final 的,但是,仅仅是将数组声明为final的并不保证数组成员也是 final 的,这需要实现上的保证。这可以确保不会出现死锁,因为获得锁的顺序是固定的。

具体请看:http://blog.csdn.net/dingji_ping/article/details/51005799

7. atomicinteger 和 Volatile 等线程安全操作的关键字的理解和使用

Volatile 修饰的成员变量在每次被线程访问时,都强迫从共享内存重新读取该成员的值,而且,当成员变量值发生变化时,强迫将变化的值重新写入共享内存,这样两个不同的线程在访问同一个共享变量的值时,始终看到的是同一个值。

java 语言规范指出:为了获取最佳的运行速度,允许线程保留共享变量的副本,当这个线程进入或者离开同步代码块时,才与共享成员变量进行比对,如果有变化再更新共享成员变量。这样当多个线程同时访问一个共享变量时,可能会存在值不同步的现象。

而 volatile 这个值的作用就是告诉 VM:对于这个成员变量不能保存它的副本,要直接与共享成员变量交互。

建议:当多个线程同时访问一个共享变量时,可以使用 volatile,而当访问的变量已在 synchronized 代码块中时,不必使用。
缺点:使用 volatile 将使得 VM 优化失去作用,导致效率较低,所以要在必要的时候使用。

AtomicInteger 为什么能够达到多而不乱,处理高并发应付自如呢,我们才看看AtomicInteger的源代码:

private volatile int value;  

大家可以看到有这个变量,value就是你设置的自加起始值。注意看它的访问控制符,是volatile,这个就是保证AtomicInteger线程安全的根源。

8. 线程间通信,wait 和 notify

如果对象调用了 wait 方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。

如果对象调用了 notify 方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。

如果对象调用了 notifyAll 方法就会通知所有等待这个对象控制权的线程继续运行。

具体请看:http://blog.csdn.net/ns_code/article/details/17225469

9. 定时线程的使用
这是最常见的,创建一个 thread,然后让它在 while 循环里一直运行着,通过 sleep 方法来达到定时任务的效果。这样可以快速简单的实现。

/**
 * 普通thread
 * 这是最常见的,创建一个thread,然后让它在while循环里一直运行着,
 * 通过sleep方法来达到定时任务的效果。这样可以快速简单的实现,代码如下:
 * @author GT
 *
 */  
public class Task1 {  
    public static void main(String[] args) {  
        // run in a second  
        final long timeInterval = 1000;  
        Runnable runnable = new Runnable() {  
            public void run() {  
                while (true) {  
                    // ------- code for task to run  
                    System.out.println("Hello !!");  
                    // ------- ends here  
                    try {  
                        Thread.sleep(timeInterval);  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                }  
            }  
        };  
        Thread thread = new Thread(runnable);  
        thread.start();  
    }  
}  

用 Timer 和 TimerTask

上面的实现搜索是非常快速简便的,但它也缺少一些功能。用 Timer 和 TimerTask 的话与上述方法相比有如下好处:

  • 当启动和去取消任务时可以控制
  • 第一次执行任务时可以指定你想要的 delay 时间

在实现时,Timer 类可以调度任务,TimerTask 则是通过在 run() 方法里实现具体任务。Timer 实例可以调度多任务,它是线程安全的。当Timer的构造器被调用时,它创建了一个线程,这个线程可以用来调度任务。

/**
 *  
 * 于第一种方式相比,优势 1>当启动和去取消任务时可以控制 2>第一次执行任务时可以指定你想要的delay时间
 *  
 * 在实现时,Timer类可以调度任务,TimerTask则是通过在run()方法里实现具体任务。 Timer实例可以调度多任务,它是线程安全的。
 * 当Timer的构造器被调用时,它创建了一个线程,这个线程可以用来调度任务。 下面是代码:
 *  
 * @author GT
 *  
 */  
public class Task2 {  
    public static void main(String[] args) {  
        TimerTask task = new TimerTask() {  
            @Override  
            public void run() {  
                // task to run goes here  
                System.out.println("Hello !!!");  
            }  
        };  
        Timer timer = new Timer();  
        long delay = 0;  
        long intevalPeriod = 1 * 1000;  
        // schedules the task to be run in an interval  
        timer.scheduleAtFixedRate(task, delay, intevalPeriod);  
    } // end of main  
}  

由于 TimerTask 是以队列的方式一个一个被顺序执行的,所以执行时间可能和预期的时间不一致,如果前面的任务消耗时间较长,则后面的任务运行的时间也会被延迟。

ScheduledExecutorService 实现

/**
 *  
 *  
 * ScheduledExecutorService是从Java SE5的java.util.concurrent里,做为并发工具类被引进的,这是最理想的定时任务实现方式。  
 * 相比于上两个方法,它有以下好处:
 * 1>相比于Timer的单线程,它是通过线程池的方式来执行任务的  
 * 2>可以很灵活的去设定第一次执行任务delay时间
 * 3>提供了良好的约定,以便设定执行的时间间隔
 *  
 * 下面是实现代码,我们通过ScheduledExecutorService#scheduleAtFixedRate展示这个例子,通过代码里参数的控制,首次执行加了delay时间。
 *  
 *  
 * @author GT
 *  
 */  
public class Task3 {  
    public static void main(String[] args) {  
        Runnable runnable = new Runnable() {  
            public void run() {  
                // task to run goes here  
                System.out.println("Hello !!");  
            }  
        };  
        ScheduledExecutorService service = Executors  
                .newSingleThreadScheduledExecutor();  
        // 第二个参数为首次执行的延时时间,第三个参数为定时执行的间隔时间  
        service.scheduleAtFixedRate(runnable, 10, 1, TimeUnit.SECONDS);  
    }  
}  

10. 场景:在一个主线程中,要求有大量(很多很多)子线程执行完之后,主线程才执行完成。多种方式,考虑效率。

那么如何确保所有的子线程执行完毕了。一般的有如下方法:

(1)让主线程等待,或着睡眠几分钟。用 Thread.sleep() 或者 TimeUnit.SECONDS.sleep(5);
如下:

public class ThreadSubMain1 {  

    public static void main(String[] args) {  
        // TODO Auto-generated method stub  

        for (int i = 0; i < 10; i++) {  

            new Thread(new Runnable() {  
                public void run() {  

                    try {  
                        Thread.sleep(1000);  
                        // 模拟子线程任务  
                    } catch (InterruptedException e) {  
                    }  
                    System.out.println("子线程" + Thread.currentThread() + "执行完毕");  
                }  
            }).start();   
        }  

        try {  
            // 等待全部子线程执行完毕  
            TimeUnit.SECONDS.sleep(5);  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        System.out.println("主线执行。");  
    }  
}  

效果如下:

子线程Thread[Thread-1,5,main]执行完毕  
子线程Thread[Thread-3,5,main]执行完毕  
子线程Thread[Thread-5,5,main]执行完毕  
子线程Thread[Thread-7,5,main]执行完毕  
子线程Thread[Thread-9,5,main]执行完毕  
子线程Thread[Thread-0,5,main]执行完毕  
子线程Thread[Thread-2,5,main]执行完毕  
子线程Thread[Thread-4,5,main]执行完毕  
子线程Thread[Thread-6,5,main]执行完毕  
子线程Thread[Thread-8,5,main]执行完毕  
主线执行。  

此方主线程只是睡了 5 秒,但是不能保证全部的子线程执行完成,所以这儿的5秒只是一个估值。

(2)使用 Thread 的 join() 等待所有的子线程执行完毕,主线程在执行。实现如下:

public class ThreadSubMain2 {  

    public static void main(String[] args) {  
        // 使用线程安全的Vector   
        Vector<Thread> threads = new Vector<Thread>();  
        for (int i = 0; i < 10; i++) {  

            Thread iThread = new Thread(new Runnable() {  
                public void run() {  

                    try {  
                        Thread.sleep(1000);  
                        // 模拟子线程任务  
                    } catch (InterruptedException e) {  
                    }  
                    System.out.println("子线程" + Thread.currentThread() + "执行完毕");  

                }  
            });  

            threads.add(iThread);  
            iThread.start();  
        }  

        for (Thread iThread : threads) {  
            try {  
                // 等待所有线程执行完毕  
                iThread.join();  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  

        System.out.println("主线执行。");  
    }  

}  

执行结果如下:

子线程Thread[Thread-1,5,main]执行完毕  
子线程Thread[Thread-2,5,main]执行完毕  
子线程Thread[Thread-0,5,main]执行完毕  
子线程Thread[Thread-3,5,main]执行完毕  
子线程Thread[Thread-4,5,main]执行完毕  
子线程Thread[Thread-9,5,main]执行完毕  
子线程Thread[Thread-7,5,main]执行完毕  
子线程Thread[Thread-5,5,main]执行完毕  
子线程Thread[Thread-8,5,main]执行完毕  
子线程Thread[Thread-6,5,main]执行完毕  
主线执行。

这种方式符合要求,它能够等待所有的子线程执行完,主线程才会执行。

(3)使用 ExecutorService 线程池,等待所有任务执行完毕再执行主线程 awaitTermination。

awaitTermination(long timeout,TimeUnit unit) 请求关闭、发生超时或者当前线程中断,无论哪一个首先发生之后,都将导致阻塞,直到所有任务完成执行。

public class ThreadSubMain3 {  

    public static void main(String[] args) {  
        // 定义一个缓冲的线程值 线程池的大小根据任务变化  
        ExecutorService threadPool = Executors.newCachedThreadPool();  
        for (int i = 0; i < 10; i++) {  

            threadPool.execute(new Runnable() {  
                public void run() {  

                    try {  
                        Thread.sleep(1000);  
                        // 模拟子线程任务  
                    } catch (InterruptedException e) {  
                    }  
                    System.out.println("子线程" + Thread.currentThread() + "执行完毕");  

                }  
            });  

        }  

        // 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。  
        threadPool.shutdown();  

        try {  
            // 请求关闭、发生超时或者当前线程中断,无论哪一个首先发生之后,都将导致阻塞,直到所有任务完成执行  
            // 设置最长等待10秒  
            threadPool.awaitTermination(10, TimeUnit.SECONDS);  
        } catch (InterruptedException e) {  
            //  
            e.printStackTrace();  
        }  

        System.out.println("主线执行。");  
    }  

}  

执行结果如下:

子线程Thread[pool-1-thread-4,5,main]执行完毕  
子线程Thread[pool-1-thread-1,5,main]执行完毕  
子线程Thread[pool-1-thread-7,5,main]执行完毕  
子线程Thread[pool-1-thread-6,5,main]执行完毕  
子线程Thread[pool-1-thread-5,5,main]执行完毕  
子线程Thread[pool-1-thread-2,5,main]执行完毕  
子线程Thread[pool-1-thread-3,5,main]执行完毕  
子线程Thread[pool-1-thread-8,5,main]执行完毕  
子线程Thread[pool-1-thread-10,5,main]执行完毕  
子线程Thread[pool-1-thread-9,5,main]执行完毕  
主线执行。  

这种方法和方法 2 一样,将等待所有子线程执行完毕之后才执行主线程。

(4)使用 Java.util.concurrent 中的 CountDownLatch,是一个倒数计数器。初始化时先设置一个倒数计数初始值,每调用一次 countDown() 方法,倒数值减一,他的 await() 方法会阻塞当前进程,直到倒数至0。

Thread t = new Thread() {
  public void run() {
	try {
        //TODO 你的应用
    } catch (Exception e) {
        //TODO 异常处理
    } finally {
       latch.countDown();  //这句是关键
       System.out.println("ok");  //5个线程都跑完后输出
	}
  }
};
 t.start();

然后让以上操作循环五次(就是说同时开5个线程),那么这个"ok"就会在等到这5个线程都ok后才会被输出一次。

具体请看:http://blog.csdn.net/fengshizty/article/details/41356845?utm_source=tuicool&utm_medium=referral

11. 进程和线程的区别

(1)定义:

  • 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
  • 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
    一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.

(2)区别:

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。

  • 定义方面:进程是程序在某个数据集合上的一次运行活动;线程是进程中的一个执行路径。
  • 角色方面:在支持线程机制的系统中,进程是系统资源分配的单位,线程是系统调度的单位。
  • 资源共享方面:进程之间不能共享资源,而线程共享所在进程的地址空间和其它资源。同时线程还有自己的栈和栈指针,程序计数器等寄存器。
  • 独立性方面:进程有自己独立的地址空间,而线程没有,线程必须依赖于进程而存在。

具体请看:http://blog.csdn.net/zheng548/article/details/54669908

12. 什么叫线程安全?

线程安全就是说多线程访问同一代码,不会产生不确定的结果,即如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。编写线程安全的代码是低依靠线程同步。

若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

13. 线程的几种状态

线程一共有以下几种状态:

(1)新建状态(New):新创建了一个线程对象。

(2)就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的 start() 方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取 CPU 的使用权。即在就绪状态的进程除 CPU 之外,其它的运行所需资源都已全部获得。

(3)运行状态(Running):就绪状态的线程获取了 CPU,执行程序代码。

(4)阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃 CPU 使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

阻塞的情况分三种:

  • 等待阻塞:运行的线程执行 wait() 方法,该线程会释放占用的所有资源,JVM 会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用 notify() 或 notifyAll() 方法才能被唤醒,
  • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
  • 其他阻塞:运行的线程执行 sleep() 或 join() 方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep() 状态超时、join() 等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。

(5)死亡状态(Dead):线程执行完了或者因异常退出了 run() 方法,该线程结束生命周期。

具体请看:http://blog.csdn.net/sinat_36042530/article/details/52565296

14. 并发、同步的接口或方法

具体请看:http://blog.csdn.net/cglthk/article/details/44900835

15. HashMap 是否线程安全,为何不安全。 ConcurrentHashMap,线程安全,为何安全。底层实现是怎么样的。

HashMap 底层是一个 Entry 数组,当发生 hash 冲突的时候,hashmap 是采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入。此实现不是同步的。如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步。

ConcurrentHashMap 对K/V的读写都是加锁的,是一个可重入锁(ReenTrantLock),当然这是一个 Segment (片段锁),只会锁定某一个 K/V,基于 CAS 调度,也就是与 CPU 的直接打交道的,使用的是 NonfairSync,所以能保证最大的吞吐量。

ConcurrentHashMap 允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap 内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的 hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。

有些方法需要跨段,比如 size() 和 containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁,在 ConcurrentHashMap 内部,段数组是 final 的,并且其成员变量实际上也是 final 的,但是,仅仅是将数组声明为 final 的并不保证数组成员也是 final 的,这需要实现上的保证。这可以确保不会出现死锁,因为获得锁的顺序是固定的。不变性是多线程编程占有很重要的地位,下面还要谈到。

16. J.U.C下的常见类的使用。 ThreadPool 的深入考察; BlockingQueue 的使用。(take,poll 的区别,put,offer 的区别);原子类的实现。

(1)Java多线程—JUC包下的常见类请参考:http://blog.csdn.net/u013080921/article/details/42922409

(2)ThreadPool 的深入考察请参考:http://shmilyaw-hotmail-com.iteye.com/blog/1897638

(3)BlockingQueue 的使用:

如果 BlockingQueue 是空的,从 BlockingQueue 取东西的操作将会被阻断进入等待状态,直到 BlockingQueue 进了东西才会被唤醒。同样,如果 BlockingQueue 是满的,任何试图往里存东西的操作也会被阻断进入等待状态,到 BlockingQueue 里有空间才会被唤醒继续操作。

使用BlockingQueue的关键技术点如下:

  • BlockingQueue定义的常用方法如下:
    • add(anObject):把 anObject 加到 BlockingQueue 里,即如果 BlockingQueue 可以容纳,则返回 true,否则报异常。
    • offer(anObject):表示如果可能的话,将 anObject 加到 BlockingQueue 里,即如果 BlockingQueue 可以容纳,则返回 true,否则返回 false。
    • put(anObject):把 anObject 加到 BlockingQueue 里,如果 BlockQueue 没有空间,则调用此方法的线程被阻断直到 BlockingQueue 里面有空间再继续。
    • poll(time):取走 BlockingQueue 里排在首位的对象,若不能立即取出,则可以等 time 参数规定的时间,取不到时返回 null
    • take():取走 BlockingQueue 里排在首位的对象,若 BlockingQueue 为空,阻断进入等待状态直到 Blocking 有新的对象被加入为止。
  • BlockingQueue 有四个具体的实现类,根据不同需求,选择不同的实现类。
    • ArrayBlockingQueue:规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小.其所含的对象是以FIFO(先入先出)顺序排序的.
    • LinkedBlockingQueue:大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定.其所含的对象是以FIFO(先入先出)顺序排序的
    • PriorityBlockingQueue:类似于LinkedBlockQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数的Comparator决定的顺序.
    • SynchronousQueue:特殊的BlockingQueue,对其的操作必须是放和取交替完成的.
    • LinkedBlockingQueue 和 ArrayBlockingQueue 比较起来,它们背后所用的数据结构不一样,导致 LinkedBlockingQueue 的数据吞吐量要大于 ArrayBlockingQueue,但在线程数量很大时其性能的可预见性低于ArrayBlockingQueue。

BlockingQueue 实现主要用于生产者-使用者队列,但它另外还支持 Collection 接口。因此,举例来说,使用 remove(x) 从队列中移除任意一个元素是有可能的。然而,这种操作通常不 会有效执行,只能有计划地偶尔使用,比如在取消排队信息时。

BlockingQueue 实现是线程安全的。所有排队方法都可以使用内部锁或其他形式的并发控制来自动达到它们的目的。然而,大量的 Collection 操作(addAll、containsAll、retainAll 和 removeAll)没有 必要自动执行,除非在实现中特别说明。因此,举例来说,在只添加了 c 中的一些元素后,addAll© 有可能失败(抛出一个异常)。

具体请看:http://wsmajunfeng.iteye.com/blog/1629354

17. 简单介绍下多线程的情况,从建立一个线程开始。然后怎么控制同步过程,多线程常用的方法和结构

具体请看前文相关知识点

18. volatile 的理解

volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其他线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

对于 volatile 修饰的变量,只保证了他的可见性,但不保证原子性。最常用的应该是 boolean 类型,它用来作为状态标志,因为它只有 true 和 false 两个值,不会有非原子性的操作。当然不是说只能用在布尔类型变量上面,其它的基本类型和对象类型都可以用。

  • Volitale:是一个轻量级的 Synchronized,是 Java 语言内部两种同步机制之一,它在多处理器开发中保证了共享变量的额“共享性”。
  • 它是如何实现的呢?答:当我们对声明了 volatile 的变量进行些操作的时候,JMM会向处理器发送一条 Lock 指令的前缀,该指令引发了下列两件事情:
    • a 将当前处理器缓存行数据写会到系统内存;
    • b 这个操作呢会使得其他 cpu 里缓存了改内存地址的数据无效。
  • volatile 的写-读语义是什么呢?答:
    • a 写:当写一个 volatile 变量时,jvm 会将该线程对应的本地内存的共享变量刷新到主内存;
    • b 读:当读一个 volatile 变量时,JMM 会把改线程对应的本地内存置为无效,线程接下来从主内存中读取变量。
  • volatile 内存语义是如何实现的呢?答:JMM 对编译器重排序和处理器重排序进行了限制,编译器在生成字节码时,在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
  • 正确使用 volatile 的条件:对变量的写操作不依赖于当前值;该变量没有包含在具有其他变量的不变式中。

19. 实现多线程有几种方式,多线程同步怎么做,说说几个线程里常用的方法。

(1)最基本的线程实现方式:

  • extends Thread
public class ThreadExtendsThread extends Thread {
  private int i;
  @Override
  public void run() {
    for(; i < 100; i++) {
      System.out.println(getName() + " " + i);
    }
  }
}

  • implements Runnable
public class ThreadImplementsRunnable implements Runnable {
  private int i;
  @Override
  public void run() {
    for(; i < 100; i++){
      System.out.println(Thread.currentThread().getName() + " " + i);
    }
  }
}
  • implements Callable
import java.util.concurrent.Callable;

public class ThreadImplementsCallable implements Callable<Integer> {
  private int i;

  @Override
  public Integer call() throws Exception {
    for(; i < 100; i++){
      System.out.println(Thread.currentThread().getName() + " " + i);
    }
    return i;
  }
}

Runnable 和 Callable 的区别是:

  • Callable 规定的方法是call(),Runnable 规定的方法是 run()。
  • Callable 的任务执行后可返回值,而 Runnable 的任务是不能返回值得
  • call 方法可以抛出异常,run 方法不可以
  • 运行 Callable 任务可以拿到一个 Future 对象,表示异步计算的结果。 它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。 通过 Future 对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

(2)多线程同步怎么做:

  • 同步方法:即有 synchronized 关键字修饰的方法。
  • 同步代码块:即有 synchronized 关键字修饰的语句块。

同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized 代码块同步关键代码即可。

  • 使用特殊域变量(volatile)实现线程同步
    • volatile 关键字为域变量的访问提供了一种免锁机制
    • 使用 volatile 修饰域相当于告诉虚拟机该域可能会被其他线程更新
    • 因此每次使用该域就要重新计算,而不是使用寄存器中的值
    • volatile 不会提供任何原子操作,它也不能用来修饰 final 类型的变量

它的原理是每次要线程要访问 volatile 修饰的变量时都是从内存中读取,而不是存缓存当中读取,因此每个线程访问到的变量值都是一样的。这样就保证了同步。

  • 使用重入锁实现线程同步。在 JavaSE5.0 中新增了一个 java.util.concurrent 包来支持同步。ReentrantLock 类是可重入、互斥、实现了 Lock 接口的锁, 它与使用 synchronized 方法和快具有相同的基本行为和语义,并且扩展了其能力。

如果 synchronized 关键字能满足用户的需求,就用synchronized,因为它能简化代码 。如果需要更高级的功能,就用 ReentrantLock 类,此时要注意及时释放锁,否则会出现死锁,通常在 finally 代码释放锁。

  • 使用局部变量 ThreadLocal 实现线程同步
private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){  

        @Override  
        protected Integer initialValue() {  
            // TODO Auto-generated method stub  
            return 0;  
        }  

    };  

如果使用 ThreadLocal 管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。

具体请看:http://blog.csdn.net/wenwen091100304/article/details/48318699

(3)线程里常用方法:

  • wait():使一个线程处于等待状态,并且释放所持有的对象的 lock。
  • sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉 InterruptedException 异常。
  • notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
  • notifyAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

网络通信

请看这篇文章的总结 Java 位面试题归类汇总-网络通信

数据库MySql

请看这篇文章的总结 Java 位面试题归类汇总-数据库

设计模式

1. 单例模式:饱汉、饿汉。以及饿汉中的延迟加载,双重检查。

(1)第一种(懒汉,线程不安全):

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}  

(2)第二种(懒汉,线程安全):

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}  

(3)第三种(饿汉):

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}  

(4)第四种(饿汉,变种):

public class Singleton {  
    private Singleton instance = null;  
    static {  
    instance = new Singleton();  
    }  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return this.instance;  
    }  
}  

(5)第五种(静态内部类):

public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.INSTANCE;  
    }  
}  

(6)第六种(双重校验锁):

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}  

重点注意

java内存模型(jmm)并不限制处理器重排序,在执行instance=new Singleton();时,并不是原子语句,实际是包括了下面三大步骤:

  • 1.为对象分配内存
    * 2.初始化实例对象
  • 3.把引用instance指向分配的内存空间

这个三个步骤并不能保证按序执行,处理器会进行指令重排序优化,存在这样的情况:
优化重排后执行顺序为:1,3,2, 这样在线程1执行到3时,instance已经不为null了,线程2此时判断instance!=null,则直接返回instance引用,但现在实例对象还没有初始化完毕,此时线程2使用instance可能会造成程序崩溃。
现在要解决的问题就是怎样限制处理器进行指令优化重排。

(7)volatile double check 懒汉模式

在JDK1.5之后,使用volatile关键字修饰instance就可以实现正确的double check单例模式了

public class Singleton{  
      
    private static volatile Singleton instance;  
      
    private Singleton(){  
    }  
      
    public static  Singleton getInstance(){  
          
        if(instance==null){  
            synchronized(Singleton.class){  
                if(instance==null){  
                    instance=new Singleton();  
                }  
            }  
        }  
        return instance;  
    }  
}  

2. 工厂模式、装饰者模式、观察者模式。

3. 工厂方法模式的优点(低耦合、高内聚,开放封闭原则)

工厂模式:http://www.cnblogs.com/kgrdomore/p/4242081.html

  • 优点
    • 克服了简单工厂违背开放-封闭原则的缺点,又保留了封装对象创建过程的优点,降低客户端和工厂的耦合性,所以说“工厂模式”是“简单工厂模式”的进一步抽象和推广。
    • 在工厂方法中,用户只需要知道所要产品的具体工厂,无须关系具体的创建过程,甚至不需要具体产品类的类名。
    • 在系统增加新的产品时,我们只需要添加一个具体产品类和对应的实现工厂,无需对原工厂进行任何修改,很好地符合了“开闭原则”。
  • 缺点
    • 每增加一个产品,相应的也要增加一个子工厂,加大了额外的开发量。

小结:

  1. 工厂方法模式完全符合“开闭原则”。
  2. 工厂方法模式使用继承,将对象的创建委托给子类,通过子类实现工厂方法来创建对象。
  3. 工厂方法允许类将实例化延伸到子类进行。
  4. 工厂方法让子类决定要实例化的类时哪一个。在这里我们要明白这并不是工厂来决定生成哪种产品,而是在编写创建者类时,不需要知道实际创建的产品是哪个,选择了使用哪个子类,就已经决定了实际创建的产品时哪个了。
  5. 在工厂方法模式中,创建者通常会包含依赖于抽象产品的代码,而这些抽象产品是、由子类创建的,创建者不需要真的知道在制作哪种具体产品。

算法

请看这篇文章的总结 Java 面试题之算法

并发与性能调优

请看这篇文章的总结 Java 面试题之并发与性能

其他

  1. 常用的linux下的命令

补充

  1. Java面试必看二十问题
  2. 近5年133个Java面试问题列表
文章目录
  1. 1. Java基础
  2. 2. Java IO
  3. 3. Java Web
  4. 4. JVM
  5. 5. 开源框架
  6. 6. 多线程
  7. 7. 网络通信
  8. 8. 数据库MySql
  9. 9. 设计模式
  10. 10. 算法
  11. 11. 并发与性能调优
  12. 12. 其他
  13. 13. 补充
|