【推荐】Java性能优化系列集锦

Java性能问题一直困扰着广大程序员,因为平台复杂性,要定位问题,找出其根源确实很难。随着10多年Java平台的改进以及新出现的多核多处理器,Java软件的性能和扩展性已经今非昔比了。现代JVM持续演进,内建了更为成熟的优化技术、运行时技术和垃圾收集器。与此同时,底层的硬件平台和操做系统也在演化。
java


目录:程序员

1、Java性能优化系列之一--设计优化

2、Java性能优化系列之二--程序优化

3、Java性能优化系列之三--并发程序设计详解

4、Java性能优化系列之四--Java内存管理与垃圾回收机制详解

5、Java性能优化系列之五--JavaIO


现代大规模关键性系统中的Java性能调优,是一项富有挑战的任务。你须要关注各类问题,包括算法结构、内存分配模式以及磁盘和文件I/O的使用方式。性能调优最困难的一般是找到问题所在,即使是经验丰富的人也会被他们的直觉所误导。性能杀手老是隐藏在最意想不到的地方。面试

这一次,我将在本文中着重介绍Java性能优化的一系列举措,但愿可以对如今的你有所帮助。以为有帮助的朋友也能够转发一下。算法


1、Java性能优化系列之一--设计优化

一、善于利用 Java 中的设计模式:享元模式、代理模式、装饰器模式等。数据库

Java 中的缓冲区:设计模式

(1)缓冲最经常使用的场景就是提升 IO 速度:好比 BufferedWriter 能够用来装饰 FileWriter ,为 FileWriter 加上缓冲。 BufferedOutputStream 能够用来装饰 FileOutputStream 。使用这两个装饰器时候能够指定缓冲区大小,默认的 size 为 8K 。数组

(2)JavaNIO 中的各类 Buffer 类族,有更增强大的缓冲区控制功能。缓存

(3)除了性能上的优化,缓冲区也能够做为上层组件和下层组件的一种通讯工具,将上层组件好下层组件进行解耦。好比生产者消费者模式中的缓冲区。安全

二、缓存:性能优化

(1)好比 Hibernate 采用的两级缓存:一级缓存和二级缓存。二级缓存指的是 sessionFactory层面上的缓存, Hibernate 采用的是 EHCache 。一级缓存指的是 session 层面上的缓存。

三、对象复用技术 -- 池的使用

(1)数据库链接池:较常使用的数据库链接池组件是 C3P0 和 Proxool 。其中 C3P0 是伴随 Hibernate 一块儿发布的, Hibernate 默认采用的数据库链接池。

(2)线程池:自定义线程池以及 jdk1.5 提供的线程池组件。

四、并行代替串行。

五、时间换空间:不引入中间变量实现两个数字的交换。代价是增长 CPU 运算。

六、空间换时间:使用下标数组排序。


2、Java性能优化系列之二--程序优化

经常使用的程序设计优化技巧:

一、字符串优化处理

(1)String 类的特色:不变性、针对常量池的优化( String.intern() 方法的意义)

(2)subString 方法的内存泄漏 :

(3)字符串分割和查找不要使用 split 函数,效率低,而是使用 StringTokenizer 或者 indexOf结合 subString() 函数完成分割。

(4)用 charAt ()方法代替 startWith ()方法。

(5)对于静态字符串或者变量字符串的链接操做, Java 在编译的时候会进行完全的优化,将多个链接操做的字符串在编译时合成一个单独的字符串,而不是生成大量的 String 实例。只生成一个对象。

(6)在无需考虑线程安全状况下尽可能使用 StringBuilder 。

(7)StringBuffer 和 StringBuilder 初始化的时候均可以设置一个初始值,默认是 16B 。若是字符串的长度大于 16B 的时候,则须要进行扩容。扩容策略是将原有的容量大小翻倍,以新的容量申请内存空间,创建 char 数组,而后将数组中的内容复制到这个新的数组中,使用 Arrays.copyOf() 函数。所以,若是能预先评估 StringBuilder 的大小,则能够节省这些复制操做,从而提升系统的性能。


二、List 接口

( 1 ) ArrayList 和 Vector 的区别:它们几乎使用了相同的算法,它们的惟一区别是对多线程的支持。 ArrayList 是不安全的,而 Vector 是线程安全的。

( 2 ) LinkedList 和 ArrayList 的区别:

|---1 、 linkedList 采用链表实现,适合于数据删除和插入很是频繁的状况,不适合随机访问。

|---2 、 ArrayList 采用数组实现,适用于随机查找和顺序读的状况,不适合删除和插 入数据很是频繁的场景。

(3)基于数组的 List 都会有一个容量参数。当 ArrayList 所存储的元素容量超过其已有大小的时候就会进行扩容,数组的扩容会致使整个数组进行一次内存复制。所以合理的数组大小会减少数组扩容的次数从而提升系统性能。

(4)遍历列表的时候尽可能使用迭代器,速度块。


三、Map 接口:

(1)HashMap 的实现原理:简单的说, HashMap 就是将 key 作 hash 算法,而后将 hash 值映射到内存地址,直接取得 key 所对应的数据。在 HashMap 中,底层数据结构使用的是数组,所谓的内存地址指的是数组的下标索引。

(2)容量参数与扩容:默认状况下, hashmap 的初始容量为 16 ,负载因子为 0.75 ,也就是说当 hashmap 的实际容量达到了初始容量 * 负载因子( hashmap 内部维护的一个 threshold 值)的时候, hashmap 就会进行扩容。在扩容时,会遍历整个 hashmap ,所以应该设置合理的初始大小和负载因子,能够减少 hashmap 扩容的次数。

(3)LinkedHashMap-- 有序的 HashMap : HashMap 的最大缺点是其无序性,被存入到 Hashmap 中的元素,在遍历 HashMap 的时候,其输出不必定按照输入的顺序,而是 HashMap 会根据 hash 算法设定一个查找高效的顺序。若是但愿保存输入顺序,则须要使用 LinkedHashMap 。LinkedHashmap 在内部又增长了一个链表,用于保存元素的顺序。

(4)LinkedList 能够提供两种类型的顺序:一个是元素插入时候的顺序,一个是最近访问的顺序。注意: LinkedHashMap 在迭代过程当中,若是设置为按照最后访问时间进行排序,即:每当使用 get() 方法访问某个元素时,该元素便会移动到链表的尾端。可是这个时候会出现异常,所以, LinkedHashMap 工做在这种模式的时候,不能在迭代器中使用 get() 操做。

(5)关于 ConcurrentModificationException :该异常通常会在集合迭代过程当中被修改时抛出。所以,不要在迭代器模式中修改集合的结构。这个特性适合于全部的集合类,包括 HashMap 、 Vector 、 ArrayList 等。

(6)TreeMap-- 若是要对元素进行排序,则使用 TreeMap 对 key 实现自定义排序,有两种方式:在 TreeMap 的构造函数中注入一个 Comparator 或者使用一个实现了 Comparable 的 key 。

(7)若是须要将排序功能加入 HashMap ,最好是使用 Treemap 而不是在应用程序自定义排序。

(8)HashMap 基于 Hash 表实现, TreeMap 基于红黑树实现。


四、 Map 和 Set 的关系:

( 1 )全部 Set 的实现都只是对应的 Map 的一种封装,其内部维护一个 Map 对象。即: Set只是相应的 Map 的 Value 是一种特殊的表现形式的一种特例。

