针对《面试心得与总结—BAT、网易、蘑菇街》一文中出现的技术问题的收集与整理

最近,我在ImportNew网站上,看到了这篇文章,觉得总结的非常好,就默默的收藏起来了,觉得日后一定要好好整理学习一下,昨天突然发现在脉脉的行业头条中,居然也推送了这篇文章,更加坚定了我整理的信心。
文中答案和详解部分过于详细的我会附上,本人认为写的 比较好的链接,供大家参考,如果有什么不正确的地方,还希望各位大神,在评论中给予指导,谢谢!
J2SE基础

1. 九种基本数据类型的大小,以及他们的封装类?
Java提供了一组基本数据类型,包括 boolean, byte, char, short, int, long, float, double, void.
同时,java也提供了这些类型的封装类,分别为 Boolean, Byte, Character, Short, Integer, Long, Float, Double, Void
为什么Java会这么做?在java中使用基本类型来存储语言支持的基本数据类型,这里没有采用对象,而是使用了传统的面向过程语言所采用的基本类在型,
主要是从性能方面来考虑的:因为即使最简单的数学计算,使用对象来处理也会引起一些开销,而这些开销对于数学计算本来是毫无必要的。
但是在java中,泛型类包括预定义的集合,使用的参数都是对象类型,无法直接使用这些基本数据类型,所以java又提供了这些基本类型的包装器。
区别:1、基本数据类型只能按值传递,而封装类按引用传递,
2、基本类型在堆栈中创建;而对于对象类型,对象在堆中创建,对象的引用在堆栈中创建。基本类型由于在堆栈中,效率会比较高,但是可能会存在内存泄漏的问题。

 

2.Switch能否用string做参数?

 

在 Java 7之前,switch 只能支持 byte、short、char、int或者其对应的封装类以及 Enum 类型。在 Java 7中,String支持被加上了。

 

3.equals与==的区别?

 

“==”比较的是值【变量(栈)内存中存放的对象的(堆)内存地址】

 

equal用于比较两个对象的值是否相同【不是比地址】

 

【特别注意】Object类中的equals方法和“==”是一样的,没有区别,而String类,Integer类等等一些类,是重写了equals方法,才使得equals和“==不同”,
所以,当自己创建类时,自动继承了Object的equals方法,要想实现不同的等于比较,必须重写equals方法。"=="比"equal"运行速度快,因为"=="只是比较引用.

 

参考链接:.equals与==

 

4. Object有哪些公用方法?

 

直接上代码:

 

Object o = new Object();

/**
* 比较当前对象和是否等于另一个对象,指向的对象是否相同
*/
System.out.println(o.equals(new Object()));

/**
* 返回hashCode
*/
System.out.println(o.hashCode());

/**
* 返回包名+类名+Integer.toHexString(hashCode())
*/
System.out.println(o.toString());

/**
* 返回class对象
*/
System.out.println(o.getClass());

 

try {
/**
* 线程等待,Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
*/
o.wait();
o.wait(1000);
o.wait(1000,1000);
} catch (InterruptedException e) {
e.printStackTrace();
}


/**
* notify()和notifyAll()都是Object对象用于通知处在等待该对象的线程的方法。两者的最大区别在于:
* notifyAll使所有原来在该对象上等待被notify的线程统统退出wait的状态,变成等待该对象上的锁,一旦该对象被解锁,他们就会去竞争。
* notify则文明得多他只是选择一个wait状态线程进行通知,并使它获得该对象上的锁,但不惊动其他同样在等待被该对象notify的线程们,当第一个线程运行完毕以后释放对象上的锁此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,继续处在wait状态,直到这个对象发出一个notify或notifyAll,它们等待的是被notify或notifyAll,而不是锁。
*/
o.notify();
o.notifyAll();
5. Java的四种引用,强弱软虚,用到的场景。

强引用
最普遍的一种引用方式,如String s = "abc",变量s就是字符串“abc”的强引用,只要强引用存在,则垃圾回收器就不会回收这个对象。

 

软引用(SoftReference)
用于描述还有用但非必须的对象,如果内存足够,不回收,如果内存不足,则回收。一般用于实现内存敏感的高速缓存,
软引用可以和引用队列ReferenceQueue联合使用,如果软引用的对象被垃圾回收,JVM就会把这个软引用加入到与之关联的引用队列中。

 

弱引用(WeakReference)
弱引用和软引用大致相同,弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。
在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

 

虚引用(PhantomReference)
就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,
在任何时候都可能被垃圾回收器回收。 虚引用主要用来跟踪对象被垃圾回收器回收的活动。
虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,
如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中

 

参考链接:http://droidyue.com/blog/2014/10/12/understanding-weakreference-in-java/

 


6. Hashcode的作用
hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable

 

参考链接:浅谈Java中的hashcode方法

7.ArrayList、LinkedList、Vector的区别
这三者都是实现了List接口,都拥有List接口里面定义的方法,并且同时拥有Collection接口的方法;

 

ArrayList:采用的是数组的方式进行存储数据的,查询和修改速度快,但是增加和删除速度慢;线程是不同步的

 

LinkedList:采用的是链表的方式进行存储数据的,查询和修改速度慢,但是增加和删除速度快;线程是不同步的

 

Vector:也采用的是数组的方式进行存储的,Vector在java1.0以前用,但是ArrayList是在java1.2版本后使用的,线程是同步的,效率相比ArrayList来说慢一点;同时Vector查询数据有迭代器,有枚举,有get(int index),有indexOf(int index)四种方式,而ArrayList却没有枚举

 

8. String、StringBuffer与StringBuilder的区别
1、可变与不可变

 

String类中使用字符数组保存字符串,如下就是,因为有“final”修饰符,所以可以知道string对象是不可变的。

 

private final char value[];

 

StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,如下就是,可知这两种对象都是可变的。

 

char[] value;

 

2、是否多线程安全

 

String中的对象是不可变的,也就可以理解为常量,显然线程安全。  

 

AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法

 

StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的

 

StringBuilder并没有对方法进行加同步锁,所以是非线程安全的

 

9. Map、Set、List、Queue、Stack的特点与用法

 

Map

 

Map是键值对,键Key是唯一不能重复的,一个键对应一个值,值可以重复。
TreeMap可以保证顺序,HashMap不保证顺序,即为无序的。
Map中可以将Key和Value单独抽取出来,其中KeySet()方法可以将所有的keys抽取正一个Set。而Values()方法可以将map中所有的values抽取成一个集合。

 

Set

 

不包含重复元素的集合,set中最多包含一个null元素
只能用Lterator实现单项遍历,Set中没有同步方法

 

List

 

有序的可重复集合。
可以在任意位置增加删除元素。
用Iterator实现单向遍历,也可用ListIterator实现双向遍历

 

Queue

 

Queue遵从先进先出原则。
使用时尽量避免add()和remove()方法,而是使用offer()来添加元素,使用poll()来移除元素,它的优点是可以通过返回值来判断是否成功。
LinkedList实现了Queue接口。
Queue通常不允许插入null元素

 

