一.前言
看书记一下关键信息,怕自己忘了,断断续续看了一两遍。
二.正文
第二部分 自动内存管理机制
- 在Java虚拟机规范的描述中,除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OOM的可能。(p50)
- HotSpot虚拟机中,对象在内存中的存储分为三部分:对象头Header,实例数据Instance Data和对齐填充Padding。对象头又分为两部分:第一部分存储自身运行时数据包括HashCode,GC分代年龄,锁状态等,这部分叫MarkWord;第二部分是类型指针,虚拟机通过这个指针确定这个对象是哪个类的实例。(p47)
- 关于OOM,堆溢出的情况:不断地创造对象,且保证Gc roots 到这些对象可达,这些对象不会被清除,当对象数量达到堆最大容量限制就会出现OOM。这种情况最常见。(p51)
- HotSpot虚拟机不区分虚拟机栈和本地方法栈,栈容量仅由-Xss参数设置。(p53)
- 虚拟机栈中,有两种异常:StackOverFlowError:线程请求的栈深度大于虚拟机所允许的最大深度时抛出;OOM:虚拟机栈在扩展过程中无法申请到足够的内存空间。
- 方法区和运行时常量池溢出:jdk1.7之前,常量池被分配在永久代(方法区),后来jdk1.7之后去掉了永久代的概念(永久代就是一个概念,hotSpot JVM专属,方法区是它的实现)。
第三部分 垃圾收集器与内存分配策略
- 即便gc-roots标记为不可达,对象也不是必须死的。还需要两个标记过程,第一次标记:对象在可达性分析中被发现没有引用,此时被标记且进行一次筛选,条件是**“这个对象有没有必要执行finalize()方法”**,(如果对象没有覆写finalize方法或者finalize()方法已经被调用过了,那么被视为没必要执行)。如果没必要执行,那么这个对象就死了会被回收
如果这个对象被判定为有必要执行finalize()方法,对象会被放在一个F-queue中,等待虚拟机创建一个Finalizer线程去执行它。会触发,但为了防止finalize()方法执行太慢,不会等待它执行结束。
finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-queue中的对象进行第二次小规模标记,如果对象想要拯救自己----------就需要重新与gc-roots引用链的对象建立关联,如果此时对象还没有建立连接,那么这个对象就会被回收。
- 方法区回收的效率很低(java堆,如果是在新生代回收,一次能回收掉70%-95%的空间),一般回收两种东西,第一种是没人用的常量,第二种是废弃的class。没人用的常量就类似于一个字符串,这个字符串没有任何引用,则被视为是废弃的,其他类、方法也是一样。
判定一个类是废弃的class条件很苛刻,需要同时满足下面的三个条件:
- 该类所有的实例都被回收,也就是Java堆中没有这个类的实例
- 加载这个类的ClassLoader被回收
- 这个类的Class对象没人使用,也无法通过反射获取这个类的方法
- 可达性分析必须在能确保一致性的快照中进行。也就是说可达性分析需要stop-the-world
- 几乎目前所有的主流Java虚拟机用的都是准确性GC,就是说虚拟机有办法知道哪些内存地址存着对象引用。在HotSpot虚拟机中,通过OopMap这个数据结构来达到目的。类加载时,HotSpot会计算对象偏移量和数据类型;在JIT编译中,会记录下栈和寄存器中哪些位置是引用,这样GC扫瞄的时候就能直接知道了。
- 只有在特定位置才会记录这些信息,这些位置叫做safePoint 安全点。
- CMS收集器的三个缺点:1.CMS收集器对CPU资源非常敏感,会占用CPU资源 2.CMS收集器无法处理浮动垃圾。3.CMS基于标记-清除算法实现,会产生空间碎片,有时候大的对象找不到连续的空间分配就只能开启full gc
浮动垃圾:CMS并发清理阶段,用户线程还在运行,CMS在当此处理中无法清除这部分垃圾,只好下一次GC再清理,这部分垃圾就称为浮动垃圾
- Java技术体系的内存管理最终就做了两件事:内存的分配和内存的回收,下面是普遍的内存分配原则
1.对象优先在Eden区分配,如果Eden区没有空间,则触发minorGC,一般来说可用的空间是Eden+一个survivor区。
2。
- 空间分配担保机制:Minor GC之前,虚拟机检查老年代最大可用连续空间是否大于新生代所有对象空间。如果没问题,那minor GC必然是安全的。否则,虚拟机会看HandlePromotionFailure设置是否允许担保失败:如果允许,则检查历史记录中老年代晋升对象平均大小,如果老年代空间足够,那么会进行minorGC,但有风险。其他情况则不会进行minor GC,会进行FULL GC
第四部分 性能监控与故障处理工具
p103
jps jstat jinfo等
第五部分 调优案例 实战
- 控制FUll GC的关键是看应用中绝大多数对象是否符合“朝生夕灭”的原则,不能有长时间存在的大对象,这样老年代空间才能稳定
- 除了java堆和永久代(方法区),下面的区域也会占用较多的内存:
- Direct Memory,内存不足会抛出OOM:Direct Buffer memory
- 线程堆栈,可通过-xss调整大小
- Socket缓存区,每个 socket连接都有Receive和Send两个缓存区,占37kb和25kb。如果空间不够可能抛出IOException
- JNI代码:如果应用使用了本地库,那么本地库所占的内存也不在堆中
- 虚拟机GC:虚拟机本身和GC代码执行也需要占内存
第六部分 类文件结构
- 常量池中主要存放两大类型常量:字面量,和符号引用
- 字面量接近于java语言的常量概念,如字符串,声明为final的常量值等
- 符号引用接近于编译原理的概念:主要包括三种
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
- 常量池表:p172
- 常量池结束后的两个字节表示访问标识:acess_flags,这个标志用于识别一些类或者接口层次的访问信息(比如这个class是类还是接口),是否public,是否abstract
- 类索引、父类索引、接口索引集合,跟在acess_flags之后,类索引、父类索引表示类的全限定名,接口索引表示这个类实现了哪些接口
- 字段表集合,描述接口或者类声明的变量,参考p176,有public,private这些
第七部分 类加载机制
- 类从被加载到虚拟机内存中开始,到被卸载出内存为止。
- 类的生命周期包括:加载、验证、准备、解析、初始化、使用、卸载
- 解析过程可以在初始化阶段后进行,因为Java语言可以运行时绑定
- 初始化的时机有五条规则:
1.遇到new getstatic 等字节码指令时
2.用java.long.reflect反射调用时
3.父类没初始化,先初始化其父类
4.虚拟机启动时,先初始化main()方法的类,这个叫主类
5.jdk1.7特性用了methodHandle,解析结果的句柄对应的类要初始化
- 虚拟机设计团队把类加载阶段中“通过一个类的全限定名来获取此类的二进制字节流”这个动作放到虚拟机外部实现,以便让程序自己决定如何去获取需要的类。实现这个动作的模块叫做类加载器。
- 只有两种类加载器:一种是启动类加载器,Boostrap Classloader,这个是用C++实现的,是虚拟机本身一部分。其他所有的都是独立于虚拟机的。
- 双亲委派机制有三次被破坏的情况
- jdk1.2发布之前,没有双亲委派机制,所以需要向之前版本兼容
- 基础类又调用回去用户的代码,比如JDBC,不同厂商有自己的底层实现
- 热部署、热替换的需求,类加载必须改为一个网状结构而不是树状结构
- 热部署相关的概念OSGi,JSR-291
第八部分 虚拟机字节码执行引擎
- Java的分派
Human man = new Man()
其中,Hunman叫做变量的静态类型/外观类型,Man称为变量的实际类型,静态类型不会改变,且编译器可知,但是只有在运行期才能确定实际类型的变化。
//静态变化 (Human)man,//动态变化 Human man = new Man(); man = new Women()
虚拟机重载时通过参数的静态类型作为判定的依据。在编译阶段,Java编译器根据参数的静态类型决定使用哪个重载版本
这就叫做静态分派,典型的应用场景是方法重载。
2. 动态分派,和重写Override关系密切。其原理是,虚拟机执行指令invokevirtual时,会确定接收者的实际类型,这个指令把常量池中的类方法符号引用解析到了直接引用上。这种运行时期根据实际类型确定方法执行版本的过程就是动态分派。
3. 动态分派、静态分派都可以认为是多态的体现(作者观点)
4. Java语言是静态多分派,动态单分派的语言
5. 动态分派的实现:因为动态分派非常频繁,所以最常见的手段就是建立一个虚方法表,,使用它的索引来查找,提高性能
6. 动态类型语言:类型检查的主体过程是在运行期而不是编译期,在编译期就检查类型的Java、C++都是静态类型语言
7.
第九部分 类加载和执行子系统
- OSGi,osgi有着灵活的类加载器结构,每个模块(Bundle)之间只有规则,没有固定的委派关系。不涉及某个具体的Package时,每个模块都是平级关系,只有具体使用某个class或者Package时,才根据Package的导入和导出来构造Bundle之间的委派和依赖
- OSGi适合单个虚拟机下的应用,有线程死锁和内存泄漏的风险
第十部分 编译期优化
- 编译期类型:
前端编译期:javac等
JIT编译器:HotSpot的 c1 c2编译器
AOT编译器:GNU,Excelsior JET
- 泛型技术:为了减少classCastException,因为之前都是用Object对象,理论上可以转成任何类型,但是编译期间,编译期无法检查转型是否成功,所以会出异常
- 泛型技术实际上是一种语法糖:运行期间,List 和List 没有任何区别
- 在class文件格式中,只要描述符没有完全一致,就可以共存,所以两个除了返回值都一样的方法同时存在一个class是可以的