( 2 ) Set 主要有三种实现类: HashSet 、 LinkedHashSet 、 TreeSet 。其中 HashSet 是基于 Hash 的快速元素插入,元素之间无序。 LinkedHashSet 同时维护着元素插入顺序,遍历集合的时候,老是按照先进先出的顺序排序。 TreeSet 是基于红黑树的实现,有着高效的基于元素 Key 的排序算法。


5 、优化集合访问代码:

( 1 )、分离循环中被重复调用的代码:例如, for 循环中使用集合的 size() 函数,则不该该把这个函数的调用放到循环中,而是放到循环外边、

( 2 )、省略相同的操做:


6 、 RandomAccess 接口:

经过 RandomAccess 可知道 List 是否支持随机快速访问。同时,若是应用程序须要经过索引下标对 List 作随机访问,尽可能 buyaoshiyongLinkedList , ArrayList 或者 Vector 能够。


七、 JavaNIO 的特性:

1 、为全部的原始类型提供 Buffer 支持。

2 、使用 Java.nio.charset.Charset 做为字符编码解码解决方案。

3 、增长通道抽象代替原有的 IO 流抽象。

4 、支持锁和内存映射文件的文件访问接口。

5 、提供基于 Selector 的异步网络 IO 。

六、 Java 中 NIO 的使用。 Channel 是一个双向通道,便可读也可写。应用程序不能直接操做 Channel ,必须借助于 Buffer 。例如读数据的时候,必须把数据从通道读入到缓冲区,而后在缓冲区中进行读取。以文件读取为例,首先经过文件输入流得到文件通道,而后把文件通道的内容读入到缓冲区中,而后就能够对缓冲区操做。


8 、 Buffer 的基本原理:

1 、 Buffer 的建立: Buffer 的静态 allocate(int size) 方法或者 Buffer.wrap(byte[]src) 。

2 、 Buffer 的工做原理:三个变量: position ,表明当前缓冲区的位置,写缓冲区的时候,将从 position 的下一个位置写数据。 Capacity ,表明缓冲区的总容量上限。 Limit ,缓冲区的实际上限,也就是说,读数据的时候,数据便是从 position 到 limit 之间的数据

3 、 flip 操做: limit=position,position=0, 通常是在读写切换的时候使用。写完数据以后,须要限定下有效数据范围,才能读数据;

4 、 clear 操做: position-0 , limit=capacity. 。为从新写入缓冲区作准备。

5 、 rewind 操做: position=0 ,为读取缓冲区中有效数据作准备,一半 limit 已经被合理设置。


9 、读写缓冲区:

1 、 public byte get() :顺序读取缓冲区的一个字节, position 会加一

2 、 public Buffer get(byte[]dst): 将缓冲区中的数据读入到数组 dst 中,并适当的移动 position

3 、 public byte get(int index) :获得第 index 个字节,但不移动 posoiion

4 、 public ByteBuffer put(byte b) :将字节 b 放入到缓冲区中,并移动 position

5 、 public ByteBuffer put(int index,byte b) :将字节 b 放到缓冲区的 index 位位置

6 、 pubglic final ByteBuffer(byte[]src) :将字节数组 src 放到缓冲区中。


10 、标志缓冲区:

相似于一个书签的功能,在数据的处理过程当中,可随时记录当前位置。而后在任意时刻,回到这个位置。 Mark 用于记录当前位置, reset 用于恢复到 mark 所在的位置、


11 、复制缓冲区:

使用 Buffer 的 duplicate 方法能够复制一个缓冲区,副本缓冲区和原缓冲区共享一份空间可是有有着独立的 position 、 capacity 和 limit 值。

缓冲区分片:缓冲区分片使用 slice 方法实现。它将在现有的缓冲区中,建立的子缓冲区。子缓冲区和父缓冲区共享数据。这个方法有助于将系统模块化。缓冲区切片能够将一个大缓冲区进行分割处理,获得的子缓冲区都具备缓冲的缓冲区模型结构;所以。这个操做有助于系统的模块化。


粉丝福利:

  • 程序员/产品/运营/测试等互联网专业资料、笔面试题获取;

  • 各大互联网公司简历内推:微信扫描二维码,或加微信x123z123xy,备注“内推”;

  • 入群:微信扫描二维码,或加微信x123z123xy,备注“入群”,人脉共享,一块儿交流;


12 、只读缓冲区:只读缓冲区能够保证核心数据的安全,若是不但愿数据被随意篡改,返回一个只读缓冲区是颇有帮助的。


13 、文件映射到内存: NIO 提供了一种将文件映射到内存的方法进行 IO 操做,这种方法比基于流 IO 快不少。这个操做主要由 FileChanne.map() 操做。使用文件内存的方式,将文本经过 FileChannel 映射到内存中。而后从内存中读取数据。同时,经过修改 Buffer, 将对内存中数据的修改写到对应的硬盘文件中。


14 、处理结构化数据:散射和汇集。

散射就是将数据读入到一组 bytebuffer 中,而汇集正好相反。经过 ScatteringByteChannel 和 GatheringByteChannel 能够简化对结构数据的操做。


15 、直接内存访问: DirectBuffer 直接分配在物理内存中,并不占用对空间,所以也不受对空间限制。 DirectBuffer 的读写操做比普通 Buffer 块,由于 DirectBuffer 直接操纵的就是内核缓冲区。


16 、引用类型:强、软、若、虚四种引用类型。

WeakHashMap :是弱引用的一中典型应用,它使用弱引用做为内部数据的存储方案。能够做为简单的缓存表解决方案。

若是在系统中,须要一张很大的 Map 表, Map 中的表项做为缓存之用。这也意味着即便没能从该 Map 中取得相应地数据,系统也能够经过选项方案获取这些数据,虽然这样会消耗更多的时间,可是不影响系统的正常运行。这个时候,使用 WeakHashMap 是最合适的。由于 WeakHashMap 会在系统内存范围内,保存全部表项,而一旦内存不够,在 GC 时,没有被引用的又会很快被清除掉,避免系统内存溢出。


17 、有助于改善系统性能的技巧:

1 、慎用异常: for 循环中使用 try-catch 会大大下降系统性能

2 、使用局部变量:局部变量的访问速度远远高于类的静态变量的访问速度,由于类的 变量是存在在堆空间中的。

3 、位运算代替乘除法:右移表明除以2、左移表明乘以二。

4 、有的时候考虑是否能够使用数组代替位运算。

5 、一维数组代替二维数组。

6 、提取表达式:尽量让程序少作重复的计算,尤为要关注在循环体的代码,从循环提中提取重复的代码能够有效的提高系统性能。


3、Java性能优化系列之三--并发程序设计详解

一、并发程序设计模式:

( 1 ) Future-Callable 模式:

FutureTask 类实现了 Runnable 接口,能够做为单独的线程运行,其 Run 方法中经过 Sync 内部类调用 Callable 接口,并维护 Callable 接口的返回值。当调用FutureTask.get() 的时候将返回 Callable 接口的返回对象。 Callable 接口是用户自定义的实现,经过实现 Callable 接口的 call() 方法,指定 FutureTask 的实际工做内容和返回对象。 Future 取得的结果类型和 Callable 返回的类型必须一致,这是由定义 FutureTask 的时候指定泛型保证的。 Callable 要采用 ExecutorSevice 的 submit 方法提交,返回的 future 对象能够取消任务。


( 2 )Master-Worker 格式:

其核心思想是系统由两类进程协做工做: Master 进程和 Worker 进程。 Master 进程负责接收和分配任务, Worker 负责处理子任务。当各个子任务处理完成后,将结果返回给 Master 进程。由 Master 进程进行概括会汇总,从而获得系统的最终结果。


( 3 )保护暂停模式:

