Java基础技术核心归纳(二)
转载请声明出处:http://blog.csdn.net/andrexpert/article/details/77918772
Android Java 数据结构
Android基础技术核心归纳(一) Java基础技术核心归纳(一) 数据结构基础知识核心归纳(一)
Android基础技术核心归纳(二) Java基础技术核心归纳(二) 数据结构基础知识核心归纳(二)
Android基础技术核心归纳(三) Java基础技术核心归纳(三) 数据结构基础知识核心归纳(三)
Android基础技术核心归纳(四) Java基础技术核心归纳(四)
Android基础技术核心归纳(一) Java基础技术核心归纳(一) 数据结构基础知识核心归纳(一)
Android基础技术核心归纳(二) Java基础技术核心归纳(二) 数据结构基础知识核心归纳(二)
Android基础技术核心归纳(三) Java基础技术核心归纳(三) 数据结构基础知识核心归纳(三)
Android基础技术核心归纳(四) Java基础技术核心归纳(四)
不知不觉又是一年的9月,今天跟一个师弟聊天,谈到了他现在面试的一些情况,突然想起自己当年也是这么走过来的,顿时感慨良多。Android/Java经验汇总系列文章,是当初自己毕业时笔试、面试和项目开发中相关的总结,虽然不是很高深的东西,也没有归纳得很全面,但是对Android、算法、Java把握个大概还是没问题,今天特意将这些文章放出来,希望能够对看到这个系列文章的毕业生朋友一点帮助吧。当然,由于受当时知识面的限制,归纳得可能不是很准确,若有疑问就留言哈。
1.Java异常.Exception&Error?
异常是一个事件,它发生在程序运行期间,当一段代码的错误条件满足时就会引发异常。异常会干扰正常的指令流程,Java程序中抛出的异常都是对象,是Throwable子类的实例。类Throwable有两个直接子类,即Error类和Exception类。
(1) Error类(错误)
Error类及其子类描述了java运行时系统的内部错误和资源耗尽错误,出现这样的错误的,除了通知用户,并尽力使程序安全地终止之外,程序本身是无法处理的。 常见的错误有:虚拟机运行错误,如栈溢出错误、内存泄漏错误。
(2)Exception类(异常)
Exception类及其子类是程序本身可以处理的异常,分为运行时异常和非运行时异常。
a.运行时异常:RuntimeException类及其子类,又称不可查异常。这类异常一般由程序的逻辑错误引起,程序运行时Java编译器不会检查它,即使程序没有捕获或抛出异常,都能够编译通过,只有程序运行时当错误条件满足时由系统抛出。程序应该从逻辑角度尽可能避免这类异常的发生。
b.非运行时异常:RuntimeException以外的异常,又称可查异常。从程序语法角度来说,程序本身必须处理的异常,要么捕获,要么抛出,如果不处理程序将不能编译通过。
总结:异常和错误的本质区别是异常能被程序本身处理,错误是无法处理。
2.Java常见异常有哪些?
(1)不可查异常(RuntimeException子类)
※→java.lang.ArrayIndexOutOfBoundsException
数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
※→java.lang.ArithmeticException
算术条件异常。譬如:整数除零等。
※→java.lang.NullPointerException
空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等
※→java.lang.NegativeArraySizeException 数组长度为负异常
※→java.lang.ArrayStoreException 数组中包含不兼容的值抛出的异常
※→java.lang.SecurityException 安全性异常
※→java.lang.IllegalArgumentException 非法参数异常
(2)可查异常
IOException、SQLException等以及用户自定义的Exception异常。
※→IOException:操作输入流和输出流时可能出现的异常。
※→EOFException 文件已结束异常
※→FileNotFoundException 文件未找到异常
※→SQLException 操作数据库异常类
※→ClassCastException 类型转换异常类
3.Java异常处理机制
在Java应用程序中处理异常机制为:抛出异常、捕获异常。对于可查异常必须捕获或者声明抛出,允许忽略不可查的RuntimeException和Error。异常总是先被抛出,后被捕捉的。
1.抛出异常
(1)throws抛出异常
如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常,并且抛出的异常对象必须为Exception类及其子类的实例。Throws抛出异常的规则:
a.当方法出现不可查异常(RuntimeException及其子类)和错误(Error类),可以不使用throws关键字来声明抛出异常,编译仍能顺利通过,但在程序运行时会被系统抛出;
b.当方法出现可查异常,要么使用throws子句声明抛出该异常,要么使用try{.....}catch{...}捕获该异常;
c.只能当抛出异常,该方法的调用者才能捕获该异常,如果方法的调用者无法处理,则继续抛出。
(2)throw抛出异常
throw关键字总是出现在函数体中,用来抛出一个throwable类型异常。程序会在throw语句后立即终止,不会继续执行它后面的语句,然后在包含它的所有try块(可能在上层调用函数中)中从里向外寻找含有与其匹配的catch子句的try块。
2.捕获异常
(1)try{...}catch{...}
try后的一对大括号将一块可能发生异常的代码包起来,称为监控区域。catch{..}为异常处理器,一旦一旦某个catch捕获到匹配的异常类型,将进入异常处理代码。一经处理结束,就意味着整个try-catch语句结束。其他的catch子句不再有匹配和捕获异常类型的机会。
(2)try{...}catch{....}finally{...}
finally子句表示无论是否出现异常,该代码块都会被执行。需要注意的是,当try{..}代码块中包含return语句时,程序先执行finally语句块,再执行return。在以下4种特殊情况下,finally块不会被执行:
◆在finally语句块中发生了异常。
◆在前面的代码中用了System.exit()退出程序。
◆在执行finally语句块之前,程序所在的线程死亡或关闭CPU。
4.介绍一下java 的集合类?分别适合什么场景?
Java容器一种数据结构,它是一个能存储其他对象(数据or元素)的对象,Java集合框架支持两种类型的容器(1)存储一个元素的集合(collection);(2)存储键/值对的图(map)。Java集合框架支持规则集、线性集、队列和图,他们分别定义在接口Set、List、Queue和Map中。
Collection-1.规则集(set接口):只允许集合存储互不重复的元素.
a.HashSet:HashSet是通过使用散列技术来存储信息,元素不能重复且集合中只能存放
一个null元素(因为不能重复null);不能保证元素的排列顺序,遍历访问速度快;多线程对
HashSet集合的访问是不同步的。
b.LinkHashSet:LinkedHashSet集合是通过使用散列法的机制来存储信息的,是专门为快速
查询而设计的。当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元
素。其他与HashSet类似。
c.TreeSet:TreeSet采用红黑树的数据结构进行排序元素,能保证元素的次序,使用它可以
从Set中提取有序的序列。
Collection-2.线性集(List接口):允许集合可以存储有序的、重复的元素,允许用户指定存储的位置且通过下标来访问线性表中的元素.
a.ArrayList:ArrayList实现了一个可变大小的数组,它允许所有元素(包括null)重复,线程
不同步。若要提取元素或在线性表的尾部插入和删除元素,ArrayList的效率比较高,适合
随机访问操作比较多、不要求线程同步的场合。
b.LinkedList:LinkedList是实现List接口的一个链表,其他与ArrayList类似。若要在线性表
的任意位置上插入和删除元素,那么LinkedList效率会比较高,适用删除和插入操作较为
频繁、不要求线程同步的场合。-----------单链表
c.Vector:与ArrayList相似,支持线程同步。
d.Stack:Stack继承自Vector,实现一个后进先出的堆栈,支持线程同步。Stack提供5个额
外的方法使得Vector被当作堆栈使用,如peel方法得到栈顶的元素,push入栈,pop出栈。
ArrayList和LinkedList的操作相似,它们最主要的不同体现在内部实现上,内部实现会影响到它们的性能。若要提取元素或在线性表的尾部插入和删除元素,ArrayList的效率比较高,适合随机访问操作比较多的场合。若要在线性表的任意位置上插入和删除元素,那么LinkedList效率会比较高,适用删除和插入操作较为频繁的场合。
Map-----3.图(Map):只允许集合存储键/值对,键不可重复(键唯一性标识记录),值允许重复.
a.HashMap:是一个最常用的Map,通过使用散列技术实现键对应的值的存取。它根据键的hashCode (散列函数)值存储数据,根据键可以直接获取它的值,具有很快的访问速度遍历时,取得数据的顺序是完全随机的。HashMap最多只允许一条记录的键为null,允许多条记录的值为null;HashMap不支持线程同步。适合在不考虑线程同步的情况下,在Map集合中插入、删除和定位元素。
b.Hashtable:存储访问与HashMap类似,但不允许记录的键或者值为空,Hashtable支持线
程同步,但遍历速度要比HashMap慢。
c.LinkedHashMap:HashMap的一个子类,通过链表指针保持了记录的插入顺序。如果需要
输出的顺序和输入的相同,那么用LinkedHashMap可以实现,它还可以按读取顺序来排列。
d.TreeMap:TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的
升序排序,当用Iterator 遍历TreeMap时,得到的记录是排过序的。
>熟悉Java集合类的框架图
异常是一个事件,它发生在程序运行期间,当一段代码的错误条件满足时就会引发异常。异常会干扰正常的指令流程,Java程序中抛出的异常都是对象,是Throwable子类的实例。类Throwable有两个直接子类,即Error类和Exception类。
(1) Error类(错误)
Error类及其子类描述了java运行时系统的内部错误和资源耗尽错误,出现这样的错误的,除了通知用户,并尽力使程序安全地终止之外,程序本身是无法处理的。 常见的错误有:虚拟机运行错误,如栈溢出错误、内存泄漏错误。
(2)Exception类(异常)
Exception类及其子类是程序本身可以处理的异常,分为运行时异常和非运行时异常。
a.运行时异常:RuntimeException类及其子类,又称不可查异常。这类异常一般由程序的逻辑错误引起,程序运行时Java编译器不会检查它,即使程序没有捕获或抛出异常,都能够编译通过,只有程序运行时当错误条件满足时由系统抛出。程序应该从逻辑角度尽可能避免这类异常的发生。
b.非运行时异常:RuntimeException以外的异常,又称可查异常。从程序语法角度来说,程序本身必须处理的异常,要么捕获,要么抛出,如果不处理程序将不能编译通过。
总结:异常和错误的本质区别是异常能被程序本身处理,错误是无法处理。
2.Java常见异常有哪些?
(1)不可查异常(RuntimeException子类)
※→java.lang.ArrayIndexOutOfBoundsException
数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
※→java.lang.ArithmeticException
算术条件异常。譬如:整数除零等。
※→java.lang.NullPointerException
空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等
※→java.lang.NegativeArraySizeException 数组长度为负异常
※→java.lang.ArrayStoreException 数组中包含不兼容的值抛出的异常
※→java.lang.SecurityException 安全性异常
※→java.lang.IllegalArgumentException 非法参数异常
(2)可查异常
IOException、SQLException等以及用户自定义的Exception异常。
※→IOException:操作输入流和输出流时可能出现的异常。
※→EOFException 文件已结束异常
※→FileNotFoundException 文件未找到异常
※→SQLException 操作数据库异常类
※→ClassCastException 类型转换异常类
3.Java异常处理机制
在Java应用程序中处理异常机制为:抛出异常、捕获异常。对于可查异常必须捕获或者声明抛出,允许忽略不可查的RuntimeException和Error。异常总是先被抛出,后被捕捉的。
1.抛出异常
(1)throws抛出异常
如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常,并且抛出的异常对象必须为Exception类及其子类的实例。Throws抛出异常的规则:
a.当方法出现不可查异常(RuntimeException及其子类)和错误(Error类),可以不使用throws关键字来声明抛出异常,编译仍能顺利通过,但在程序运行时会被系统抛出;
b.当方法出现可查异常,要么使用throws子句声明抛出该异常,要么使用try{.....}catch{...}捕获该异常;
c.只能当抛出异常,该方法的调用者才能捕获该异常,如果方法的调用者无法处理,则继续抛出。
(2)throw抛出异常
throw关键字总是出现在函数体中,用来抛出一个throwable类型异常。程序会在throw语句后立即终止,不会继续执行它后面的语句,然后在包含它的所有try块(可能在上层调用函数中)中从里向外寻找含有与其匹配的catch子句的try块。
2.捕获异常
(1)try{...}catch{...}
try后的一对大括号将一块可能发生异常的代码包起来,称为监控区域。catch{..}为异常处理器,一旦一旦某个catch捕获到匹配的异常类型,将进入异常处理代码。一经处理结束,就意味着整个try-catch语句结束。其他的catch子句不再有匹配和捕获异常类型的机会。
(2)try{...}catch{....}finally{...}
finally子句表示无论是否出现异常,该代码块都会被执行。需要注意的是,当try{..}代码块中包含return语句时,程序先执行finally语句块,再执行return。在以下4种特殊情况下,finally块不会被执行:
◆在finally语句块中发生了异常。
◆在前面的代码中用了System.exit()退出程序。
◆在执行finally语句块之前,程序所在的线程死亡或关闭CPU。
4.介绍一下java 的集合类?分别适合什么场景?
Java容器一种数据结构,它是一个能存储其他对象(数据or元素)的对象,Java集合框架支持两种类型的容器(1)存储一个元素的集合(collection);(2)存储键/值对的图(map)。Java集合框架支持规则集、线性集、队列和图,他们分别定义在接口Set、List、Queue和Map中。
Collection-1.规则集(set接口):只允许集合存储互不重复的元素.
a.HashSet:HashSet是通过使用散列技术来存储信息,元素不能重复且集合中只能存放
一个null元素(因为不能重复null);不能保证元素的排列顺序,遍历访问速度快;多线程对
HashSet集合的访问是不同步的。
b.LinkHashSet:LinkedHashSet集合是通过使用散列法的机制来存储信息的,是专门为快速
查询而设计的。当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元
素。其他与HashSet类似。
c.TreeSet:TreeSet采用红黑树的数据结构进行排序元素,能保证元素的次序,使用它可以
从Set中提取有序的序列。
Collection-2.线性集(List接口):允许集合可以存储有序的、重复的元素,允许用户指定存储的位置且通过下标来访问线性表中的元素.
a.ArrayList:ArrayList实现了一个可变大小的数组,它允许所有元素(包括null)重复,线程
不同步。若要提取元素或在线性表的尾部插入和删除元素,ArrayList的效率比较高,适合
随机访问操作比较多、不要求线程同步的场合。
b.LinkedList:LinkedList是实现List接口的一个链表,其他与ArrayList类似。若要在线性表
的任意位置上插入和删除元素,那么LinkedList效率会比较高,适用删除和插入操作较为
频繁、不要求线程同步的场合。-----------单链表
c.Vector:与ArrayList相似,支持线程同步。
d.Stack:Stack继承自Vector,实现一个后进先出的堆栈,支持线程同步。Stack提供5个额
外的方法使得Vector被当作堆栈使用,如peel方法得到栈顶的元素,push入栈,pop出栈。
ArrayList和LinkedList的操作相似,它们最主要的不同体现在内部实现上,内部实现会影响到它们的性能。若要提取元素或在线性表的尾部插入和删除元素,ArrayList的效率比较高,适合随机访问操作比较多的场合。若要在线性表的任意位置上插入和删除元素,那么LinkedList效率会比较高,适用删除和插入操作较为频繁的场合。
Map-----3.图(Map):只允许集合存储键/值对,键不可重复(键唯一性标识记录),值允许重复.
a.HashMap:是一个最常用的Map,通过使用散列技术实现键对应的值的存取。它根据键的hashCode (散列函数)值存储数据,根据键可以直接获取它的值,具有很快的访问速度遍历时,取得数据的顺序是完全随机的。HashMap最多只允许一条记录的键为null,允许多条记录的值为null;HashMap不支持线程同步。适合在不考虑线程同步的情况下,在Map集合中插入、删除和定位元素。
b.Hashtable:存储访问与HashMap类似,但不允许记录的键或者值为空,Hashtable支持线
程同步,但遍历速度要比HashMap慢。
c.LinkedHashMap:HashMap的一个子类,通过链表指针保持了记录的插入顺序。如果需要
输出的顺序和输入的相同,那么用LinkedHashMap可以实现,它还可以按读取顺序来排列。
d.TreeMap:TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的
升序排序,当用Iterator 遍历TreeMap时,得到的记录是排过序的。
>熟悉Java集合类的框架图
5.HashMap的存储机制?
HashMap:是一个最常用的Map,通过使用散列技术实现键对应的值的存取。它根据键的hashCode (散列函数)值存储数据,根据键可以直接获取它的值,具有很快的访问速度遍历时,存取得数据的顺序是完全随机的。HashMap最多只允许一条记录的键为null,允许多条记录的值为null;HashMap不支持线程同步。适合在不考虑线程同步的情况下,在Map集合中插入、删除和定位元素。HashMap的存储机制是基于Hash规则,即它根据键的hashCode值(哈希地址) 值存储数据,根据键的hashCode也可以直接访问获得对应的值,具有很快的访问速度并且取值的顺序是完成随机的。
(1)HashMap内部结构
HashMap的内部存储结构是数组和链表的结合:当实例化一个HashMap时,系统会创建一个长度(容量)为Capacity的Entry类型数组,在这个数组中可以存放元素的位置我们称之为"桶(bucket)",每个bucket都有自己的索引,系统可以根据索引快速地查找bucket中的元素。
(2)Entry对象与Entry链
每个bucket中存储一个元素,即一个Entry对象,每一个Entry对象可以带一个引用变量用于指向下一个元素,因此在一个桶中可以生成一个Entry链。当插入HashMap中的元素不发生冲突时,则每一个Bucket里只有一个Entry对象,HashMap是一个数组,根据索引(hashCode)可以迅速找到Entry;当发生冲突时,单个Bucket里存储的就是一个Entry链,为了减少遍历次数,冲突的元素都是直接插入到Entry链的第一个结点的后面。当需要访问一个Entry时,首先先根据hashCode找到其所对应的"桶",再根据hash(key.hashCode)找到Entry链中的键值对,然后通过values()方法在链表中找到key对应的value.
(3)put(K key,V value)源码
public V put(K key, V value) {(4)get(Object key)源码
if (key == null) {
return putValueForNullKey(value);
}
int hash = secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
int index = hash & (tab.length - 1);
for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
if (e.hash == hash && key.equals(e.key)) {
preModify(e);
V oldValue = e.value;
e.value = value;
return oldValue;
}
}
// No entry for (non-null) key is present; create one
modCount++;
if (size++ > threshold) {
tab = doubleCapacity();
index = hash & (tab.length - 1);
}
addNewEntry(key, value, hash, index);
return null;
}
public V get(Object key) {散列技术的冲突处理问题
if (key == null) {
HashMapEntry<K, V> e = entryForNullKey;
return e == null ? null : e.value;
}
// Doug Lea's supplemental secondaryHash function (inlined).
// Replace with Collections.secondaryHash when the VM is fast enough (http://b/8290590).
int hash = key.hashCode();
hash ^= (hash >>> 20) ^ (hash >>> 12);
hash ^= (hash >>> 7) ^ (hash >>> 4);
HashMapEntry<K, V>[] tab = table;
for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
e != null; e = e.next) {
K eKey = e.key;
if (eKey == key || (e.hash == hash && key.equals(eKey))) {
return e.value;
}
}
return null;
}
由于哈希函数是一个压缩映象,因此在一般情况下,很容易产生“冲突”现象,即key1 ≠ key2,而f(key1)=f(key2)。而且,由于关键字的集合比较大,这种冲突是不可避免的,所以必须采取合理的解决方案,找出尽量少产生冲突的哈希函数和处理冲突的方法。对于哈希函数的构造,通常有开放地址法、数字分析法、平方取中法、折叠法、除留余数法、随机数法等。而这里重点讲述处理冲突的两种方法。
a) 开放地址法
开放地址法是对那些发生冲突的记录,用hi=(h(key)+di)mod n方法再次确定Hash地址。
n:为哈希表长;
di:为增量序列,其取法有以下三种:
1)线性探测再散列 di= c * i
2)二次探测再散列 di = 12, -12, 22, -22, …,
3) 随机探测再散列 di是一组伪随机数列 或者 di=i×H2(key) (又称双散列函数探测)
例如表长为11的哈希表中已填有关键字为17,60,29的记录,H(key)=key MOD 11,现有第4个记录,其关键字为38
H(38)=38 MOD 11=5 冲突
H1=(5+1) MOD 11=6 冲突
H2=(5+2) MOD 11=7 冲突
H3=(5+3) MOD 11=8 不冲突
对于其他增量序列的方法也是如此计算。
b)链地址法
将所有哈希地址相同的记录都链接在同一链表中图形类似于图2。也就是说,当HashMap中的每一个bucket里只有一个Entry,不发生冲突时,Hashmap是一个数组,根据索引可以迅速找到Entry。但是,当发生冲突时,单个的bucket里存储的是一个Entry链,系统必须按顺序遍历每个Entry,直到找到为止。为了减少数据的遍历,冲突的元素都是直接插入到第一个Entry后面的,所以,最早放入bucket中的Entry,位于Entry链中的最末端。这从put(K key,V value)中也可以看出,在同一个bucket存储Entry链的情况下,新放入的Entry总是位于bucket中。
(3)注意事项
存储Entry对象的数据结构是一个叫做Entry类型的table数组。
数组中一个特定的索引位置称为bucket,因为它可以容纳一个LinkedList的第一个元素的对象。
Key对象的hashCode()需要用来计算Entry对象的存储位置。
Key对象的equals()方法需要用来维持Map中对象的唯一性。
get()和put()方法跟Value对象的hashCode和equals方法无关。
null的hashCode总是0,这样的Entry对象总是被存储在数组的第一个位置
参考:
>>HashMap实现原理分析:http://blog.csdn.net/vking_wang/article/details/14166593
>>HashMap的存储与实现:http://carmen-hongpeng.iteye.com/blog/1706415
>>Java 中 HashMap 的工作机制:http://www.oschina.net/question/82993_76564
6.String、StringBuffer、StringBuilder区别?
1.String对象一旦初始化,大小是不可变的。通过查看String类源码,发现String中的3个成员变量value,count,offset都是final的,String类也是final(不可继承),所以一旦初始化后不能修改的。
2.StringBufer、StringBuilder继承于同一个接口,均是一个可变的字符序列(随着append会扩大value[]的容量),StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,只是 StringBuffer 中的方法大都采用了synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是线程不安全的。
StringBuffer为了达到线程安全的目的在一定程度上会降低程序的性能。所以在单线程中,StringBuilder的性能要比StringBuffer高,多线程为了线程安全需要采用StingBuffer。
效率比较String<StringBuffer<StringBuilder,但是在 String S1 = “This is only a” + “ simple” + “ test”时,String效率最高
说明:Java中的String是一个类,而并非基本数据类型。string是值传入,不是引用传入。String S1 = “This is only a” + “ simple” + “ test” ;在编译的时候S1就是常量了可以理解为 String S1 = “This is only a simple test” ; ,不存在运行时对字符串的处理,所以效率最高。
7.JVM垃圾回收机制与算法?
1.Java垃圾回收机制
GC,即垃圾回收机制,是指JVM用于释放那些不再使用的对象所占用的内存。Java语言并不要求JVM有GC,也没有规定GC如何工作。不够常用的JVM都有GC,而且大多数GC都是用类似的算法管理内存和执行收集操作。Java的垃圾回收机制是为所有的Java应用程序服务的而不是为某个特定的进程服务的。任何一个进程都不能命令垃圾回收机制的行为,在JVM垃圾收集器收集一个对象之前,一般要求程序调用适当的方法释放资源(默认为finalize()方法),在finalize()方法返回之后,对象消失,垃圾收集开始执行。
通过垃圾回收(gc)机制,可以方便程序开发人员排除内存溢出、内存泄漏,性能调优和排查并发瓶颈。通常,出现以下情况将会触发GC对一个对象的回收操作:
a.对象没有引用;
b.程序执行完毕或执行过程中出现异常
->作用域发生未捕获异常;
->程序在作用域正常执行完毕;
->程序执行了System.exit();
->程序发生以外终止(进程被杀死等).
2.判断对象是否可被回收方法
垃圾收集的目的在于清除不再使用的对象,GC通过确定对象是否被活动对象引用来确定是否收集该对象。在Java语言中,判断一块内存是否符合垃圾收集器收集标准:a)给对象赋予了空值null,以后再也没有调用过;b)给对象赋予了新值,即重新分配了内存空间。
注:不一定会被垃圾收集器收集。GC主要通过两种常用的方法来判断该对象是否能被收集(即对象是否存活),具体描述如下:
(1)引用计数算法
当这个类被加载到内存以后,就会产生方法区,堆栈、程序计数器等一系列信息,当创建对象的时候,为这个对象在堆栈空间中分配对象,同时会给对象添加一个引用计数器。每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用,即可以被收回。
◆优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序不被长时间打断的实时环境比较有利。
◆缺点: 无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.
(2)可达性分析算法,即对象引用遍历
基本思路:通过一系列称为"GC Roots"的对象作为起始点,从这些结点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连(即从GC Roots到该对象不可达)时,则证明此对象是不可用的。
其中,可作为GC Roots的对象包括以下几种:
● 虚拟机栈(栈帧中的本地变量表)中引用的对象;
● 方法区中类静态属性引用的对象;
● 方法区中常量引用的对象;
● 本地方法栈中JNI引用的对象;
3.垃圾回收算法
(1)标记-清除算法
算法分为两部分,即"标记"和“清除"两个阶段。首先标记出所有需要收回的对象,在标记完成后统一回收所有被标记的对象,其中,可通过"引用计数算法"和"可达性分析算法"进行标记。它有两个缺点:
● 一是效率问题,标记和清除两个过程的效率都不高;
● 一是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
(2)复制算法
为了解决效率问题,一种称为"复制"的收集算法出现了。其基本思想:它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将该存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使用每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可。
● 实现简单,运行高效;
● 以将内存缩小为原来的一般为代价;
(3)标记-整理算法
复制算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行担保,以应对被使用的内存中所有对象都100%存活的极短情况,所以在老年代一般不能直接选用这种算法。
根据老年代的特点,提出"标记-整理"算法。标记过程仍然与"标记-清除"算法一样,但后序步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一段移动,然后直接清理掉端边界以外的内存。
(4)分代收集算法
当前商业虚拟机的垃圾收集都采用"分代收集"算法,即根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。比如在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高,没有额外空间对它进行分配担保,就必须使用"标记-清理"或"标记-整理"算法来进行收回。
4.垃圾收集器优势
C/C++语言要求程序员显示地分配内存、释放内存。程序在需要时分配内存,在不需要时释放内存。但是这种做法常常引起"内存泄漏",即由于某种原因使分配的内存始终没有得到释放。如果该任务不断地重复,程序最终会耗尽内存并异常终止,至少无法继续运行。
Java不要求程序员显示地分配内存和释放内存,避免了很多潜在问题。Java在创建对象时会自动分配内存,并使用垃圾收集器来监视Java程序的运行,当对象不再被使用使就自动释放对象所使用的内存。Java使用一些了软指针(即指针对象的引用,而不是直接指向对象)来跟踪对象的各个引用,并用一个对象表将这些软指针映射为对象的引用。使用软指针,Java的垃圾收集器能够以单独的线程在后台运行,并依次检查每个对象。通过更改对象表,垃圾收集器可以标记对象,移出对象、移动对象或检查对象。
8.JVM内存管理和存储机制?
1.内存管理机制
Java的内存管理就是对象的分配和释放问题。在Java中,程序员需要通过关键字new为每个对象申请内存空间(基本类型除外),所有的对象都在堆(Heap)中分配空间。另外,对象的释放是由GC决定和执行的。因此在Java中,内存的分配是由程序完成的,而内存的释放由GC完成。GC为了能够正确的释放对象(即对象不再被引用),必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。
GC工作原理理解:
假设对象为有向图的顶点,引用关系为图的有向边,有向边从引用者指向被引用对象,每个线程对象作为一个图的起始顶点。例如,大多数程序从main进程开始执行,那么该图以main进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象是有效对象,GC不回收;如果某个对象与这个根顶点不可达,则认为该对象不再被引用,可以被GC收回。
2.内存存储机制
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,即运行时数据区:
程序计数器、栈(Java虚拟机栈、本地方法栈)、堆(Java堆、方法区(运行时常量池)。
(1)程序计数器:是一块较小的内存空间,它可以被看作是当前线程所执行的字节码的行号指示器。由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式实现的,为了线程切换后能恢复到执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响且独立存储。
● 特点:线程私有,唯一一个没有规定任何OutOfMemoryError情况的区域;
(2)Java虚拟机栈
虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直到执行完成的过程,就对应者一个栈帧在虚拟机栈中入栈到出栈的过程。其中,局部变量表存放了编译期可知的各种基本类型(boolean,byte,char,short,int,float,long,double)、对象引用,局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
● 特点:线程私有,抛出*Error异常和OutOfMemoryError异常;
(3)本地方法栈
本地方法栈与虚拟机栈类似,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(即字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。
● 特点:线程私有,抛出*Error异常和OutOfMemoryError异常;
(4)Java堆
Java堆是Java虚拟机所管理的内存中最大的一块,Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例(以及数组)都在这里分配内存。Java堆是垃圾收集器管理的主要区域,可以处于物理上不连续的内存空间中,只要裸机上是连续的即可。
● 特点:所有线程共享,抛出OutOfMemoryError异常;
(5)方法区(永久代)
方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量,即时编译器编译后的代码等数据。HotSpot的垃圾收集器可以像管理Java堆一样管理这部分内存,能够省去专门为方法区编写内存管理代码的工作。除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。
● 特点:所有线程共享,抛出OutOfMemoryError异常;
(6)运行时常量池
运行时常量池是方法区的一部分,class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期间生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
● 特点:所有线程共享,抛出OutOfMemoryError异常;
3.内存溢出与内存泄漏
(1)内存泄漏
在Java中内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点:a)对象是可达的,即在有向图中存在通路可以与其相连;b)对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所收回,然而它们却占用内存。为了确保能回收对象占用的内存,可以通过将对象字段设置为null或者从集合中移出对象。内存泄漏举例:
(2)内存溢出
当程序申请的内存时,没有足够的内存可供申请,就会出现内存溢出。
● 虚拟机栈区(本地方法栈区):如果虚拟机栈可以动态扩展,且扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。
● 堆区:如果在堆中没有内存完成实例分配,并且堆也无法再扩展,将会抛出OutOfMemoryError异常。
● 方法区(运行常量池):当方法区(运行常量池)无法满足内存分配需求时,将抛出OutOfMemoryError异常。
● 直接内存:抛出OutOfMemoryError异常
4.内存优化方法(原则:减少对象的创建,避免不常用的对象常驻内存)
(1)没有必要时不用使用静态变量
当某个对象被定义为static变量所引用,这个对象所占有的内存将不会被回收。有时,开发者会将经常调用的对象或者变量定义为static,以便提供程序的运行性能。因此,不是常用到的对象或者变量,不要定义为static类型的变量,尤其是静态类对象的定义,一定要仔细考虑是否必要。例如类X创建了没有被收回的话,静态变量a一直占用内存:
public class X{(2)充分利用单例模式
static Y a = new Y();
}
使用单例模式可以减少对资源的加载,缩短运行的时间,提高系统效率。但单例模式并不是所有地方都适用,可以适用于以下两个方面:
● 控制资源的使用,通过线程同步来控制资源的并发访问;
● 控制实例的产生,以达到节约资源的目的;
(3)减少对象的创建
尽量避免在经常调用的方法中循环使用new对象,由于系统不仅要花费时间来创建对象,而且还要花时间对这些对象进行垃圾回收和处理。设计模式中的享元模式就是为了减少对象的多次创建而来的
5.Java虚拟机作用
(1)通过 ClassLoader 寻找和装载 class 文件
(2)解释字节码成为指令并执行,提供 class 文件的运行环境
(3)进行运行期间垃圾回收
(4)提供与硬件交互的平台
9.JAVA反射机制
1.Java反射机制
动态语言,即程序运行时,允许改变程序结构或变量类型,因此,Perl、Python、Ruby是动态语言,C++、Java、C#不是动态语言。Java语言本身不具备动态性,但Java所具有的"反射(Reflection)"机制使得Java具有了"动态性"。Reflection 是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等等)、superclass(例如Object)、实现之interfaces(例如Serializable),也包括fields和methods的所有信息,并可于运行时改变fields内容或调用methods。
2.Java反射机制功能
● 在运行时判断任意一个对象所属的类。
● 在运行时构造任意一个类的对象。
● 在运行时查看任意一个类所具有的成员变量和方法。
● 在运行时调用任意一个对象的方法。
3.Java 反射机制APIs
(1)java.lang.reflect
§ Class类:代表一个类。
§ Field 类:代表类的成员变量(成员变量也称为类的属性)。
§ Method类:代表类的方法。
§ Constructor 类:代表类的构造方法。
§ Array类:提供了动态创建数组,以及访问数组的元素的静态方法。
(2)通过Class类是Reflection API中的核心类,用于运行时获取成员变量、成员方法、接口、超类、构造方法等;
(3)用反射机制调用对象的方法
说明:Method类的invoke(Object obj,Object args[])方法接收的参数必须为对象,如果参数为基本类型数据,必须转换为相应的包装类型的对象。invoke()方法的返回值总是对象,如果y实际被调用的方法的返回类型是基本类型数据,那么invoke()方法会把它转换为相应的包装类型的对象,再将其返回。
(4)运行时更改类的field(属性)内容
首先调用Class的getField()并指定field名称,获得特定的Field object之后便可直接调用Field的get()和set()。由于更改类的field内容不需要参数和自变量,因此相对来说较为简单。
参考:http://lavasoft.blog.51cto.com/62575/43218/
http://www.cnblogs.com/rollenholt/archive/2011/09/02/2163758.html
典型例题:
普通的java对象是通过new关键字把对应类的字节码文件加载到内存,然后创建该对象的。
反射是通过一个名为Class的特殊类,用Class.forName("className");得到类的字节码对象,然后用newInstance()方法在虚拟机内部构造这个对象(针对无参构造函数)。
也就是说反射机制让我们可以先拿到java类对应的字节码对象,然后动态的进行任何可能的操作,
包括
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时调用任意一个对象的方法
这些都是反射的功能。
使用反射的主要作用是方便程序的扩展。
10.对象的序列化和反序列?
1.基本概念
对象的序列化,即把对象(各个属性量))换转为字节序列的过程;对象的反序列化,即把字节序列恢复为对象的过程。对象的序列化主要有两种用途:
(1)把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。
(2)在网络上传送对象的字节序列
当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。
2.Java序列化实现
(1)序列化API
java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
(2)实现方法
只有实现了Serializable和Externalizable接口的类的对象才能够被序列化,其中,继承自Serializable接口Externalizable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以采用默认的序列化方式。
对象的序列化具体步骤如下:
○ 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
○ 通过对象输出流的weiteObject()方法写对象;
对象的反序列化具体步骤如下:
○ 创建一个对象输入流。它可以包装一个其他类型的源输入流,如文件输入流;
○ 通过对象输入流的readObject()方法读取对象
(3)java核心代码
○ 创建一个实现Serializable接口的类,该类对象才允许被序列化
public class Person implements Serializable {说明:对没有指定serialVersionUID的Person对象序列化后,再向Person类添加一个属性或方法,当需要反序列化获得Person对象时,程序会报"java.io.InvalidClassException"异常。这是由于文件流的class与修改过的class不兼容,出于安全机制考虑,程序抛出了错误,并且拒绝载入。
private static final long serialVersionUID = -5809782578272943999L; //序列化ID
.....
}
○ 将对象序列化与反序列化
/*
*序列化:ObjectOutputStream 对象输出流,将Person对象存储到E盘的Person.txt文件中,完成对
* Person对象的序列化操作
*/
ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(
new File("E:/Person.txt")));
oo.writeObject(person);
oo.close();
/*
*反序列化:ObjectInputStream 对象输入流,从Person.txt文件中读取Person对象,完成对
* Person对象的序列化操作
*/
ObjectInputStream ois= new ObjectInputStream (new FileInputStream(
new File("E:/Person.txt")));
Person person = (Person) ois.readObject();
ois.close();
在TestSerialversionUID例子中,没有指定Customer类的serialVersionUID的,那么java编译器会自动给这个class进行一个摘要算法,类似于指纹算法,只要这个文件 多一个空格,得到的UID就会截然不同的,可以保证在这么多类中,这个编号是唯一的。所以,添加了一个字段后,由于没有显指定 serialVersionUID,编译器又为我们生成了一个UID,当然和前面保存在文件中的那个不会一样了,于是就出现了2个序列化版本号不一致的错误。因此,只要我们自己指定了serialVersionUID,就可以在序列化后,去添加一个字段,或者方法,而不会影响到后期的还原,还原后的对象照样可以使用,而且还多了方法或者属性可以用。
(4)serialVersionUID的取值
serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。
类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的 serialVersionUID,也有可能相同。为了提高serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。
显式地定义serialVersionUID有两种用途:
1、 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
2、 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。
采用这种方式生成的serialVersionUID是根据类名,接口名,方法和属性等来生成的
3.序列化的特点:
(1)如果某个类能够被序列化,其子类也可以被序列化。
(2)声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态, transient代表对象的临时数据。
(3)相关的类和接口:在java.io包中提供如下涉及对象的序列化的类与接口ObjectOutput接口、ObjectOutputStream类、ObjectInput接口、
ObjectInputStream类
4.序列化对类的处理原则
并不是一个实现了序列化接口的类的所有字段及属性都是可以序列化的。我们分为以下
几个部分来说明:
(1)如果该类有父类,则分两种情况来考虑,如果该父类已经实现了可序列化接口。则
其父类的相应字段及属性的处理和该类相同;如果该类的父类没有实现可序列化接
口,则该类的父类所有的字段属性将不会序列化。
(2)如果该类的某个属性标识为static类型的,则该属性不能序列化,因为不属于对象的状态;
(3)如果该类的某个属性采用transient关键字标识,则该属性不能序列化,因为只是临时状态;
需要注意的是,在我们标注一个类可以序列化的时候,其以下属性应该设置为transient来避免序列化:
(3)线程相关的属性;
(4)需要访问IO、本地资源、网络资源等的属性;
(5)没有实现可序列化接口的属性;(注:如果一个属性没有实现可序列化,而我们又
没有将其用transient 标识, 则在对象序列化的时候, 会抛出
java.io.NotSerializableException 异常)。
参考:http://www.cnblogs.com/xdp-gacl/p/3777987.html
11.JVM加载类过程?
1.JVM类装载原理及其步骤
所谓装载就是寻找一个类或是一个接口的二进制形式,并用该二进制形式来构造代表这个类或是这个接口的class对象的过程,其中类或接口的名称是给定了的。在Java中,类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成:装载、链接和初始化,其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的,各个步骤的主要工作如下:
(1)装载:查找和导入类或接口的二进制数据;
(2)链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的;
校验:检查导入类或接口的二进制数据的正确性;
准备:给类的静态变量分配并初始化存储空间;
解析:将符号引用转成直接引用;
(6)初始化:激活类的静态变量的初始化Java代码和静态Java代码块。
2.java的几种ClassLoader(加载器)及其作用
类装载器是用来把类(class)装载进JVM。当JVM(Java虚拟机)启动时,会形成由三个类加载器组成的初始类加载器层次结构:
bootstrap classloader
|
extension classloader
|
system classloader
(1)bootstrap classloader -引导类加载器,它负责加载Java的核心类。这个加载器的是非常特殊的,它实际上不是 java.lang.ClassLoader的子类,而是由JVM自身实现的。
(2)extension classloader -扩展类加载器,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中JAR的 类包。这为引入除Java核心类以外的新功能提供了一个标准机制。
(3)system classloader -系统(也称为应用)类加载器,它负责在JVM被启动时,加载来自在命令java中的-classpath或者java.class.path系统属性或 者 CLASSPATH操作系统属性所指定的JAR类包和类路径。总能通过静态方法ClassLoader.getSystemClassLoader()找 到该类加载器。