最近两月系统的将《深入理解Java虚拟机》(周志明著 机械工业出版社 第二版)这本书看完了,虽然很多内容看的不要是很明白,但还是想写写读书笔记以总结下。
1、概述
这本书写还是挺好的,系统的介绍了虚拟机的各个方面,主要分为5个部分,走进java、自动内存管理机制、虚拟机执行子系统、程序编译与代码优化、高效并发。其中虚拟机执行子系统、程序编译与代码优化这两个部分主要讲述了类文件结构、虚拟机类加载机制、字节码执行引擎、编译期早期优化及编译期晚期优化;对于我(3年开发经验)来说这2部分有点难以理解,虽然看完,但是感觉没有理解。其他几部分算是基本理解了,理解的部分主要讲述:java内存区域与内存溢出异常、垃圾收集器与内存分配策略、虚拟机性能监控与故障处理工具、java内存模型与线程、线程安全与锁优化;以下我将我理解的这基本进行了个归纳总结,自己个做记录,也供大家参考和拍砖。
2、走进java
(1)java虚拟包括很多种,其中大家最熟悉的就是 Sun HotSpot VM了;
(2)OpenJDK是一种开源的JDK,也是sun(后来oracle)公司整的,其性能、功能、执行逻辑与官方的oracle jdK差不多,大家可以学习下;不过大家要注意正式环境还是建议大家不要随机切换jdk版本和类型,上次我将服务器的jdk从1.6升级到JDK1.8部分代码执行就报错了。
3、java内存区域与异常
对于一名初学者,可能只是简单将java管理内存区域“简单”分为堆和栈,那么对一名中高级java工程师,这就不够了。在本书中,系统介绍了以下几个区域:
序号 | 内存区域 | 介绍 | 抛出异常 |
1 | java堆 | 最大的一块,存放对象实体,可分为新生代和老年代;线程共享的数据区 | outOfMemory |
2 | java虚拟机栈 | 储存局部变量表、操作数栈、动态链接、方法出口等信息;线程隔离的数据区 | stackOverFlowError(深度) outOfMemoryError(内存大小) |
3 | 本地方法栈 | 与java虚拟机栈类似,只不过虚拟机栈为虚拟机执行java方法服务、本地栈为native方法服务; 线程隔离的数据区 | 同上 |
4 | 程序计数器 | 当前程序所执行的字节码的行号指示器。线程隔离的数据区 | |
方法区 | 已被虚拟机加载的类信息、常量、静态变量、即时编译器后的代码;线程共享数据区 | outOfMemory | |
4、垃圾收集器
(1)哪些内存需要回收
确定哪些对象还活着,主流用的方法是“可达性分析算法”,老的教科书可能说的“引用计数法”
(2)什么时候回收、如何回收
这跟用的垃圾收集算法、垃圾回收器是有很大关系的,jdk1.7update 14以后的虚拟机用的是G1垃圾回收器,主要用的基于标记-压缩算法。其他垃圾回收器包括CMS收集器、parNew收集器、Serial收集器;涉及算法主要包括标记-清除算法、复制算法、标记-整理算法。内容太多,需要的大家自己去看吧。
5、内存分配与回收策略
堆一般可分为新生代(eden)和老年代(old),回收一般分配为Minior GC和Full GC,Minior GC一般是在新生代堆中回收,Full GC在老年代回收,一般来说会触发Minior GC。Full GC 比Minior GC速度慢,频率低。随便说句,我发现稳定后的系统老年代的堆大小一般变化不大。
6、性能监控与故障处理工具
在java的bin文件夹下,有个两个很好的可视化工具,一个是Jconsole和VisualVM工具,Jconsole轻量级一些,VisualVM功能全点,一般我是用Jconsole监控有问题后,再启用VisualVM监控,如有用问题,立马快照“堆 Dump”。
7、类文件
.class文件可由java程序、JRuby程序、Groovy程序、其他语言生成,只要符合相应虚拟机规范就好了。
8、双亲委派加载机制
因为tomcat用的就是“双亲委派加载机制”,所以我特别注意了这种类加载机制,它主要工作过程:如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动累加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。
9、java内存
java内存主要分为工作内存与主内存,工作内存可简单看成与“线程”一一对应,主内存存放着共享的数据。
10、线程安全的实现
笔者将这本书的线程安全这2章简单理解成一段话,供大家理解,深入学习还请自己照书系统学习。
线程安全实现可以由几下几种方法:
(1)synchronized
这种方法,大家都很熟悉,就是lock上某个变量、方法、程序段,在某个时刻,只允许一个线程访问lock上的数据。这个方法大家都很常用,但是笔者认为最大的两个弊端,一个弊端是效率低(据说jdk1.6以后有很大的改善),另一个弊端是等待的同优先线程获得解锁后的资源是随机的,并不按等待的先后顺序先后获得资源。
(2)java.util.concurrent包中的重入锁(ReentrantLock)
对比synchronized,它有3个高级功能,一个是等待都可中断、二个是可实现公平锁、三个是锁可绑定多个条件。其中可实现公平锁是我最看重的,因为在并发量大的项目时,它比较合理。
(3)volatile
volatile使用方法和synchronized差不多,主要是针对数据,它使用后有两个特性,一个是变量(数据)对所有线程都可见(包括更新后的变量(数据),但是在某些条件下不能及时能信); 二个是禁止指令重新优化排序。这个方法建议只有当大家深入理解后,才使用此方法。