其核心思想是仅当服务进程准备好时,才提供服务。设想一种场景,服务器会在很短期内承受大量的客户端请求,客户端请求的数量可能超过服务器自己的即时处理能力。为了避免丢弃任意一个请求,最好的方式就是将这个客户端进行排列,由服务器逐个处理。


( 4 )不变模式:

为了尽量的去除这些因为线程安全而引起的同步操做,提升并行程序性能 ,能够使用一种不可变的对象,依靠对象的不变性,能够确保在没有同步操做的多线程环境中依然保持内部状态的一致性和正确性。


( 5 )Java 实现不变模式的条件:

1) 去除 setter 方法以及全部修改自身属性的方法。

2 )将全部属性设置为私有,并用 final 标记,确保其不可修改。

3 )确保没有子类能够重载修改它的行为。

4 )有一个能够建立完整对象的构造函数。

Java 中,不变模式的使用有: java.lang.String 类。以及全部的元数据类包装类。


( 6)生产者 - 消费者模式:

生产者进程负责提交用户请求,消费者进程负责具体处理生产者进程提交的任务。生产者和消费者之间经过共享内存缓冲区进行通讯。经过 Java 提供和饿 BlockingQueue 能够实现生产者消费者模式。


二、JDK 多任务执行框架:

( 1 )简单线程池实现:

线程池的基本功能就是进行线程的复用。当系统接受一个提交的任务,须要一个线程时,并不着急当即去建立进程,而是先去线程池查找是否有空余的进程,如有则直接使用线程池中的线程工做。若是没有,则再去建立新的进程。待任务完成后,不是简单的销毁进程,而是将线程放入线程池的空闲队列,等待下次使用。使用线程池以后,线程的建立和关闭一般由线程池维护,线程一般不会由于会执行晚一次任务而被关闭,线程池中的线程会被多个任务重复使用。

( 2 )Executor 框架:

Executor 框架提供了建立一个固定线程数量的线程池、返回一个只有一个线程的线程池、建立一个可根据实际状况进行线程数量调整的线程池、可调度的单线程池以及可变线程数量的可调度的线程池。

( 3 )自定义线程池 :

使用 ThreadPoolExecutor 接口: ThreadPoolExecutor 的构造函数参数以下:

corePoolSize :指的是保留的线程池大小

maximumPoolSize : 指的是线程池的最大大小

keepAliveTime :指的是空闲线程结束的超时时间

Unit : 是一个枚举,表示 keepAliveTime 的单位

workQueue : 表示存听任务的队列。

ThreadFactory :建立线程的时候,使用到的线程工厂

handler : 当线程达到最大限制,而且工做队列里面也已近存放满了任务的时候,决定如何处理提交到线程池的任务策略

上述的几种线程池的内部实现均使用了 ThreadPoolExecutor 接口。咱们能够自定义提交可是未被执行的任务队列被执行的顺序,常见的有直接提交的队列、有界的任务队列、无界的任务队列、优先任务队列,这样能够在系统繁忙的时候忽略任务的提交前后次序,老是让优先级高的任务先执行。使用优先队列时,必须让 target 实现 Comparable 接口。

(4)优化线程池大小:

NThreads=Ncpi*Ucpu*(1+W/C) , Java 中使用: Runtime.getRuntime().availableProcesses() 获取可用的 CPU 数量。


三、JDK 并发数据结构:

( 1 )并发 List :

Vector 或者 CopyOnWriteArrayList 是两个线程安全的 List 实现。

CopyOnWriteArrayList 很好的利用了对象的不变性,在没有对对象进行写操做以前,因为对象未发生改变,所以不须要加锁。而在试图改变对象的时候,老是先得到对象的一个副本,而后对副本进行修改,最后将副本写回。 CopyOnWriteArrayList 适合读多写少的高并发场合。而 Vector适合高并发写的场合。

( 2 )并发 Set :

synchronizedSet 适合高并发写的情景、 CopyOnWriteSet 适合读多写少的高并发场合。

( 3 )并发 Map :

ConcurrentHashMap 是专门为线程并发而设计的 HashMap ,它的 get 操做是无锁的,其 put 操做的锁粒度小于 SynchronizedHashMap ,所以其总体性能优于 SynchronizedHashMap 。

( 4 )并发 Queue :

在并发队列上, JDK 提供了两种实现,一个是以 ConcurrentLinkedQueue 为表明的高性能队列,一个是以 BlockingQueue 接口为表明的阻塞队列。若是须要一个可以在高并发时,仍能保持良好性能的队列,能够使用 ConcurrentLinkedQueue 对象。而 BlockingQueue的主要适用场景就是生产者消费者模式中的实现数据共享。 BlockingQueue 接口主要有两种实现: ArrayBlockingQueue 是一种基于数组的阻塞队列实现,也就是说其内部维护着一个定长数组,用于缓存队列中的数据对象。 LinkedBlockingQueue 则使用一个链表构成的数据缓冲队列。


4 、并发控制方法:

( 1 )Java 中的内存模型与 Volatile :

在 Java 中,每个线程有一块工做内存区,其中存放着被全部线程共享的主内存中的变量的值的拷贝。当线程执行时,它在本身的内存中操做变量。为了存取一个共享的变量,一个线程一般要先获取锁定而且清除它的内存缓冲区,这保证该共享变量从全部线程的共享内存区正确地装入到线程的工做内存区;当线程解锁时保证该工做内存区中变量的值写回到共享内存中。

( 2 )Volatile 关键字:声明为 Volatile 的变量能够作如下保证:

1 )其余线程对变量的修改,能够随即反应在当前进程中。

2 )确保当前线程对 Volatile 变量的修改,能随即写回到共享主内存中,并被其余线程所见

3 )使用 Volatile 声明的变量,编译器会保证其有序性。

4 )double 和 long 类型的非原子处理:若是一个 double 类型或者 long 类型的变量没有被声明为 volatile 类型,则变量在进行 read 和 write 操做的时候,主内存会把它当成两个 32 位的read 或者 write 操做。所以,在 32 为操做系统中,必须对 double 或者 long 进行同步

缘由在于:使用 Volatile 标志变量,将迫使全部线程均读写主内存中的对应变量,从而使得 Volatile 变量在多线程间可见。

(3)同步关键字 -Synchronized ,其本质是一把锁:

Synchronized 关键字能够做用在方法或者代码块中。看成用的是成员方法时,默认的锁是该对象 this ,这个时候通常在共享资源上进行Synchronized 操做。该关键字通常和 wait ()和 notify ()方法一块儿使用,调用这两个方法的时候通常指的是资源自己。因为全部的对象都能当成资源,所以这两个方法是从 Object 继承而来的,而不是 Thread 或者 Runnable 才具备的方法。

(4)ReentrantLock 锁:

比 Synchronized 的功能更强大,可中断、可定时。全部使用内部锁实现的功能,均可以使用重入锁实现。重入锁必须放入 finally 块中进行释放,而内部锁能够自动释放。 重入锁有着更强大的功能,好比提供了锁等待时间 (boolean tryLock(long time.TimeUnit unit)) 、支持锁中断 (lockInterruptibly()) 和快速锁轮询 (boolean tryLock()) 以及一套 Condition 机制,这个机制相似于内部锁的 wait() 和 notify() 方法。

(5)ReadWriteLock :

读写分列锁。若是 系统中读操做次数远远大于写操做,而读写锁就能够发挥巨大的做用。

(6)Condition 对象:

await() 方法和 signal() 方法。 Condition 对象须要和重入锁( ReentrantLock )配合工做以完成多线程协做的控制。