Stack

 

Stack遵从后进先出原则。
Stack继承自Vector。
它通过五个操作对类Vector进行扩展,允许将向量视为堆栈,它提供了通常的push和pop操作,以及取堆栈顶点的peek()方法、测试堆栈是否为空的empty方法等

 

如果涉及堆栈,队列等操作,建议使用List
对于快速插入和删除元素的,建议使用LinkedList
如果需要快速随机访问元素的,建议使用ArrayList

 


HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度。

 

10. HashMap和HashTable的区别。
HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。
HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。
由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
HashMap不能保证随着时间的推移Map中的元素次序是不变的
参考文章:HashMap和HashTable的区别
11、HashMap和ConcurrentHashMap的区别,HashMap的底层源码。

 

Hashmap本质是数组加链表。根据key取得hash值,然后计算出数组下标,如果多个key对应到同一个下标,就用链表串起来,新插入的在前面。

 

ConcurrentHashMap:在hashMap的基础上,ConcurrentHashMap将数据分为多个segment(类似hashtable),默认16个(concurrency level),然后在每一个分段上都用锁进行保护,从而让锁的粒度更精细一些,并发性能更好

 


参考文章:HashMap VS ConcurrentHashMap

 

HashMap的源码:【Java集合源码剖析】HashMap源码剖析

 

12、TreeMap、HashMap、LindedHashMap的区别

 


1.HashMap里面存入的键值对在取出的时候是随机的,也是我们最常用的一个Map.它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。在Map 中插入、删除和定位元素,HashMap 是最好的选择。
2.TreeMap取出来的是排序后的键值对。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。
3. LinkedHashMap 是HashMap的一个子类,如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现. (应用场景:购物车等需要顺序的)
13. Collection包结构,与Collections的区别。

 

Collection是集合类的上级接口,子接口主要有Set 和List、Map。

Collections是针对集合类的一个帮助类,提供了操作集合的工具方法:一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。

给大家补充一个链接:collections在java中的常见用法

 

参考链接:http://blog.sina.com.cn/s/blog_105817120102vzh6.html

 

14、try catch finally,try里有return,finally还执行么?

 

1、不管有木有出现异常,finally块中代码都会执行;
2、当try和catch中有return时,finally仍然会执行;
3、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。

 

情况1:try{} catch(){}finally{} return;
显然程序按顺序执行。
情况2:try{ return; }catch(){} finally{} return;
程序执行try块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,最后执行try中return;
finally块之后的语句return,因为程序在try中已经return所以不再执行。
情况3:try{ } catch(){return;} finally{} return;
程序先执行try,如果遇到异常执行catch块,
有异常:则执行catch中return之前(包括return语句中的表达式运算)代码,再执行finally语句中全部代码,
最后执行catch块中return. finally之后也就是4处的代码不再执行。
无异常:执行完try再finally再return.
情况4:try{ return; }catch(){} finally{return;}
程序执行try块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,因为finally块中有return所以提前退出。
情况5:try{} catch(){return;}finally{return;}
程序执行catch块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,因为finally块中有return所以提前退出。
情况6:try{ return;}catch(){return;} finally{return;}
程序执行try块中return之前(包括return语句中的表达式运算)代码;
有异常:执行catch块中return之前(包括return语句中的表达式运算)代码;
则再执行finally块,因为finally块中有return所以提前退出。
无异常:则再执行finally块,因为finally块中有return所以提前退出。

 

最终结论:任何执行try 或者catch中的return语句之前,都会先执行finally语句,如果finally存在的话。
如果finally中有return语句,那么程序就return了,所以finally中的return是一定会被return的,
编译器把finally中的return实现为一个warning。

 

15. Excption与Error包结构。OOM你遇到过哪些情况,SOF你遇到过哪些情况

这里写图片描述

 

(一)Throwable

 

  Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类或其子类之一的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出,才可以是 catch 子句中的参数类型。
  Throwable 类及其子类有两个构造方法,一个不带参数,另一个带有 String 参数,此参数可用于生成详细消息。
  Throwable 包含了其线程创建时线程执行堆栈的快照。它还包含了给出有关错误更多信息的消息字符串。
  
Java将可抛出(Throwable)的结构分为三种类型:
  错误(Error)
  运行时异常(RuntimeException)
  被检查的异常(Checked Exception)

 

 1.Error
  Error 是 Throwable 的子类,用于指示合理的应用程序不应该试图捕获的严重问题。大多数这样的错误都是异常条件。
  和RuntimeException一样, 编译器也不会检查Error。
  当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误,程序本身无法修复这些错误的。
  
 2.Exception
  Exception 类及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。
   对于可以恢复的条件使用被检查异常(Exception的子类中除了RuntimeException之外的其它子类),对于程序错误使用运行时异常。 
  

 

① ClassNotFoundException
  
当应用程序试图使用以下方法通过字符串名加载类时:
Class 类中的 forName 方法。
ClassLoader 类中的 findSystemClass 方法。
ClassLoader 类中的 loadClass 方法。
但是没有找到具有指定名称的类的定义,抛出该异常。

 

CloneNotSupportedException

 

当调用
Object 类中的 clone 方法复制对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。重写 clone 方法的应用程序也可能抛出此异常,指示不能或不应复制一个对象。

 

③ IOException
当发生某种 I/O 异常时,抛出此异常。此类是失败或中断的 I/O 操作生成的异常的通用类。

 

-EOFException
  当输入过程中意外到达文件或流的末尾时,抛出此异常。
此异常主要被数据输入流用来表明到达流的末尾。
注意:其他许多输入操作返回一个特殊值表示到达流的末尾,而不是抛出异常。
    
-FileNotFoundException
  当试图打开指定路径名表示的文件失败时,抛出此异常。
在不存在具有指定路径名的文件时,此异常将由 FileInputStream、FileOutputStream 和 RandomAccessFile 构造方法抛出。如果该文件存在,但是由于某些原因不可访问,比如试图打开一个只读文件进行写入,则此时这些构造方法仍然会抛出该异常。

 

-MalformedURLException
  抛出这一异常指示出现了错误的 URL。或者在规范字符串中找不到任何合法协议,或者无法解析字符串。 
 
-UnknownHostException
  指示主机 IP 地址无法确定而抛出的异常。

 

④ RuntimeException
   是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。可能在执行方法期间抛出但未被捕获的 RuntimeException 的任何子类都无需在 throws 子句中进行声明。
   Java编译器不会检查它。当程序中可能出现这类异常时,还是会编译通过。
   虽然Java编译器不会检查运行时异常,但是我们也可以通过throws进行声明抛出,也可以通过try-catch对它进行捕获处理。

 

-ArithmeticException
当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。

 

-ClassCastException
  当试图将对象强制转换为不是实例的子类时,抛出该异常。
例如:Object x = new Integer(0);

 

