前言

英文版《Java Performance The Definitive Guide》,出版于2014年,中文版出版于2016年,相比于《深入理解Java虚拟机》,讲的更加透彻、准确。适合读一遍,然后做长期参考。

作者Scott Oaks是Oracle公司的一位架构师,专注研究Oracle中间件软件的性能。加入Oracle之前,他曾于Sun Microsystem公司任职多年,在多个技术领域都有建树,包括SunOS的内核、网络程序设计、Windows系统的远程方法调用(RPC)以及 OPEN LOOK虚拟窗口管理器。1996年,Scott成为Sun公司的Java布道师,并于2001年加入Sun公司的Java性能小组——从那时起他就一直专注于Java的性能提升。此外,Scott也在O’Reilly出版社出版了多部书籍,包括Java Security、Java Threads、JXTA in a Nutshell和Jini in a Nutshell。

大约两年半之前,根据同事推荐看了《深入理解Java虚拟机》,这是一本入门JVM的好书。

现在有幸又被推荐了这本书。看完这本书,大约用了1个月,断断续续,中间经过一个端午节,还速读了7部小说《明朝那些事》,大致看了《南渡北归》,扫了几眼《人类简史》,很多也是,看了,忘了,真是想找到一种克服遗忘的办法。

据说,克服遗忘最好的办法是重复,重复,需要的是时间,于是也越发感觉时光如电。

关于遗忘,也可以从学习方法角度找下原因,此前有小总结:http://blog.csdn.net/puma_dong/article/details/45345397#t0

原文链接:Link

第一章 导论

本书的侧重点是拓展知识的深度。本书的知识分为两大类:JVM自身的调优,Java平台(既指Java语言,比如线程和同步,也指JavaAPI,比如XML解析性能)的特性对性能的影响。

请记住,JVM只占整体性能的一小部分,更多的是操作系统、数据库,应用系统,不过本书不是讲整体分析的,本书假设性能瓶颈在Java。

第二章 性能测试方法

原则1:测试真实应用;

原则2:理解批处理流逝时间、吞吐量和响应时间

原则3:用统计方法应对性能的变化

原则4:尽早频繁测试

本章的目的,是给出一些性能测试的理论和定量方法。

第三章 Java性能调优工具箱

欲善其事,先利其器。

几个月前做了几根网线,不知道压线是否通了,每条都试一下,真是麻烦,于是买了个测线仪,压完就知道结果,方便多了。

看Java,看JVM,我们也需要这样的工具。

1、看操作系统的:CPU、磁盘、网络,用linux的操作系统命令。

2、看Java的:线程、类、堆、栈,用jstack,jcmd等命令。

3、采样、探查

4、JFR,Java飞行记录仪,收费版JVM才有的。

常用的Linux操作系统命令,以及Java性能相关的命令:jcmd、jstack、jmap、jstat、jinfo的常用参数,要纯熟。

第四章 JIT编译器

JIT编译,我们的调优参数并不多,还是要学习熟悉一下。

以前看《深入理解Java虚拟机》时,想知道机器默认是使用client/server模式,查了很多资料,未必准确,而本章就有介绍。

第五章 垃圾收集入门

本章介绍了垃圾收集的入门知识。比如选择GC算法,调整堆、永久代,控制并发等。

Stop The World

请问新生代的GC,会STW吗?老年代呢?

实际的结果是,所有的新生代GC算法,都是100%的STW,因为时间短,一般都是几毫秒~几十毫秒左右,对于大部分应用都是可以接受的。

老年代GC算法,常用的CMS,会尽量不STW,只有在经过一定失败条件,CMS无法继续进行时,会退化成使用SerialOld,才会STW,一次老年代GC,大约需要1秒,于存活对象大小成正比,如果老年代全部STW,这个1秒,真是无法承受。

我写过一个测试代码,验证过STW,如下:

/**
 * java -Xms1000m -Xmx1000m Test2
 * 可以看到,每次垃圾回收(Young GC),都是Stop the world,导致方法的执行时间 > 15毫秒,增加的时间=Young GC耗费的时间
 */
public class Test2 {
	public static void main(String[] args) throws Exception {
		
		final List<Object> os = new ArrayList<Object>();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				int i = 0;
				while(true) {
					for(int j=0;j<1000;j++)
						os.add(new java.util.concurrent.ConcurrentHashMap<String, String>());
					try {
						Thread.sleep(1);
					} catch (InterruptedException e) {
					}
					i++;
					if(i == 300) {
						os.clear();
						i = 0;
					}
				}
				
			}
		}).start();
		
		while(true) {
			
			long start = System.currentTimeMillis();
			Thread.sleep(15);
			long m = System.currentTimeMillis() - start;
			if(m>20)
				System.out.println(m);
		}
	}
}