(7)Semaphore 信号量:

信号量为多线程写做提供了更为强大的控制方法。广义上讲,信号量是对锁的扩展。不管是内部锁( Synchronized )仍是重入锁( ReentrantLock ),一次都只容许一个进程访问一个资源。而信号量却能够指定多个线程同时访问某一个资源。

(8)ThreadLocal 线程局部变量:

ThreadLocal 是一种多线程间并发访问变量的解决方案。与synchronized 等加锁方式不一样, ThreadLocal 彻底不提供锁,而使用以空间换时间的手段,为每一个线程提供变量的独立副本,以保障线程安全,所以并非一种数据共享的解决方案。


五、同步工具类:

( 1 ) CountDownLatch (闭锁):确保一个服务不会开始,直到它依赖的其余服务都准备就绪。 CountDownLatch 做用犹如倒计时计数器,调用 CountDownLatch 对象的 countDown 方法就将计数器减 1 ,当计数到达 0 时,则全部等待者或单个等待者开始执行。好比有 10 个运动员的田径比赛 , ,有两个裁判 A 和 B , A 在起点吹哨起跑, B 在终点记录记录并公布每一个运动员的成绩。刚开始的时候,运动员们都趴在跑道上( A.await() )等到裁判吹哨。 A 吹哨耗费了 5 秒,此时调用 A.countDown() 方法将等待时间减为 4 秒。当减为 0 的时候,全部的运动员开始起跑。这个时候, B 裁判开始工做。启动一个初始值为 10 的定时器,每当有一个运动员跑到重点的时候,就将计数器减一,表明已经有一个运动员跑到终点。当计时器为 0 的时候,表明全部的运动员都跑到了终点。此时能够根据公布成绩了。

( 2 ) CylicBarrier (关卡):

1 )相似于闭锁,它们可以阻塞一组线程直到某些事件发生

2 )与同步锁的不一样之处是一个能够重用,一个不能够重用

3 )全部线程必须同时到达关卡点,才能继续处理。

相似组团旅游,导游就是一个关卡。表示你们彼此等待,你们集合好后才开始出发,分散活动后又在指定地点集合碰面,这就比如整个公司的人员利用周末时间集体郊游同样,先各自从家出发到公司集合后,再同时出发到公园游玩,在指定地点集合后再同时开始就餐。

( 3 ) Exchanger :使用在两个伙伴线程之间进行数据交换,这个交换对于两个线程来讲都是安全的。

讲解 Exchanger 的比喻:比如两个毒贩要进行交易,一手交钱、一手交货,无论谁先来到接头地点后,就处于等待状态了,当另一方也到达了接头地点(所谓到达接头地点,也就是到到达了准备接头的状态)时,二者的数据就当即交换了,而后就又能够各忙各的了。

exchange 方法就至关于两手高高举着待交换物,等待人家前来交换,一旦人家到来(即人家也执行到 exchange 方法),则二者立马完成数据的交换。


六、关于死锁:

(1)死锁的四个条件:

1) 互斥条件:一个资源只能被一个线程使用:

2 )请求与保持条件:一个线程因请求资源而阻塞时,对已得到则资源保持不放。

3 )不剥夺条件:进程已经得到的资源,在未使用完以前,不能强行剥夺。

4 )循环等待条件:若干个线程已经造成了一种头尾相接的循环等待资源关系。

(2)常见的死锁:静态顺序死锁、动态顺序死锁、协做对象间的死锁、线程饥饿死锁。

(3)如何尽可能避免死锁:

1 )制定锁的顺序,来避免死锁

2 )尝试使用定时锁( lock.tryLock(timeout) )

3 )在持有锁的方法中进行其余方法的调用,尽可能使用开放调用(当调用方法不须要持有锁时,叫作开放调用)

4 )减小锁的持有时间、减少锁代码块的粒度。

5 )不要将功能互斥的 Task 放入到同一个 Executor 中执行。


七、 代码层面对锁的优化机制:

1 、避免死锁

2 、减小锁持有时间,代码块级别的锁,而不是方法级别的锁

3 、减少锁粒度, ConcurrentHashMap 分段加锁

4 、读写锁代替独占锁

5 、锁分离,例如 LinkedBlockingQueue 的尾插头出的特色,用两把锁 (putLock takeLock) 分离两种操做。

6 、重入锁和内部锁

重入锁( ReentrantLock )和内部锁( Synchronized ):全部使用内部锁实现的功能,均可以使用重入锁实现。重入锁必须放入 finally 块中进行释放,而内部锁能够自动释放。

重入锁有着更强大的功能,好比提供了锁等待时间 (boolean tryLock(long time.TimeUnit unit))、支持锁中断 (lockInterruptibly()) 和快速锁轮询 (boolean tryLock()) 以及一套 Condition 机制,这个机制相似于内部锁的 wait() 和 notify() 方法。想要获取多线程面试题的能够加群:650385180,面试题及答案在群的共享区。

7 、锁粗化:

虚拟机在遇到一连串连续的对同一个锁不断进行请求和释放从操做的时候,便会把全部的锁操做整合成对锁的一次请求,从而减小对锁的请求同步次数。


八、 Java 虚拟机层面对锁的优化机制:

1.自旋锁:

因为线程切换(线程的挂起和恢复)消耗的时间较大,则使线程在没有得到锁时,不被挂起,而转而执行一个空循环。在若干空循环后,线程若是得到了锁,而继续执行,若线程依然不能得到锁,而才被挂起。

2.锁消除:

JVM 经过对上下文的扫描,去除不可能存在共享资源竞争的锁,这样能够节省毫无心义的请求锁时间。好比单线程中或者非共享资源的常使用的 StringBuffer 和 Vector 。

3.锁偏向:

若某一个锁被线程获取后,便进入偏向模式,当线程再次请求这个锁时,无需进行相关的同步操做,从而节省了操做时间。


9 、 Java 无锁实现并发的机制:

( 1 )非阻塞的同步 / 无锁: ThreadLocal ,让每一个进程拥有各自独立的变量副本,所以在并行计算时候,无须相互等待而形成阻塞。 CVS 算法的无锁并发控制方法。

( 2 )原子操做: java.util.concurrent.atomic 包。


4、Java性能优化系列之四--Java内存管理与垃圾回收机制详解

1 、 JVM 运行时数据区域

( 1 )程序计数器:每个 Java 线程都有一个程序计数器来用于保存程序执行到当前方法的哪个指令。此内存区域是惟一一个在 JVM Spec 中没有规定任何 OutOfMemoryError 状况的区域。

( 2 ) Java 虚拟机栈:该块内存描述的是 Java 方法调用的内存模型,每一个方法在被执行的时候,都会同时建立一个帧( Frame )用于存储本地变量表、操做栈、动态连接、方法出入口等信息。

( 3 )本地方法栈。本地方法调用的内存模型。

( 4 )Java 堆。 Java 中的对象以及类的静态变量的存放地方。

( 5 )方法区:方法区中存放了每一个 Class 的结构信息,包括常量池、字段描述、方法描述等等

( 6 )运行时常量池: Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量表 (constant_pool table) ,用于存放编译期已可知的常量,这部份内容将在类加载后进入方法区(永久代)存放。可是 Java 语言并不要求常量必定只有编译期预置入 Class 的常量表的内容才能进入方法区常量池,运行期间也可将新内容放入常量池(最典型的 String.intern() 方法)。运行时常量池是方法区的一部分,天然受到方法区内存的限制,当常量池没法在申请到内存时会抛出 OutOfMemoryError 异常。

( 7 )本机直接内存( Direct Memory )