-LllegalArgumentException
  抛出的异常表明向方法传递了一个不合法或不正确的参数。

 

-IllegalStateException
  在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。

 

-IndexOutOfBoundsException 
  指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
应用程序可以为这个类创建子类,以指示类似的异常。

 

-NoSuchElementException
  由 Enumeration 的 nextElement 方法抛出,表明枚举中没有更多的元素。

 

-NullPointerException
  当应用程序试图在需要对象的地方使用 null 时,抛出该异常。这种情况包括:
调用 null 对象的实例方法。
访问或修改 null 对象的字段。
将 null 作为一个数组,获得其长度。
将 null 作为一个数组,访问或修改其时间片。
将 null 作为 Throwable 值抛出。
应用程序应该抛出该类的实例,指示其他对 null 对象的非法使用。

 

(二) SOF (堆栈溢出 StackOverflow)

 

StackOverflowError 的定义:
当应用程序递归太深而发生堆栈溢出时,抛出该错误。

 

因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。

 

栈溢出的原因:

 

递归调用
大量循环或死循环
全局变量是否过多
数组、List、map数据过大

 

16. Java面向对象的三个特征与含义

 

1 . 封装性
将对象的状态信息尽可能的隐藏在对象内部,只保留有限的接口和方法与外界进行交互,从而避免了外界对对象内部属性的破坏。
Java中使用访问控制符来保护对类、变量、方法和构造方法的访问

 

2. 继承
java通过继承创建分等级层次的类,可以理解为一个对象从另一个对象获取属性的过程。

 

3.多态
多态是同一个行为具有多个不同表现形式或形态的能力。 多态性是对象多种表现形式的体现
参考链接:https://yq.aliyun.com/articles/52843

 


17. Override和Overload的含义和区别

 

方法的重写(Overriding)和重载(Overloading)是Java多态性的不同表现。
重写(Overriding)是父类与子类之间多态性的一种表现,而重载(Overloading)是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding) 。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被"屏蔽"了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型或有不同的参数次序,则称为方法的重载(Overloading)。不能通过访问权限、返回类型、抛出的异常进行重载。

 

1. Override 特点
1、覆盖的方法的标志必须要和被覆盖的方法的标志完全匹配,才能达到覆盖的效果;
2、覆盖的方法的返回值必须和被覆盖的方法的返回一致;
3、覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类;
4、方法被定义为final不能被重写。
5、对于继承来说,如果某一方法在父类中是访问权限是private,那么就不能在子类对其进行重写覆盖,如果定义的话,也只是定义了一个新方法,而不会达到重写覆盖的效果。(通常存在于父类和子类之间。)

 

2.Overload 特点
1、在使用重载时只能通过不同的参数样式。例如,不同的参数类型,不同的参数个数,不同的参数顺序(当然,同一方法内的几个参数类型必须不一样,例如可以是fun(int, float), 但是不能为fun(int, int));
2、不能通过访问权限、返回类型、抛出的异常进行重载;
3、方法的异常类型和数目不会对重载造成影响;
4、重载事件通常发生在同一个类中,不同方法之间的现象。
5、存在于同一类中,但是只有虚方法和抽象方法才能被覆写。

 

其具体实现机制:

 

overload是重载,重载是一种参数多态机制,即代码通过参数的类型或个数不同而实现的多态机制。 是一种静态的绑定机制(在编译时已经知道具体执行的是哪个代码段)。

override是覆盖。覆盖是一种动态绑定的多态机制。即在父类和子类中同名元素(如成员函数)有不同 的实现代码。执行的是哪个代码是根据运行时实际情况而定的。

 

18. Interface与abstract类的区别

 

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

 

19. Static class 与non static class的区别

 

内部静态类不需要有指向外部类的引用。但非静态内部类需要持有对外部类的引用。
非静态内部类能够访问外部类的静态和非静态成员。静态类不能访问外部类的非静态成员。他只能访问外部类的静态成员。
一个非静态内部类不能脱离外部类实体被创建,一个非静态内部类可以访问外部类的数据和方法,因为他就在外部类里面。

 

1.首先是类中的数据,static的
class A {
static int a;
}
class B {
int b;
}
无论新建几个A对象,这几个对象公用一个int a,一个对象的a改变,另一个也会改变。
而B对象,不同对象之间的int b独立存在,互不影响,可以有多个值。

 

2.类中的方法
静态的方法,不需要建立对象就可以访问
如Math.abs()这个方法,我们没有建立Math的对象,就可以通过类名直接使用abs这个方法。
而非静态的方法,必须先建立对象,然后通过对象名,调用这个方法。
如JButton jb = new JButton();
jb.addActionListener(l);

 

ps:在静态方法的定义中,不能直接引用本类的其他非静态方法。例如。我们不能在main中直接引用,本类的其他方法。所以我们经常可以看见,在main方法中,先建立本类的一个对象,然后才通过对象调用本类的其他方法。

 

3.在初始化过程中,静态的总是先初始化

 

20. java多态的实现原理。

 

参考链接:http://www.cnblogs.com/startRuning/p/5673485.html

 


21. 实现多线程的两种方法:Thread与Runable

 

Thread 和 Runnable 的相同点:都是“多线程的实现方式”。
Thread 和 Runnable 的不同点:
Thread 是类,而Runnable是接口;Thread本身是实现了Runnable接口的类。我们知道“一个类只能有一个父类,但是却能实现多个接口”,因此Runnable具有更好的扩展性。
此外,Runnable还可以用于“资源的共享”。即,多个线程都是基于某一个Runnable对象建立的,它们会共享Runnable对象上的资源。
通常,建议通过“Runnable”实现多线程

 

参考链接:http://hjsj186.blog.163.com/blog/static/2465820332015218115231968/

 

22. 线程同步的方法:sychronized、lock、reentrantLock等

 

一.什么是sychronized

 

sychronized是Java中最基本同步互斥的手段,可以修饰代码块,方法,类.

 

在修饰代码块的时候需要一个reference对象作为锁的对象.

 

在修饰方法的时候默认是当前对象作为锁的对象.

 

在修饰类时候默认是当前类的Class对象作为锁的对象.

 

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

 


二.什么ReentrantLock

 

以对象的方式来操作对象锁.相对于sychronized需要在finally中去释放锁

 

三.synchronized和ReentrantLock的区别

 

除了synchronized的功能,多了三个高级功能.

 

等待可中断,公平锁,绑定多个Condition.

 

1.等待可中断

 

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

 

2.公平锁

 

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

 

3.绑定多个Condition

 

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

 


分析理解:
在并发量比较小的情况下,使用synchronized是个不错的选择,但是在并发量比较高的情况下,其性能下降很严重,此时ReentrantLock是个不错的方案。

1、ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候

 

线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,

 

如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断

 

如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情

 

 

 

ReentrantLock获取锁定与三种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁

 

b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;

 

c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;

 

d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断

 

 

 

2、synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中

 

 

 