System.gc

这行Java代码,会不会导致GC。写行代码试验一下就知道了,实践出真知。

答案是:这行代码会产生GC,回收所有的堆空间,新生代、老年代、永久代。通过 jstat -gccause -h10 pid 1000 可以清楚的看到。

这是一个测试场景,在当时的场景下是正确的。

如果在实际的生产中,其实并不一定会马上GC。

第六章 垃圾收集算法

详细讲了3类GC收集器的原理:以最大CPU利用率为目的的Throughput(parallel),最小响应延迟为目的的CMS,和CMS类似但是适用于大内存的G1。

一些高级调优选项。

在看本章的P116,“CMS收集器的永久代调优”时,从JVM角度消除了一个生产报警。场景描述如下:

有一个调用频繁的服务,一天大约调用100万次,每次都是通过反射,从Spring容器拿出一个对象(因为这个服务有状态,所以必须是prototype),经过代码跟踪,发现这种方式,在生成新对象的过程中,总会产生一个类似这样的新类: sun.reflect.GeneratedSerializationConstructorAccessor10079,大约5个小时,256m的永久代就会超过90%,监控开始报警,然后permgen到达100%时,会进行full gc,回收所有的堆(新生代、老年代、永久代)。

java-performance-definitive

java-performance-definitive

-XX:+CMSPermGenSweepingEnabled

-XX:CMSInitiatingPermOccupancyFraction=70

CMS接管Perm,70%时并发进行垃圾收集,避免满了再FullGC。在70%时,只会回收新生代和永久代,不会回收老年代。

java-performance-definitive

java-performance-definitive

第七章 堆内存最佳实践

本章首先介绍了使用jmap命令或者其他工具进行堆转储,然后通过Eclipse Memory Analyze工具分析转储文件,理解浅内存、保留内存、深内存,及EMA的用法。

然后介绍了几种常见的内存溢出场景。

然后从减少内存使用绝度(减小对象大小、延迟初始化、不可变对象和标准化对象、字符串的保留),以及对象生命周期管理(对象重用、弱引用/软引用/其他引用)绝度,讨论了堆内存最佳实践方法。

以上实际都是编程的绝度。

第八章 原生内存最佳实践

首先介绍了原生内存使用的理论知识。

在Java8中,通过开启-XX:NativeMemoryTracking=off summary detail,然后可以通过命令 jcmd pid VM.native_memory summary查看原生内存使用情况。

介绍了操作系统级别的JVM优化:大页和压缩的OOP。

页是OS管理内存的一个单元,也是OS分配内存的最小单元:要分配一个字节,OS一定会分配一个整页。

介绍了Linux大页和Linux透明大页(从2.6.32开始):cat(echo always > ) /sys/kernel/mm/transparent_hugepage/enabled

压缩的OOP(ordinary object pointer 普通对象指针),在java7和更新的版本中,只要堆小于32G,压缩的oop默认就是启用的,不用参数-XX:+UseCompressedOops,如果堆大于32G,要大于一定数值,因为需要额外的空间弥补非压缩引用所使用的空间,平均而言,对象引用会占用20%的堆空间,38G是个不错的起点。

第九章 线程于同步的性能

拿到这本书后,基于对线程知识的理解(过去看过关于线程的书,查询过很多资料,总结过博客:http://blog.csdn.net/puma_dong/article/details/37597261),所以最先看的就是这一章。

本章开始讲了线程池、ThreadPoolExecutor(根据应用场景:CPU/IO密集等,设置最大线程数、最小线程数、线程池任务大小、设置ThreadPoolExecutor大小)、ForkJoinPool、线程同步,以及JVM线程调优(调节线程栈大小、偏向锁、自旋锁、线程优先级)。

最后讲解了线程监视于分析,最常用的是jstack,我们需要数量的看懂jstack的输出结果,并能快速的分析出存在的问题。

第十章 Java EE性能调优

本章介绍了一些Java EE技术,及常见的调优,比如Servlet、Jsp、Http、EJB(过时的技术)、数据交换技术(XML、JSON)、对象序列化、数据传输(Http、WebService、RESTFull)

本章,个人觉得,其他简单看看,着重看看对象序列化就行了。

第十一章 数据库性能的最佳实践

本章所讲的理论,更像是数据库相关的理论。

在日常的应用开发中,关于数据库相关,我们往往是应用持久化框架,所以从开发绝度考虑DB性能调优,更多的是在持久化框架配置方面。

第十二章 Java SE API技巧

本章讲解了一些Java SE API技巧。

比如缓冲式I/O、类加载(双亲委派、并行加载)、随机数(伪随机、真正的随机)、Java原生接口、异常、字符串的性能、日志、集合类等。