在 JDK1.4 中新加入了 NIO 类,引入一种基于渠道与缓冲区的 I/O 方式,它能够经过本机 Native 函数库直接分配本机内存,而后经过一个存储在 Java 堆里面的 DirectByteBuffer 对象做为这块内存的引用进行操做。这样能在一些场景中显著提升性能,由于避免了在 Java 对和本机堆中来回复制数据。


二、Java 类加载机制的特色:

(1)基于父类的委托机制:运行一个程序时,老是由 AppClass Loader (系统类加载器)开始加载指定的类,在加载类时,每一个类加载器会将加载任务上交给其父,若是其父找不到,再由本身去加载, Bootstrap Loader (启动类加载器)是最顶级的类加载器了,其父加载器为 null 。若是父类加载器找不到给定的类名,则交由子加载器去加载,若是最低一层的子加载器也没法找到,则抛出异常。

(2)全盘负责机制:所谓全盘负责,就是当一个类加载器负责加载某个 Class 时,该 Class 锁依赖的和引用的其余 Class 也将由该类加载器负责载入,除非显式使用另一个类加载器来载入。

(3)缓存机制:缓存机制将会保证全部加载过的 Class 对象都会被缓存,当程序中须要使用某个 Class 时,类加载器会先从缓冲区中搜寻该 Class ,只有当缓存区中不存在该 Class 对象时,系统才会读取该类对应的二进制数据,并将其转化为 Class 对象,存入缓存区中。这就是为何修改了 Class 后,必须从新启动 JVM ,程序所作的修改才会生效的缘由。同时,往们比较 A.getClass() 与 B.getClass() 是否相等时,直接使用 == 比较,由于缓存机制保证类的字节码在内存中只可能存在一份。

(4)类加载器的三种方法以及其区别:

1)命令行启动应用时候由 JVM 初始化加载

2)经过 Class.forName() 方法动态加载

3)经过 ClassLoader.loadClass() 方法动态加载 // 使用 Class.forName() 来加载类,默认会执行初始化块 , // 使用 Class.forName() 来加载类,并指定 ClassLoader ,初始化时不执行静态块。

4)区别:使用 ClassLoader.loadClass() 来加载类,不会执行初始化块,


3 、类的主动引用

什么状况下须要开始类加载过程的第一个阶段,也即类的初始化阶段。 Java 虚拟机规定了有且只有 5 种状况下必须当即对类进行初始化:

(1)遇到 new 、 getstatic 、 putstatic 或 invokestatic 这四条字节码指令时,若是类没有进行过初始化,则须要触发其初始化。(并且初始化的时候按照先父后子的顺序)。这四条指令最多见的 Java 代码场景是:使用 new 关键字实例化对象的时候、读取或设置一个类的静态字段(被 final 修饰,已在编译时期把结果放入常量池的静态字段除外)、调用一个类的静态方法的的时候。

(2)使用 java.lang.reflect 包的方法对类进行反射调用的时候,若是类没有进行过初始化,则须要先对其进行初始化。

(3)当初始化一个类的时候,若是发现其父类尚未进行过初始化,则须要先触发其父类的初始化。可是一个接口在初始化时,并不要求其父类接口所有都完成了初始化,只有在真正使用到父接口的时候(如引用接口中定义的常量)才会被初始化。

(4)当虚拟机启动时,用户须要指定一个要执行的主类(包含 main ()方法的那个类),虚拟机会先初始化这个主类。

(5)当使用 jdk1.7 的动态语言支持时,若是一个 java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic 、 REF_putStatic 、 REF_invokeStatic 的方法句柄,而且这个方法句柄所对应的类没有初始化过,则须要先触发其初始化。


4 、类的被动引用

一、对于静态字段,只有直接定义这个字段的类才会被初始化,所以经过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。

二、经过数组定义来引用类,不会触发类的初始化 SuperClass[]sca=new SuperClass[10].

三、常量在编译阶段会存入调用类的常量池中,本质上并无直接引用到定义常量的类,所以不会触发常量的类的初始化。

5 、 Java 对象的建立过程以及如何保证对象建立的多线程的安全性:

虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,而且检查这个符号引用表明的类是否已经被加载、解析和初始化过。若是没有则进行类加载过程。

在类加载经过后,接下来虚拟机将为新生对象分配内存。对象所需的内存的大小在类加载完成后即可彻底肯定。为对象分配空间的任务等价于把一块肯定大小的内存从 Java 堆中划分出来。

保证多线程的安全性。 有两种方案,一种是对分配内存的动做进行同步操做,实际上虚拟机采用 CAS 加上失败重试的方式保证更新操做的原子性。另外一种是把内存分配的动做按照线程划分在不一样的空间中进行。即为每一个线程在 Java 堆中预先分配一小块内存,成为本地线程分配缓冲( TLAB )。哪一个线程要分配内存,就在哪一个 TLAB 上分配,只有 TLAB 用完并分配新的 TLAB时,才须要分配新的 TLAB 。

六、 何时判断一个对象能够被回收?

用可达性分析算法。这个算法的基本思路就是经过一系列的成为“ GC roots ”的对象做为起始点,从这些节点开始向下搜索,若是一个对象到 GCroots 没有任何引用链相连,则证实此对象是不可用的。可做为 GCroots 的对象包括虚拟机栈中引用的对象、方法区中常量引用的对象、方法区中静态属性引用的对象或者本地方法栈中 JNI 引用的对象,这些对象的共同点都是生命周期与程序的生命周期同样长,通常不会被 GC 。判断一个对象死亡,至少经历两次标记过程:若是对象在进行可达性算法后,发现没有与 GC Roots 相链接的引用链,那他将会被第一次标记,并在稍后执行其 finalize ()方法。执行是有机会,并不必定执行。稍后 GC 进行第二次标记,若是第一次标记的对象在 finalize ()方法中拯救本身,好比把本身赋值到某个引用上,则第二次标记时它将被移除出“即将回收”的集合,若是这个时候对象尚未逃脱,那基本上就会被 GC 了。

7 、 关于 finalize ()方法的做用的说明:

finalize ()方法的工做原理理论上是这样的:一旦垃圾回收器准备好释放占用的存储空间,将首先调用其 finalize ()方法,而且在下一次垃圾回收动做发生时,才会真正回收对象占用的内存,因此使用 finalize ()的目的就是在垃圾回收时刻作一些重要的清理工做。咱们知道,使用 GC 的惟一缘由就是回收程序再也不使用的内存,因此对于与垃圾回收有关的任何行为来讲,包括 finalize() 方法,它们也必须同内存及其回收有关。我的认为 Java 对象的 finalize ()方法有两个做用( 1 )回收经过建立对象方式之外的方式为对象分配了存储空间。好比,好比在 Java 代码中采用了 JNI 操做,即在内存分配时,采用了相似 C 语言中的 malloc 函数来分配内存,并且没有调用free 函数进行释放。此时就须要在 finalize ()中用本地方法调用 free 函数以释放内存。( 2 )对象终结条件的验证,即用来断定对象是否符合被回收条件。好比,若是要回收一个对象,对象被清理时应该处于某种状态,好比说是一个打开的文件,在回收以前应该关闭这个文件。只要对象中存在没有被适当清理的部分, finalize ()就能够用来最终法相这种状况。由于对象在被清理的时候确定处于生命周期的最后一个阶段,若是此时还含有一些未释放的资源,则有能力释放这些资源。这个不是 C/C++ 里面的析构函数,它运行代价高昂,不肯定性大,没法保证各个对象的调用顺序。须要关闭外部资源之类的事情,基本上它能作的使用 try-finally 能够作的更好。