3、在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;

 

JDK5.0的多线程任务包对于同步的性能方面有了很大的改进,在原有synchronized关键字的基础上,又增加了ReentrantLock,以及各种Atomic类。了解其性能的优劣程度,有助与我们在特定的情形下做出正确的选择。

 

总体的结论先摆出来:

 

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

 

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

 

Atomic:
和上面的类似,不激烈情况下,性能比synchronized略逊,而激烈的时候,也能维持常态。激烈的时候,Atomic的性能会优于ReentrantLock一倍左右。但是其有一个缺点,就是只能同步一个值,一段代码中只能出现一个Atomic的变量,多于一个同步无效。因为他不能在多个Atomic之间同步。

 


所以,我们写同步的时候,优先考虑synchronized,如果有特殊需要,再进一步优化。ReentrantLock和Atomic如果用的不好,不仅不能提高性能,还可能带来灾难。

 

23. 锁的等级:方法锁、对象锁、类锁

 

方法锁(synchronized修饰方法时)
通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。

 

synchronized 方法控制对类成员变量的访问:
每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态,从而有效避免了类成员变量的访问冲突。

 

对象锁(synchronized修饰方法或代码块)

 

  当一个对象中有synchronized method或synchronized block的时候调用此对象的同步方法或进入其同步区域时,就必须先获得对象锁。如果此对象的对象锁已被其他调用者占用,则需要等待此锁被释放。(方法锁也是对象锁)       

 

java的所有对象都含有1个互斥锁,这个锁由JVM自动获取和释放。线程进入synchronized方法的时候获取该对象的锁,当然如果已经有线程获取了这个对象的锁,那么当前线程会等待;synchronized方法正常返回或者抛异常而终止,JVM会自动释放对象锁。这里也体现了用synchronized来加锁的1个好处,方法抛异常的时候,锁仍然可以由JVM来自动释放。
类锁(synchronized 修饰静态的方法或代码块)

 

  由于一个class不论被实例化多少次,其中的静态方法和静态变量在内存中都只有一份。所以,一旦一个静态的方法被申明为synchronized。此类所有的实例化对象在调用此方法,共用同一把锁,我们称之为类锁。  

 

对象锁是用来控制实例方法之间的同步,类锁是用来控制静态方法(或静态变量互斥体)之间的同步。 

 

类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的。
24. 写出生产者消费者模式。

 

生产者-消费者(producer-consumer)问题,也称作有界缓冲区(bounded-buffer)问题,两个进程共享一个公共的固定大小的缓冲区。其中一个是生产者,用于将消息放入缓冲区;另外一个是消费者,用于从缓冲区中取出消息。问题出现在当缓冲区已经满了,而此时生产者还想向其中放入一个新的数据项的情形,其解决方法是让生产者此时进行休眠,等待消费者从缓冲区中取走了一个或者多个数据后再去唤醒它。同样地,当缓冲区已经空了,而消费者还想去取消息,此时也可以让消费者进行休眠,等待生产者放入一个或者多个数据时再唤醒它。

 

一,首先定义公共资源类,其中的变量number是保存的公共数据。并且定义两个方法,增加number的值和减少number的值。由于多线程的原因,必须加上synchronized关键字,注意while判断的条件。
/**
* 公共资源类
*/
public class PublicResource {
private int number = 0;

/**
* 增加公共资源
*/
public synchronized void increace() {
while (number != 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number++;
System.out.println(number);
notify();
}

/**
* 减少公共资源
*/
public synchronized void decreace() {
while (number == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number--;
System.out.println(number);
notify();
}
} </span>

 

二,分别定义生产者线程和消费者线程,并模拟多次生产和消费,即增加和减少公共资源的number值
[java] view plain copy
<span style="font-size:18px;"> /**
* 生产者线程,负责生产公共资源
*/
public class ProducerThread implements Runnable {
private PublicResource resource;

public ProducerThread(PublicResource resource) {
this.resource = resource;
}

@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
resource.increace();
}
}
}
/**
* 消费者线程,负责消费公共资源
*/
public class ConsumerThread implements Runnable {
private PublicResource resource;

public ConsumerThread(PublicResource resource) {
this.resource = resource;
}

@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
resource.decreace();
}
}
} </span>

 

三,模拟多个生产者和消费者操作公共资源的情形,结果须保证是在允许的范围内。
public class ProducerConsumerTest {
public static void main(String[] args) {
PublicResource resource = new PublicResource();
new Thread(new ProducerThread(resource)).start();
new Thread(new ConsumerThread(resource)).start();
new Thread(new ProducerThread(resource)).start();
new Thread(new ConsumerThread(resource)).start();
new Thread(new ProducerThread(resource)).start();
new Thread(new ConsumerThread(resource)).start();
}
}
参考链接:http://blog.csdn.net/u010339647/article/details/52013123

 

25. ThreadLocal的设计理念与作用
ThreadLocal类的大致结构和进行ThreadLocalMap的操作.我们可以从中得出以下的结论:
1. ThreadLocalMap变量属于线程(Thread)的内部属性,不同的线程(Thread)拥有完全不同的ThreadLocalMap变量.
2. 线程(Thread)中的ThreadLocalMap变量的值是在ThreadLocal对象进行set或者get操作时创建的.
3. 在创建ThreadLocalMap之前,会首先检查当前线程(Thread)中的ThreadLocalMap变量是否已经存在,如果不存在则创建一个;
如果已经存在,则使用当前线程(Thread)已创建的ThreadLocalMap.
4. 使用当前线程(Thread)的ThreadLocalMap的关键在于使用当前的ThreadLocal的实例作为key进行存储ThreadLocal模式
,至少从两个方面完成了数据访问隔离,有了横向和纵向的两种不同的隔离方式,ThreadLocal模式就能真正地做到线程安全:
纵向隔离 —— 线程(Thread)与线程(Thread)之间的数据访问隔离.这一点由线程(Thread)的数据结构保证.因为每个线程(Thread)在进行对象访问时,
访问的都是各自线程自己的ThreadLocalMap.横向隔离 —— 同一个线程中,不同的ThreadLocal实例操作的对象之间的相互隔离.
这一点由ThreadLocalMap在存储时,采用当前ThreadLocal的实例作为key来保证.

 

话不多说,给大家推荐一篇原创博文,我就是在这篇博客http://blog.csdn.net/hua286306956/article/details/8660268里 学习的

 

26. ThreadPool用法与优势。

 

先定义一个线程池ThreadPoolExecutor,使用的时候用executor来调用runnable

 

优势:合理利用线程池能够带来三个好处。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

 


推荐链接:http://blog.csdn.net/scboyhj__/article/details/48805881
27. Concurrent包里的其他东西:ArrayBlockingQueue、CountDownLatch等等。

 

ArrayBlockingQueue

 

