得空把这本书卡,的确是一本好书,可惜我的实践比较少,有很多优化的方法最好还说自己实践下,来的理解透彻。
第二章,内存区域与内存溢出异常。
-
程序计数器:每个线程轮流由处理器处理,因为每个线程有个独立的线程计数器,作为记号。很小的内存空间,相当于记录线程执行到哪一行了。
-
虚拟机栈,线程私有,相当于局部变量表部分,存储基本类型。栈深度超了,会stackoverflowerror,如果扩展内存超了,申请不到,会outofmemoryerror。
-
本地方法栈与虚拟机栈类似,区别是后者为虚拟机执行java方法服务,前者为用到的native方法服务,有的虚拟机两者合二为一。
-
java堆,内存中最大的一块,所有线程共享,主要存储对象实例,是gc回收的主要目标,有xmx的设置,有outofmemory的异常。
-
方法区,同样线程共享,加载类信息,常量,静态变量等等。在hotspot虚拟机上又叫永久代,作为堆的一部分管理,受xmx的限制。注意除了上面的还有个直接内存容易outofmemory。
-
outofmemoryerror异常。java堆溢出,一般就是heap space修改xmx设置即可。如果是栈抛错,那么一般的设置时候的,除非多线程导致内存溢出,在不能减少线程数或者更换64位虚拟机的情况下,只能减少最大堆和减少栈容量解决,或者减少内存,这种比较难想到。其他的暂不考虑。
第三章,垃圾收集器与内存分配策略
-
一般来说,程序计数器,虚拟机栈,本地方法栈,这三个区域分配和回收都有确定性,至少编译期可知,不需要过多考虑回收的问题。而java堆和方法区一般运行期才知道创建哪些对象,一般指这些内存的分配和回收。
-
引用计数法,引用一个加一个,失效减一个,很难解决对象间相互循环引用的问题。可达性分析法,是否根对象可达,否则回收。当然也不是必死无疑,有一些举措给一些余地。方法区,就是永久代,垃圾收集效率低,而堆,尤其是新生代,一次可回收70-95的空间。
-
垃圾收集算法。一,先标记再清除。效率问题和空间问题,就是大量不连续碎片。二,复制算法,解决效率问题。腾出一半的内存来存放上次清理剩下的内存,一半可以改成10%。三,标记-整理算法。适合老年代,大部分对象要保留,存活率高,标记后,所有对象向一端移动,清理边界外的内存。四,分代收集,新生代用复制算法,老年代其他两种。
-
内存分配和回收策略。新生代优先放在esen区,大对象直接进入老年代,可以设置,长期存活将进入老年代,活几代也是设置的。当然也不纯粹是按照年龄,也有动态:survivor里面相同年龄大小大于一半,她们可以进入老年代。如果老年代不足以容纳新生代,那么有策略看是否进行老年代清理。
第四章,虚拟机性能监控与故障处理工具
第五章,调优案例分析与实战
-
一,堆外内存导致的溢出错误。有些用到直接内存,但是他分到的空间很小,即使满了也不一定抛出异常,只能等老年代清理顺便帮忙清理,谨慎使用。二,程序里面调用外部的shell脚本或者命令会导致cpu使用率高,最好使用api获取这些信息。三,关联系统无返回,导致系统持续请求中,要求关联方用生产者,消费者模式的消息队列实现异步。四,用map存储数据文件空间太浪费内存,利用率只有18%。
-
上面的多是服务端的调优。Java可以怎么做到调优呢,如下:
-
Jdk升级到1.6反而会内存溢出,为啥呢,因为变成oracle的了,eclipse不认识了,所以不认识永久代的参数,需要自行设置maxpermsize为256m,原来的64m不够用。另外堆是会自行扩展容量的,并不是一开始就是最大值,而是慢慢根据情况扩展的,扩展的同时会触发一次垃圾收集,使用中的堆变小。
-
升级到1.6如果取消字节玛验证,eclipse启动速度变快,1.5反而变慢了。1.6的类加载时间多了一倍,后续讲解。三大非用户程序时间,类加载,编译时间,还有垃圾回收时间。gc最重要,持续在程序的每个运行环节,每分每秒都可能用到。
-
这一点讲下垃圾回收。如果新生代gc过于频繁,是由于虚拟机分配的新生代空间太小,xmn调整。如果full时间过长,查看因为eclipse启动时老年代空间耗尽扩容,也会full gc,永久代也有一部分,为了避免内存扩展(会导致无用的full gc),因此强制老年代和永久代容量定下来,把xms和xx:permsize设置成最大值xmx和xx:maxpermsize。
第12章,java内存模型与线程
-
线程共享主内存,但是使用的是自己的工作内存,是一个主内存的拷贝。
-
Java的原子性,可见性,有序性来控制并发,而synchronized同时满足这三个,所以看起来是万能的。
-
先行发生原则和时间先后没有啥关系,分析问题的时候不用考虑时间的先后。
第13章,线程安全和锁优化
-
方法一同步锁,syn那个,属于阻塞同步,线程阻塞和唤醒有性能问题。
-
方法二,非阻塞同步,就是乐观锁,先进行操作,如果有冲突,再采取补偿措施。比如举例那个,20个线程每个自增10000次,如果用a++可能中途就有冲突,比如两个线程都从10加到了11,导致最后少了1。使用非阻塞同步的方法,先加一,发现当前值和加一后的值不同,代表加一失败,重新处理。
-
三,无同步优化,包括可重入代码和线程本地存储。
-
虚拟机进行的锁优化。
-
一,自旋锁和自适应自旋。就是因为阻塞是挂起和恢复线程,需要进入内核态处理,影响并发。所以如果持有锁时间比较短,就可以让后面的线程持续等待,比如默认十次,重新获取,当然这个自旋占用处理器时间,所以对持有锁很快释放的比较划算,后续也有自适应,如果从来没有被获得过的锁,就默认不做自旋了。
-
二,锁消除,编译阶段,对一些不太可能有数据竞争的锁消除掉。
-
三,锁粗化。对一段代码频繁对一个锁加锁解锁,就把同步的范围扩大,只需要一次就行了。
-
轻量级锁。默认给一个轻量级的,如果有两条以上线程竞争的话,升级为重量级的,并且后续的线程会阻塞,减少不必要的阻塞。但是如果有竞争,增加了cas的操作,会比传统的重量级的慢。
-
偏向锁。就是偏向第一个获取的线程,提高了有同步但是没有竞争的程序的性能。