8 、 一个类被回收的条件:

(1)该类全部的实例都已经为 GC ,也就是说 JVM 中不存在该 Class 的任何实例。

(2)加载该类的 ClassLoader 已经被 GC 。

(3)该类对应的 java.lang.Class 对象没有在任何地方被引用,如不能在任何地方经过反射访问类的方法。

九、 垃圾回收算法 :

(1)标记 - 清除算法:标记阶段根据根节点标记全部从根节点开始的可达对象。则未被标记的对象就是未被引用的垃圾对象,而后在清除阶段,清楚全部未被标记的对象。其最大缺点是空间碎片。

(2)复制算法:将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,以后清楚正在使用的内存快中的全部对象,而后交换两个内存的角色。完成垃圾回收。这种算法比较适合新生代,由于在新生代,垃圾对象一般会多于存活对象,复制算法效果较好。 Java 的新生代串行 GC 中,就使用了复制算法的思想。新生代分为 eden 空间、 from 空间和 to 空间三个部分。 From 和 to 空间能够视为用于复制的两块大小相同、地位相等、且能够进行角色互换的空间块。 From 和 to 空间也成为 survivor 空间,即幸存者空间,用于存放未被回收的对象。

(3)标记 - 压缩算法:标记过程与标记清楚算法同样,但后续不是直接对可回收对象进行清理,而是让全部存活的对象向一段移动,而后直接清理掉端边界之外的内存。适合老年代的回收。

(4)分代收集算法。

10 、 垃圾回收器:

( 1 )Serial 收集器

单线程收集器,收集时会暂停全部工做线程(咱们将这件事情称之为 Stop The World ,下称 STW ),使用复制收集算法,虚拟机运行在 Client 模式时的默认新生代收集器。

(2) ParNew 收集器就是 Serial 的多线程版本,除了使用多条收集线程外,其他行为包括算法、 STW 、对象分配规则、回收策略等都与 Serial 收集器一摸同样。对应的这种收集器是虚拟机运行在 Server 模式的默认新生代收集器,在单 CPU 的环境中, ParNew 收集器并不会比 Serial 收集器有更好的效果。

(3)Parallel Scavenge 收集器(下称 PS 收集器)也是一个多线程收集器,也是使用复制算法,但它的对象分配规则与回收策略都与 ParNew 收集器有所不一样,它是以吞吐量最大化(即 GC 时间占总运行时间最小)为目标的收集器实现,它容许较长时间的 STW 换取总吞吐量最大化。

(4)4.Serial Old 收集器 Serial Old 是单线程收集器,使用标记-整理算法,是老年代的收集器

(5) Parallel Old 收集器

老年代版本吞吐量优先收集器,使用多线程和标记-整理算法, JVM 1.6 提供,在此以前,新生代使用了 PS 收集器的话,老年代除 Serial Old 外别无选择,由于 PS 没法与 CMS 收集器配合工做。

(6)CMS ( Concurrent Mark Sweep )收集器

CMS 是一种以最短停顿时间为目标的收集器,使用 CMS 并不能达到 GC 效率最高(整体 GC时间最小),但它能尽量下降 GC 时服务的停顿时间,这一点对于实时或者高交互性应用(譬如证券交易)来讲相当重要。

( 7 )G1 收集器。

11 、内存分配与回收策略:

( 1 )规则一:一般状况下,对象在 eden 中分配。当 eden 没法分配时,触发一次 Minor GC 。

( 2 )规则二:配置了 PretenureSizeThreshold 的状况下,对象大于设置值将直接在老年代分配。

( 3 )规则三:在 eden 通过 GC 后存活,而且 survivor 能容纳的对象,将移动到 survivor 空间内,若是对象在 survivor 中继续熬过若干次回收(默认为 15 次)将会被移动到老年代中。回收次数由 MaxTenuringThreshold 设置。

( 4 )规则四:若是在 survivor 空间中相同年龄全部对象大小的累计值大于 survivor 空间的一半,大于或等于该年龄的对象就能够直接进入老年代,无需达到 MaxTenuringThreshold 中要求的年龄。

( 5 )规则五:在 Minor GC 触发时,会检测以前每次晋升到老年代的平均大小是否大于老年代的剩余空间,若是大于,改成直接进行一次 Full GC ,若是小于则查看 HandlePromotionFailure 设置看看是否容许担保失败,若是容许,那仍然进行 Minor GC ,若是不容许,则也要改成进行一次 Full GC 。

十一、 关于 Minor GC 与 Full GC:

Java 堆,分配对象实例所在空间,是 GC 的主要对象。分为新生代 (Young Generation/New)和老年代 (Tenured Generation/Old) 。新生代又划分红 Eden Space 、 From Survivor/Survivor 0 、To Survivor/Survivor 1 。

新生代要如此划分是由于新生代使用的 GC 算法是复制收集算法。新生代使用赋值收集算法,可是为了内存利用率,只使用一个 Survivor 空间来做为轮转备份(之因此把该空间分为 FromSpace 和 ToSpace 两部分是为了在 Minor GC 的时候把一些 age 大的对象重新生代空间中复制到老年代空间中)这种算法效率较高,而 GC 主要是发生在对象常常消亡的新生代,所以新生代适合使用这种复制收集算法。因为有一个假设:在一次新生代的 GC(Minor GC) 后大部分的对象占用的内存都会被回收,所以留存的放置 GC 后仍然活的对象的空间就比较小了。这个留存的空间就是 Survivor space : From Survivor 或 To Survivor 。这两个 Survivor 空间是同样大小的。例如,新生代大小是 10M(Xmn10M) ,那么缺省状况下 (-XX:SurvivorRatio=8) , Eden Space 是 8M , From 和 To 都是 1M 。

在 new 一个对象时,先在 Eden Space 上分配,若是 Eden Space 空间不够就要作一次 Minor GC 。 Minor GC 后,要把 Eden 和 From 中仍然活着的对象们复制到 To 空间中去。若是 To 空间不能容纳 Minor GC 后活着的某个对象,那么该对象就被 promote 到老年代空间。从 Eden 空间被复制到 To 空间的对象就有了 age=1 。此 age=1 的对象若是在下一次的 Minor GC 后仍然存活,它还会被复制到另外一个 Survivor 空间 ( 若是认为 From 和 To 是固定的,就是又从 To 回到了From 空间 ) ,而它的 age=2 。如此反复,若是 age 大于某个阈值 (-XX:MaxTenuringThreshold=n),那个该对象就也能够 promote 到老年代了。

若是 Survivor 空间中相同 age( 例如, age=5) 对象的总和大于等于 Survivor 空间的一半,那么 age>=5 的对象在下一次 Minor GC 后就能够直接 promote 到老年代,而不用等到 age 增加到阈值。

在作 Minor GC 时,只对新生代作回收,不会回收老年代。即便老年代的对象无人索引也将仍然存活,直到下一次 Full GC 。

在发生 Minor GC 以前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代全部对象总空间,若是这个条件成立,那么 Minor GC 能够确保是安全的。若是通过 Minor GC 后仍有大量对象存活的状况,则须要老年代进行分配担保,把 Survior 没法容纳的对象直接进入老年代。

13 、四种引用类型:

( 1 )强引用:直接关联,虚拟机永远不会回收。

( 2 )软引用:描述一些还有用但并不是必须的对象,虚拟机会在抛出内存溢出异常以前会对 这些对象进行第二次回收。

( 3 )弱引用:虚拟机必定会回收的对象

( 4 )虚引用:为一个对象设置虚引用关联的惟一目的就是能在这个对象被收集器回收时收到一个系统通知。