一个建立在数组之上被BlockingQueue绑定的阻塞队列。这个队列元素顺序是先进先出。队列的头部是在队列中待的时间最长的元素。队列的尾部是再队列中待的时间最短的元素。新的元素会被插入到队列尾部,并且队列从队列头部获取元素。
这是一个典型的绑定缓冲,在这个缓冲区中,有一个固定大小的数组持有生产者插入的数据,并且消费者会提取这些数据。一旦这个类被创建,那么这个数组的容量将不能再被改变。尝试使用put操作给一个满队列插入元素将导致这个操作被阻塞;尝试从空队列中取元素也会被阻塞。
这个类推荐了一个可选的公平策略来排序等待的生产者和消费者线程。默认的,这个顺序是不确定的。但是队列会使用公平的设置true来使线程按照先进先出顺序访问。通常公平性会减少吞吐量但是却减少了可变性以及避免了线程饥饿。
参考博文:http://blog.csdn.net/startupmount/article/details/37413275?utm_source=tuicool&utm_medium=referral

 

concurrent包是jdk1.5引入的重要的包,主要代码由大牛Doug Lea完成,其实是在jdk1.4时代,由于java语言内置对多线程编程的支持比较基础和有限,所以他写了这个,因为实在太过于优秀,所以被加入到jdk之中;

 

通常所说的concurrent包基本有3个package组成
java.util.concurrent:提供大部分关于并发的接口和类,如BlockingQueue,Callable,ConcurrentHashMap,ExecutorService, Semaphore等
java.util.concurrent.atomic:提供所有原子操作的类, 如AtomicInteger, AtomicLong等;
java.util.concurrent.locks:提供锁相关的类, 如Lock, ReentrantLock, ReadWriteLock, Condition等;

 


ountDownLatch, 可以用来在一个线程中等待多个线程完成任务的类;
通常的使用场景是,某个主线程接到一个任务,起了n个子线程去完成,但是主线程需要等待这n个子线程都完成任务了以后才开始执行某个操作;

 

掩饰代码:
@Test
public void demoCountDown()
{
int count = 10;

final CountDownLatch l = new CountDownLatch(count);
for(int i = 0; i < count; ++i)
{
final int index = i;
new Thread(new Runnable() {

@Override
public void run() {

try {
Thread.currentThread().sleep(20 * 1000);
} catch (InterruptedException e) {

e.printStackTrace();
}

System.out.println("thread " + index + " has finished...");

l.countDown();

}
}).start();
}

try {
l.await();
} catch (InterruptedException e) {

e.printStackTrace();
}

System.out.println("now all threads have finished");

}

 

28. wait()和sleep()的区别
① 这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类。
sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。
② 锁: 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。

 

sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。

 

Thread.sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”。

 

③ 使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。

 

synchronized(x){
x.notify()
//或者wait()
}
29. foreach与正常for循环效率对比

 

不是绝对的,在选择for, foreach的时候,应该考虑以下几点:

 

1. 如果只是读数据,优先选择foreach,因为效率高,而且代码简单,方便;

 

2. 如果要写数据,就只能选择for了

 

30. Java IO与NIO

 

区别对比

 

IO NIO
面向流 面向缓冲
阻塞IO 非阻塞IO
无 选择器</span>
1、面向流与面向缓冲
Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

 

2、阻塞与非阻塞IO

 

Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

 

3、选择器(Selector)

 

ava NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。

 

31. 反射的作用与原理。

 

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

 

推荐链接:Class对象和java反射机制

 

java反射

 

32. 泛型常用特点,List<String>能否转为List<Object>。

 

1、类型安全。类型错误现在在编译期间就被捕获到了,而不是在运行时当作java.lang.ClassCastException展示出来,将类型检查从运行时挪到编译时有助于开发者更容易找到错误,并提高程序的可靠性

 

2、消除了代码中许多的强制类型转换,增强了代码的可读性

 

3、为较大的优化带来了可能

 

List<String>向上转换至List<Object>会丢失String类的身份(String类型的特有接口),这种转换是不完美的。
当需要由List向下转型时,你的程序必须明确的知道将对象转换成何种具体类型,不然这将是不不‘安全的操作!

 

33. 解析XML的几种方式的原理与特点:DOM、SAX、PULL
SAX是基于事件流的解析
当解析器发现元素开始、元素结束、文本、文档的开始或结束等时,发送事件,程序员编写响应这些事件的代码,保存数据
DOM是基于XML文档树结构的解析
解析器读入整个文档,然后构建一个驻留内存的树结构,然后代码就可以使用 DOM 接口来操作这个树结构
Sax定义

 

SAX是一个解析速度快并且占用内存少的xml解析器,非常适合用于Android等移动设备

 

SAX全称是Simple API for Xml,既是指一种接口,也是一个软件包

 

作为接口,sax是事件驱动型xml解析的一个标准接口

 

Sax工作原理

 

Sax的工作原理简单的说,就是对文档进行顺序扫描,扫描到文档(document)开始与结束,扫描到元素(element)开始、结束等地方时调用事件处理

 

处理函数做相应动作,然后继续扫描,直到文档结束。

 

Sax特点

 

1. 解析效率高,占用内存少

 

2.可以随时停止解析

 

3.不能载入整个文档到内存

 

4.不能写入xml

 

5.SAX解析xml文件采用的是事件驱动

 

---sax并不需要解析完 整个文档,在按内容顺序解析文档的过程中,sax会判断当前读到的字符是否合法xml语法中的某部分,如果符合就会触发事件

 

DOM工作原理
dom全称Document Object Model ,为xml文档的已解析版本定义了一组接口。解析器读入整个文档,然后构建一个主流内存的树结构,

 

然后代码就可以使用dom接口来操作这个树结构

 

DOM的特点
>优点

 

1.整个文档树在内存中,便于操作;支持删除、修改、重新排列等多种功能

 

2.通过树形结构存取xml文档

 

3.可以在树的某个节点上向前或向后移动

 

>缺点

 

1.将整个文档调入内存(包括无用的节点),浪费时间和空间

 

>适用场合

 

一旦解析了文档还需多次访问这些数据;硬件资源充足(内存,cpu)

 

pull解析器简介
1.pull解析器是android内置的解析器,解析原理与sax类似

 

2.pull它提供了类似的事件。

 

如:开始元素和结束元素事件,使用parse.next()可以进入下一个元素并触发相应的事件,事件将作为数值代码被发送

 

因此可以使用一个switch对感兴趣的事件进行处理。当元素开始解析时,调用parser.nextText()方法获取下一个Text类型节点的值

 

pull与sax的不同之处
1.pull读取xml文件后触发相应的事件调用方法返回的是数字。

 

2.pull可以在程序中控制,想解析到哪里就可以停止到哪里

 

3.Android中更推荐使用pull解析

 

参考链接:http://blog.csdn.net/kukulongzai_123/article/details/7058008

 