14 、关于 Java 中生成对象的 4 种方式与区别:

( 1 )使用 new 操做符,这是最广泛的一种(会调用相应的构造函数):

如: String s=new String("abc");

( 2 )使用反射动态生成( 会调用相应的构造函数 ):

利用 Class , ClassLoader , Constructor 中的方法能够动态的生成类实例

如: Object o=Class.forName("java.lang.String").newInstance();

Object o=String.class.getClassLoader.loadClass("java.lang.String").newInstance();

以上的方式须要目标类拥有公有无参构造函数

如下使用 Constructor 进行动态生成

class User{

public User(String user,Integer id){}

}

Constructor c=User.class.getConstructor(new Class[]{String.class,Integer.class});

User user=(User)c.newInstance(new Object[]{"zhang san",123});

( 3 )使用克隆生成对象( 不会调用构造函数 )

例如使用一个实现了 Cloneable 接口的对象,调用其 clone() 方法得到该对象的一份拷贝,使用 Java 序列化方式实现深拷贝。

( 4 )利用反序列化从流中生成对象( 不会调用构造函数 ):

利用 ObjectInptuStream 的 readObject() 方法生成对象


5、Java性能优化系列之五--JavaIO

1 、关于 Java 序列化与反序列化:

(1)做用:

一、实现对象状态的保存到本地,以便下一次启动虚拟机的时候直接读取保存的序列化字节生成对象,而不是初始化对象; 2 、实现对象的网络传输( RMI 分布对象); 3 、实现对象的深拷贝。

一:对象序列化能够实现分布式对象。主要应用例如: RMI 要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时同样。

二: java 对象序列化不只保留一个对象的数据,并且递归保存对象引用的每一个对象的数据。能够将整个对象层次写入字节流中,能够保存在文件中或在网络链接上传递。利用对象序列化能够进行对象的 " 深复制 " ,即复制对象自己及引用的对象自己。序列化一个对象可能获得整个对象序列。

(2)基本方式:

ObjectOutputStream 只能对 Serializable 接口的类的对象进行序列化。默认状况下, ObjectOutputStream 按照默认方式序列化,这种序列化方式仅仅对对象的非 transient 的实例变量进行序列化,而不会序列化对象的 transient 的实例变量,也不会序列化静态变量。

当 ObjectOutputStream 按照默认方式反序列化时,具备以下特色:

1 ) 若是在内存中对象所属的类尚未被加载,那么会先加载并初始化这个类。若是在 classpath 中不存在相应的类文件,那么会抛出 ClassNotFoundException ;

2 ) 在反序列化时不会调用类的任何构造方法。

若是用户但愿控制类的序列化方式,能够在可序列化类中提供如下形式的 writeObject() 和 readObject() 方法。

private void writeObject(java.io.ObjectOutputStream out) throws IOException

private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;

当 ObjectOutputStream 对一个 Customer 对象进行序列化时,若是该对象具备 writeObject() 方法,那么就会执行这一方法,不然就按默认方式序列化。在该对象的 writeObjectt() 方法中,能够先调用 ObjectOutputStream 的 defaultWriteObject() 方法,使得对象输出流先执行默认的序列化操做。同理可得出反序列化的状况,不过此次是 defaultReadObject() 方法。

有些对象中包含一些敏感信息,这些信息不宜对外公开。若是按照默认方式对它们序列化,那么它们的序列化数据在网络上传输时,可能会被不法份子窃取。对于这类信息,能够对它们进行加密后再序列化,在反序列化时则须要解密,再恢复为原来的信息。

默认的序列化方式会序列化整个对象图,这须要递归遍历对象图。若是对象图很复杂,递归遍历操做须要消耗不少的空间和时间,它的内部数据结构为双向列表。

在应用时,若是对某些成员变量都改成 transient 类型,将节省空间和时间,提升序列化的性能。

|-1 、实体对象实现 seriable 接口以及自定义 seriousid 。

|-2 、 ObjectOutputStream out= new ObjectOutputStream(baos);

out.writeObject(new PersonDemo("rollen", 20));

out.close();

|-3 、 ByteArrayInputStream bais=new ByteArrayInputStream(baos.toByteArray());

ObjectInputStream input=new ObjectInputStream(bais);

Object obj =input.readObject();

input.close();

(3)Java 自定义序列化反序列化:复写实现了 seriliable 的实体类的 readObject() 和 writeObject() 的方法的缘由:

有些对象中包含一些敏感信息,这些信息不宜对外公开。若是按照默认方式对它们序列化,那么它们的序列化数据在网络上传输时,可能会被不法份子窃取。对于这类信息,能够对它们进行加密后再序列化,在反序列化时则须要解密,再恢复为原来的信息。此时便不能使用默认的 readObject 和 writeObject() 方法。

private void writeObject(java.io.ObjectOutputStream out) throws IOException{

out.defaultWriteObject();

out.writeUTF(name);

}

private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{

in.defaultReadObject();

name=in.readUTF();

}

通常状况直接实现 Serializable 接口就能够实现序列化的要求,可是有些状况须要对序列化作一些特殊的要求。

(4)Transits 关键字的做用:

屏蔽一些不想进行序列化的成员变量,解屏蔽的方法能够用(3)

(5)Externalize 的做用:

Externalizable 接口继承自 Serializable 接口,若是一个类实现了 Externalizable 接口,那么将彻底由这个类控制自身的序列化行为。 Externalizable 接口声明了两个方法:

public void writeExternal(ObjectOutput out) throws IOException

public void readExternal(ObjectInput in) throws IOException , ClassNotFoundException

前者负责序列化操做,后者负责反序列化操做。

在对实现了 Externalizable 接口的类的对象进行反序列化时, 会先调用类的不带参数的构造方法,这是有别于默认反序列方式的。若是把类的不带参数的构造方法删除 ,或者把该构造方法的访问权限设置为 private 、默认或 protected 级别,会抛出 java.io.InvalidException: no valid constructor 异常。

(6)与 Java 构造函数的关系:

实现了 Externalizable 接口的类的对象进行反序列化时,会先调用类的不带参数的构造方法;而实现了 Serializable 接口的类的对象进行反序列化时,不会调用任何构造方法。仅仅是根据所保存的对象的状态信息,在内存中从新构建对象!

(7)注意事项:

1) 序列化运行时使用一个称为 serialVersionUID 的版本号与每一个可序列化类相关联,该序列号在反序列化过程当中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。为它赋予明确的值。显式地定义 serialVersionUID 有两种用途:

在某些场合,但愿类的不一样版本对序列化兼容,所以须要确保类的不一样版本具备相同的 serialVersionUID ;

在某些场合,不但愿类的不一样版本对序列化兼容,所以须要确保类的不一样版本具备不一样的 serialVersionUID 。

2) java 有不少基础类已经实现了 serializable 接口,好比 string,vector 等。可是好比 hashtable 就没有实现 serializable 接口。

3)、并非全部的对象均可以被序列化。因为安全方面的缘由一个对象拥有 private,public 等 field, 对于一个要传输的对象 , 好比写到文件 , 或者进行 rmi 传输等等 , 在序列化进行传输的过程当中 ,这个对象的 private 等域是不受保护的;资源分配方面的缘由 , 好比 socket,thread 类 , 若是能够序列化 , 进行传输或者保存 , 也没法对他们进行从新的资源分配 , 并且 , 也是没有必要这样实现 .

4)反序列化对象时,并不会调用该对象的任何构造方法,仅仅是根据所保存的对象的状态信息,在内存中从新构建对象!

5)当一个对象被序列化时,只保存对象的非静态成员变量,不能保存任何的成员方法和静态的成员变量

6)若是一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存!这是能用序列化解决深拷贝的重要缘由。

(8)序列化与单例模式的冲突解决办法:

另外还有两个自定义序列化方法 writeReplace 和 readResolve ,分别用来在序列化以前替换序列化对象 和 在反序列化以后的对返回对象的处理。通常能够用来避免 singleTon 对象跨 jvm 序列化和反序列化时产生多个对象实例,事实上 singleTon 的对象一旦可序列化,它就不能保证 singleTon 了。 JVM 的 Enum 实现里就是重写了 readResolve 方法,由 JVM 保证 Enum 的值都是 singleTon 的,因此建议多使用 Enum 代替使用 writeReplace 和 readResolve 方法。

Java 代码

private Object readResolve()

{

return INSTANCE;

}

private Object writeReplace(){

return INSTANCE;

}

注: writeReplace 调用在 writeObject 前 ;readResolve 调用在 readObject 以后。

(9)序列化解决深拷贝的代码:

public Object deepClone() throws IOException, OptionalDataException,

ClassNotFoundException {

// 将对象写到流里

ByteArrayOutputStream bo = new ByteArrayOutputStream();

ObjectOutputStream oo = new ObjectOutputStream(bo);

oo.writeObject(this); // 从流里读出来

ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());

ObjectInputStream oi = new ObjectInputStream(bi);

return (oi.readObject());

}

对象所属的类要实现 Serializable 接口。同时将该方法写入到对象所属的类中。

深拷贝的时候,调用该方法便可。


二、JavaIO 中的装饰模式:

Java 中使用的最普遍的装饰器模式就是 JavaIO 类的设计。好比, OutPutStream 是输出流的基类,其子类有 FileOutputStream 和 FilterOutputStream, 而 FilterOutputStream 的子类有 BufferedOutputStream 和 DataOutputStream 两个子类。其中, FileOutputStream 为系统的核心类,它实现了向文件写数据的功能,使用 DataOutputStream 能够在 FileOutputStream 的基础上增长多种数据类型的写操做支持( DataOutputStream 类中有 writeUTF 、 writeInt 等函数),而 BufferdOutputStream 装饰器能够对 FileOutputStream 增长缓冲功能,优化 I/O 性能。


三、JavaIO 流的使用场景:

(1)IO 流:用于处理设备上的数据,这里的设备指的是:硬盘上的文件、内存、键盘输入、屏幕显示。

(2)字节流和字符流:字节流好理解,由于全部格式的文件都是以字节形式硬盘上存储的,包括图片、 MP3 、 avi 等,所以字节流能够处理全部类型的数据。字符流读取的时候读到一个或多个字节时(中文对应的 字节数是两个,在 UTF-8 码表中是三个字节)时,先去查指定的编码表,将查到的字符返回。字符流之因此出现,就是由于有了文件编码的不一样,而有了对字符进行高效操做的字符流对象。所以,只要是处理纯文本数据,就要优先考虑使用字符流,除此以外都使用字节流。

(3)流操做的基本规律:

1 )、明确数据源和数据汇,目的是明确使用输入流仍是输出流。

2 )、明确操做的数据是不是纯文本数据。

3 )、是否须要进行字节流和字符流的转换。

4 )、是否须要使用缓存。

(4)实例说明流操做的基本流程:把键盘上读入的数据以指定的编码存入到文件中。

1 )明白数据源:键盘输入, System.in ,可用 InputStream 和 Reader

2 )发现 System.in 对应的流是字节读入流,因此要将其进行转换,将字节转换为字符。

3 )因此要使用 InputStreamReader 转换流

4 )若是想提升效率,要加入缓存机制,那么就要加入字符流的缓冲区。 BufferedReader,所以前四步构造出的输入流为:

BufferedReader bur = new BufferedReader(new InputStreamReader(System.in));

5 )明白数据汇:既然是数据汇,则必定是输出流,能够用 OutputStream 或 Writer 。

6 )往文件中存储的都是文本文件,所以选用 Writer 。

7 )由于要指定编码表,因此使用 Writer 中的转换流, OutputStreamWriter 。

注意:虽然最终是文件,可是不能够选择 FileWriter ,由于该对象是使用默认编码表。

8 )是否要提升效率,选择 BufferedWriter 。

9 )转换输出流须要接收一个字节输出流进来,因此要是用 OutputStream 体系,而最终输出到一个文件中。那么就要使用 OutputStream 体系中能够操做的文件的字符流对象, FileOutputStream 。

10 )经过前面的分析,获得的输出流对象以下:

//String charSet = System.getProperty("file.encoding");

String charSet = "utf-8";

BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new

FileOutputStream("a.txt"),charSet);


4 、能够和流相关联的集合对象 Properties

Map

|--HashTable

|--Properties

Properties :该集合不须要泛型,由于该集合中的键值都是 String 类型。


五、其余流对象:

( 1 )打印流:

PrintStream :是一个字节打印流 System.out 对应的就是 PrintStream 。它的构造函数能够接收三种数据类型的值:字符串路径、 File 对象、 OutputStream (当为 System.out 的时候即把输入显示到屏幕上)

PrintWriter :是一个字符打印流。构造函数能够接收四种类型的值。字符串路径、 File 对象(对于这两中类型的数据,还能够指定编码表。也便是是字符集)、 OutPutSream 、 Writer (对于3、四类型的数据,能够指定自动刷新,注意:当自动刷新的值为 true 时,只有三个方法能够用: printlf 、 printf 、 format )

(2)管道流: PipedOutputStream 和 PipedInputStream 。通常在多线程中通讯的时候用。

(3)RandomAccessFile :该对象不是流体系中的一员,可是该队选中封装了字节流,同时还封装了一个缓冲区(字节数组),经过内部的指针来操做数组中的数据。该对象特色:只能操做文件和对文件读写均可以。多用于多线程下载。、

(4)合并流:能够将多个读取流合并成一个流。其实就是将每个读取流对象存储到一个集合中,最后一个流对象结尾做为这个流的结尾。

(5)对象的序列化。 ObjectInputStream 和 ObjectInputStream 。

(6)操做基本数据类型的流对象: DataInputStream 和 DataOutputStream 。

(7)操纵内存数组的流对象,这些对象的数据源是内存,数据汇也是内存: ByteArrayInputStream 和 ByteArrayOutputStream , CharArrayReader 和 CharArrayWriter 。这些流并未调用系统资源,使用的是内存中的数组,因此在使用的时候不用 close 。

(8)编码转换:

在 IO 中涉及到编码转换的流是转换流和打印流,可是打印流只有输出。转换流是能够指定编码表的,默认状况下,都是本机默认的编码表, GBK 。能够经过: Syetem.getProperty( “file.encoding”) 获得。字符串到字节数组成为编码的过程,经过 getBytes(charset) 完成,从字节数组到字符串的过程是解码的过程,经过 String 类的构造函数完成 String ( byte[],charset ) .

(9)编码实例与解析

(10)JavaNIO 的 Charset 类专门用来编码和解码。

(做者:伊竹凌,原文连接:https://www.jianshu.com/p/1eee9e1c8225)

粉丝福利:

  • 程序员/产品/运营/测试等互联网专业资料、笔面试题获取;

  • 各大互联网公司简历内推:微信扫描二维码,或加微信x123z123xy,备注“内推”;

  • 入群:微信扫描二维码,或加微信x123z123xy,备注“入群”,人脉共享,一块儿交流;

相关文章
相关标签/搜索