34. Java与C++对比
Java没有显式指针,而在C++中却可以用。
Java是主动多态的,不用关心具有继承关系的多个类之间的同名成员函数会调用哪个,Java会主动地从祖父类、祖祖父类……,追溯至最高一级父类,然后从上至下开始寻找并调用;C++却不会主动使用多态,要使用多态,就要用虚函数。
Java是隐式继承的;C++是被动多态的,C++把话说明白了,你继承谁就继承谁,继承多个都可以,你什么都不说那么就不继承。
Java有接口,C++中却没有。C++中是定义了一个抽象类,把成员函数设为常量,并改成纯虚函数,在C++中这样的抽象类就是接口。
Java是单根继承的,但是允许一个类实现多个接口;C++虽然支持多继承,尽管很少有人去用它。
Java中所有的函数都与类相关,没有全局变量和非成员函数,而C++却支持这些。
C++中使用的动态内存怎么用就怎么还,Java中由于包含一个垃圾收集系统。
Java有很紧凑的异常处理机制,而C++稍微显得草率了一些。但是,这不代表C++异常处理机制不强大,因为Java只能抛出Throwable之类的异常,而C++却什么都可以。
Java标准库是Java庞大的体现,涵盖了国际化、网络化、数学、声音、Web应用和服务以及数据库等。
35. Java1.7与1.8新特性。

JDK 1.7 新特性
1,switch中可以使用字串了

 

2,"<>"这个玩意儿的运用List<String> tempList = new ArrayList<>(); 即泛型实例化类型自动推断

 

3. 自定义自动关闭类

 

4. 新增一些取环境信息的工具方法

 

5. Boolean类型反转,空指针安全,参与位运算

 

6. 两个char间的equals

 

7,安全的加减乘除

 

8、对Java集合(Collections)的增强支持

 

9、数值可加下划线

 

10、支持二进制文字

 

11、简化了可变参数方法的调用

 

12、在try catch异常扑捉中,一个catch可以写多个异常类型,用"|"隔开,

 

13、jdk7之前,你必须用try{}finally{}在try内使用资源,在finally中关闭资源,不管try中的代码是否正常退出或者异常退出。jdk7之后,你可以不必要写finally语句来关闭资源,只要你在try()的括号内部定义要使用的资源

 

JDK 1.8 新特性
一、接口的默认方法
二、Lambda 表达式

 

三、函数式接口

 

四、方法与构造函数引用

 

五、Lambda 作用域

 

六、访问局部变量

 

八、访问接口的默认方法

 

九、Date API

 

十、Annotation 注解

 

强烈推荐参考链接:JDK各个版本的新特性jdk1.5-jdk8

 

36. 设计模式:单例、工厂、适配器、责任链、观察者等等。

 

Java开发中的23种设计模式详解(转)

 

37. JNI的使用
JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。
从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。
JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。
使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。
例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。

 

推荐链接:JNI的简单使用

 

j2se的部分 可算整理完了,希望各位读者能够喜欢,不足的地方希望各位大神多多批评指正,过几天给大家整理JVM 的部分

JVM
1. 内存模型以及分区,需要详细到每个区放什么

JVM内存区域模型

 

 

 

JVM内存模型
1.方法区
也称”永久代” 、“非堆”, 它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。默认最小值为16MB,最大值为64MB,可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小。
运行时常量池:是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加载后放到方法区的运行时常量池中。

2.虚拟机栈
描述的是Java 方法执行的内存模型:每个方法被执行的时候 都会创建一个“栈帧”用于存储局部变量表(包括参数)、操作栈、方法出口等信息。每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。声明周期与线程相同,是线程私有的。
局部变量表存放了编译器可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(引用指针,并非对象本身),其中64位长度的long和double类型的数据会占用2个局部变量的空间,其余数据类型只占1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量是完全确定的,在运行期间栈帧不会改变局部变量表的大小空间。

3.本地方法栈
与虚拟机栈基本类似,区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈则是为Native方法服务。

4.堆
也叫做java 堆、GC堆是java虚拟机所管理的内存中最大的一块内存区域,也是被各个线程共享的内存区域,在JVM启动时创建。该内存区域存放了对象实例及数组(所有new的对象)。其大小通过-Xms(最小值)和-Xmx(最大值)参数设置,-Xms为JVM启动时申请的最小内存,默认为操作系统物理内存的1/64但小于1G,-Xmx为JVM可申请的最大内存,默认为物理内存的1/4但小于1G,默认当空余堆内存小于40%时,JVM会增大Heap到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列;当空余堆内存大于70%时,JVM会减小heap的大小到-Xms指定的大小,可通过XX:MaxHeapFreeRation=来指定这个比列,对于运行系统,为避免在运行时频繁调整Heap的大小,通常-Xms与-Xmx的值设成一样。
由于现在收集器都是采用分代收集算法,堆被划分为新生代和老年代。新生代主要存储新创建的对象和尚未进入老年代的对象。老年代存储经过多次新生代GC(Minor GC)任然存活的对象。

新生代:
程序新创建的对象都是从新生代分配内存,新生代由Eden Space和两块相同大小的Survivor Space(通常又称S0和S1或From和To)构成,可通过-Xmn参数来指定新生代的大小,也可以通过-XX:SurvivorRation来调整Eden Space及Survivor Space的大小。

老年代:
用于存放经过多次新生代GC任然存活的对象,例如缓存对象,新建的对象也有可能直接进入老年代,主要有两种情况:①.大对象,可通过启动参数设置-XX:PretenureSizeThreshold=1024(单位为字节,默认为0)来代表超过多大时就不在新生代分配,而是直接在老年代分配。②.大的数组对象,切数组中无引用外部对象。
老年代所占的内存大小为-Xmx对应的值减去-Xmn对应的值。

5.程序计数器
是最小的一块内存区域,它的作用是当前线程所执行的字节码的行号指示器,在虚拟机的模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、异常处理、线程恢复等基础功能都需要依赖计数器完成。

2. 堆里面的分区:Eden,survival from to,老年代,各自的特点。
HotSpot虚拟机的分代收集,分为一个Eden区、两个Survivor去以及Old Generation/Tenured区,其中Eden以及Survivor共同组成New Generatiton/Young space。
通常将对New Generation进行的回收称为Minor GC;对Old Generation进行的回收称为Major GC,但由于Major GC除并发GC外均需对整个堆以及Permanent Generation进行扫描和回收,因此又称为Full GC。

Eden区是分配对象的区域。
Survivor是minor/younger gc后存储存活对象的区域。
Tenured区域存储长时间存活的对象
1.Eden区
Eden区位于Java堆的年轻代,是新对象分配内存的地方,由于堆是所有线程共享的,因此在堆上分配内存需要加锁。而Sun JDK为提升效率,会为每个新建的线程在Eden上分配一块独立的空间由该线程独享,这块空间称为TLAB(Thread Local Allocation Buffer)。在TLAB上分配内存不需要加锁,因此JVM在给线程中的对象分配内存时会尽量在TLAB上分配。如果对象过大或TLAB用完,则仍然在堆上进行分配。如果Eden区内存也用完了,则会进行一次Minor GC(young GC)。

2.Survival from to
Survival区与Eden区相同都在Java堆的年轻代。Survival区有两块,一块称为from区,另一块为to区,这两个区是相对的,在发生一次Minor GC后,from区就会和to区互换。在发生Minor GC时,Eden区和Survival from区会把一些仍然存活的对象复制进Survival to区,并清除内存。Survival to区会把一些存活得足够旧的对象移至年老代。

3.年老代
年老代里存放的都是存活时间较久的,大小较大的对象,因此年老代使用标记整理算法。当年老代容量满的时候,会触发一次Major GC(full GC),回收年老代和年轻代中不再被使用的对象资源。

3. 对象创建方法,对象的内存分配,对象的访问定位。

Java对象的创建大致上有以下几个步骤:
类加载检查:检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类的加载过程
为对象分配内存:对象所需内存的大小在类加载完成后便完全确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。由于堆被线程共享,因此此过程需要进行同步处理(分配在TLAB上不需要同步)
内存空间初始化:虚拟机将分配到的内存空间都初始化为零值(不包括对象头),内存空间初始化保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
对象设置:JVM对对象头进行必要的设置,保存一些对象的信息(指明是哪个类的实例,哈希码,GC年龄等)
init:执行完上面的4个步骤后,对JVM来说对象已经创建完毕了,但对于Java程序来说,我们还需要对对象进行一些必要的初始化。
对象的内存分配
Java对象的内存分配有两种情况,由Java堆是否规整来决定(Java堆是否规整由所采用的垃圾收集器是否带有压缩整理功能决定):
指针碰撞(Bump the pointer):如果Java堆中的内存是规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,分配内存也就是把指针向空闲空间那边移动一段与内存大小相等的距离
空闲列表(Free List):如果Java堆中的内存不是规整的,已使用的内存和空闲的内存相互交错,就没有办法简单的进行指针碰撞了。虚拟机必须维护一张列表,记录哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录
对象的访问定位
对象的访问形式取决于虚拟机的实现,目前主流的访问方式有使用句柄和直接指针两种:
使用句柄:
如果使用句柄访问,Java堆中将会划分出一块内存来作为句柄池,引用中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息:
对象访问句柄
优势:引用中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而引用本身不需要修改。
直接指针:
如果使用直接指针访问对象,那么对象的实例数据中就包含一个指向对象类型数据的指针,引用中存的直接就是对象的地址:
对象访问直接指针
优势:速度更快,节省了一次指针定位的时间开销,积少成多的效应非常可观。

4. GC的两种判定方法:引用计数与引用链。
基于引用计数与基于引用链这两大类别的自动内存管理方式最大的不同之处在于:前者只需要局部信息,而后者需要全局信息

引用计数
引用计数顾名思义,就是记录下一个对象被引用指向的次数。引用计数方式最基本的形态就是让每个被管理的对象与一个引用计数器关联在一起,该计数器记录着该对象当前被引用的次数,每当创建一个新的引用指向该对象时其计数器就加1,每当指向该对象的引用失效时计数器就减1。当该计数器的值降到0就认为对象死亡。每个计数器只记录了其对应对象的局部信息——被引用的次数,而没有(也不需要)一份全局的对象图的生死信息。由于只维护局部信息,所以不需要扫描全局对象图就可以识别并释放死对象;但也因为缺乏全局对象图信息,所以无法处理循环引用的状况。

引用链
引用链需要内存的全局信息,当使用引用链进行GC时,从对象图的“根”(GC Root,必然是活的引用,包括栈中的引用,类静态属性的引用,常量的引用,JNI的引用等)出发扫描出去,基于引用的可到达性算法来判断对象的生死。这使得对象的生死状态能批量的被识别出来,然后批量释放死对象。引用链不需要显式维护对象的引用计数,只在GC使用可达性算法遍历全局信息的时候判断对象是否被引用,是否存活。

5. GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别用在什么地方,如果让你优化收集方法,有什么思路?
标记清除
标记清除算法分两步执行:
暂停用户线程,通过GC Root使用可达性算法标记存活对象
清除未被标记的垃圾对象
标记清除算法缺点如下:
效率较低,需要暂停用户线程
清除垃圾对象后内存空间不连续,存在较多内存碎片
标记算法如今使用的较少了

复制算法
复制算法也分两步执行,在复制算法中一般会有至少两片的内存空间(一片是活动空间,里面含有各种对象,另一片是空闲空间,里面是空的):
暂停用户线程,标记活动空间的存活对象
把活动空间的存活对象复制到空闲空间去,清除活动空间
复制算法相比标记清除算法,优势在于其垃圾回收后的内存是连续的。
但是复制算法的缺点也很明显:
需要浪费一定的内存作为空闲空间
如果对象的存活率很高,则需要复制大量存活对象,导致效率低下
复制算法一般用于年轻代的Minor GC,主要是因为年轻代的大部分对象存活率都较低

标记整理
标记整理算法是标记清除算法的改进,分为标记、整理两步:
暂停用户线程,标记所有存活对象
移动所有存活对象,按内存地址次序一次排列,回收末端对象以后的内存空间
标记整理算法与标记清除算法相比,整理出的内存是连续的;而与复制算法相比,不需要多片内存空间。
然而标记整理算法的第二步整理过程较为麻烦,需要整理存活对象的引用地址,理论上来说效率要低于复制算法。
因此标记整理算法一般引用于老年代的Major GC
参考博文:Java虚拟机学习(2):垃圾收集算法

6. GC收集器有哪些?CMS收集器与G1收集器的特点?

常见的GC收集器如下图所示,连线代表可搭配使用:
GC收集器
1.Serial收集器(串行收集器)
用于新生代的单线程收集器,收集时需要暂停所有工作线程(Stop the world)。优点在于:简单高效,单个CPU时没有线程交互的开销,堆较小时停顿时间不长。常与Serial Old 收集器一起使用,示意图如下所示:
串行收集器

2.ParNew收集器(parallel new 收集器,新生代并行收集器)
Serial收集器多线程版本,除了使用多线程外和Serial收集器一模一样。常与Serial Old 收集器一起使用,示意图如下:
ParNew收集器

3.Parallel Scavenge收集器
与ParNew收集器一样是一款多线程收集器,其特点在于关注点与别的GC收集器不同:一般的GC收集器关注于缩短工作线程暂停的时间,而该收集器关注于吞吐量,因此也被称为吞吐量优先收集器。(吞吐量 = 用户运行代码时间 / (用户运行代码时间 + 垃圾回收时间))高吞吐量与停顿时间短相比主要强调任务快完成,因此常和Parallel Old 收集器一起使用(没有Parallel Old之前与Serial Old一起使用),示意图如下:
Parallel Old 收集器

4.Serial Old收集器
Serial收集器的年老代版本,不在赘述。

5.Parallel Old收集器
年老代的并行收集器,在JDK1.6开始使用。

6.CMS收集器(Concurrent Mark Sweep,并发标记清除收集器)
CMS收集器是一个年老代的收集器,是以最短回收停顿时间为目标的收集器,其示意图如下所示:
CMS收集器
CMS收集器基于标记清除算法实现,主要分为4个步骤:
初始标记,需要stop the world,标记GC Root能关联到的对象,速度快
并发标记,对GC Root执行可达性算法
重新标记,需要stop the world,修复并发标记时因用户线程运行而产生的标记变化,所需时间比初始标记长,但远比并发标记短
并发清理
CMS收集器的缺点在于:
其对于CPU资源很敏感。在并发阶段,虽然CMS收集器不会暂停用户线程,但是会因为占用了一部分CPU资源而导致应用程序变慢,总吞吐量降低。其默认启动的回收线程数是(cpu数量+3)/4,当cpu数较少的时候,会分掉大部分的cpu去执行收集器线程
无法处理浮动垃圾,浮动垃圾即在并发清除阶段因为是并发执行,还会产生垃圾,这一部分垃圾即为浮动垃圾,要等下次收集
CMS收集器使用的是标记清除算法,GC后会产生碎片
7.G1收集器(Garbage First收集器)
相比CMS收集器,G1收集器主要有两处改进:
使用标记整理算法,确保GC后不会产生内存碎片
可以精确控制停顿,允许指定消耗在垃圾回收上的时间
G1收集器可以实现在基本不牺牲吞吐量的前提下完成低停顿的内存回收,这是由于它能够极力地避免全区域的垃圾收集,之前的收集器进行收集的范围都是整个新生代或老年代,而G1将整个Java堆(包括新生代、老年代)划分为多个大小固定的独立区域(Region),并且跟踪这些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的区域(这就是Garbage First名称的来由)。区域划分及有优先级的区域回收,保证了G1收集器在有限的时间内可以获得最高的收集效率。

7. Minor GC与Full GC分别在什么时候发生?

Minor GC也叫Young GC,当年轻代内存满的时候会触发,会对年轻代进行GC
Full GC也叫Major GC,当年老代满的时候会触发,当我们调用System.gc时也可能会触发,会对年轻代和年老代进行GC

9. 类加载的五个过程:加载、验证、准备、解析、初始化。
JVM把class文件加载的内存,并对数据进行校验、转换解析和初始化,最终形成JVM可以直接使用的Java类型的过程就是加载机制。
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的生命周期包括了:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)七个阶段,其中验证、准备、解析三个部分统称链接。
1.加载
在加载阶段,虚拟机需要完成以下事情:
通过一个类的权限定名来获取定义此类的二进制字节流
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
在java堆中生成一个代表这个类的java.lang.Class对象,作为方法去这些数据的访问入口
2.验证
在验证阶段,虚拟机主要完成:
文件格式验证:验证class文件格式规范
元数据验证:这个阶段是对字节码描述的信息进行语义分析,以保证起描述的信息符合java语言规范要求
字节码验证:进行数据流和控制流分析,这个阶段对类的方法体进行校验分析,这个阶段的任务是保证被校验类的方法在运行时不会做出危害虚拟机安全的行为
符号引用验证:符号引用中通过字符串描述的全限定名是否能找到对应的类、符号引用类中的类,字段和方法的访问性(private、protected、public、default)是否可被当前类访问
3.准备
准备阶段是正式为类变量(被static修饰的变量)分配内存并设置变量初始值(0值)的阶段,这些内存都将在方法区中进行分配

4.解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
常见的解析有四种:
类或接口的解析
字段解析
类方法解析
接口方法解析

5.初始化
初始化阶段才真正开始执行类中定义的java程序代码,初始化阶段是执行类构造器<clinit>()方法的过程

参考博文:Java虚拟机学习(3): 类加载机制

10. 双亲委派模型:Bootstrap ClassLoader、Extension ClassLoader、ApplicationClassLoader


上图中所展示的类加载器之间的这种层次关系,就称为类加载器的双亲委托模型。双亲委托模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承的关系来实现,而是使用组合关系来复用父加载器的代码。

public abstract class ClassLoader {

privatestatic nativevoid registerNatives();
static{
registerNatives();
}

// The parent class loader for delegation
privateClassLoader parent;

// Hashtable that maps packages to certs
privateHashtable package2certs = newHashtable(11);


双亲委托的工作过程:如果一个类加载器收到了一个类加载请求,它首先不会自己去加载这个类,而是把这个请求委托给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成加载请求(它管理的范围之中没有这个类)时,子加载器才会尝试着自己去加载。

使用双亲委托模型来组织类加载器之间的关系,有一个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,例如java.lang.Object存放在rt.jar之中,无论那个类加载器要加载这个类,最终都是委托给启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类,相反,如果没有双亲委托模型,由各个类加载器去完成的话,如果用户自己写一个名为java.lang.Object的类,并放在classpath中,应用程序中可能会出现多个不同的Object类,java类型体系中最基本安全行为也就无法保证。

分派:静态分派与动态分派

静态分派
所有依赖静态类型来定位方法执行版本的分派动作称为静态分派,其典型应用是方法重载(重载是通过参数的静态类型而不是实际类型来选择重载的版本的)
[java] view plain copy
class Car {}
class Bus extends Car {}
class Jeep extends Car {}
public class Main {
public static void main(String[] args) throws Exception {
// Car 为静态类型,Car 为实际类型
Car car1 = new Car();
// Car 为静态类型,Bus 为实际类型
Car car2 = new Bus();
// Car 为静态类型,Jeep 为实际类型
Car car3 = new Jeep();

showCar(car1);
showCar(car2);
showCar(car3);
}
private static void showCar(Car car) {
System.out.println("I have a Car !");
}
private static void showCar(Bus bus) {
System.out.println("I have a Bus !");
}
private static void showCar(Jeep jeep) {
System.out.println("I have a Jeep !");
}
}
结果
静态分派重载
动态分派
与静态分派类似,动态分派指在在运行期根据实际类型确定方法执行版本,其典型应用是方法重写(即多态)。
举例Java代码如下:
[java] view plain copy
class Car {
public void showCar() {
System.out.println("I have a Car !");
}
}
class Bus extends Car {
public void showCar() {
System.out.println("I have a Bus !");
}
}
class Jeep extends Car {
public void showCar() {
System.out.println("I have a Jeep !");
}
}
public class Main {
public static void main(String[] args) throws Exception {
// Car 为静态类型,Car 为实际类型
Car car1 = new Car();
// Car 为静态类型,Bus 为实际类型
Car car2 = new Bus();
// Car 为静态类型,Jeep 为实际类型
Car car3 = new Jeep();

car1.showCar();
car2.showCar();
car3.showCar();
}
}
动态分派重写
可以看出来重写是一个根据实际类型决定方法版本的动态分派过程。
参考链接:http://www.importnew.com/20438.html

原文地址:https://www.cnblogs.com/huanyi0723/p/7423490.html