《深入理解Java虚拟机》笔记

第二章 Java内存区域域内存溢出异常
2.2运行时数据区域
1、程序计数器 P39。当前线程所执行的字节码的行号指示器。是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域;
2、java虚拟机栈 P39。java虚拟机栈描述的是java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程;
局部变量表中存放了编译期可知的各种基本类型数据、对象引用。
在java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError;如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,将抛出OutOfMemoryError异常;
3、本地方法栈 P40。与虚拟机栈相似,为虚拟机使用到的Native方法服务;
4、java堆 P41。java堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样;
如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常;
5、方法区 P41。是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
6、运行时常量池 P42。是方法区的一部分。具备动态性。String类的intern方法。用于存放编译期生成的各种字面量和符号引用。
7、直接内存 P43。

HotSpot虚拟机:
1、对象的创建:P44
1)指针碰撞:java堆规整;在使用Serial、ParNew等带Conpact过程的收集器时,系统采用此分配算法;
2)空闲列表:java对不规整;使用CMS这种基于Mark-Sweep(标记-清除)算法的收集器时,通常采用空闲列表;
保证分配对象空间线程安全方法:
1)、对分配内存空间的操作进行同步处理:实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;
2)、把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,LTAB),虚拟机是否使用LTAB,可通过-XX:+/-UseLTAB参数参数来设定;
内存分配完后,虚拟机需要将分配到的内存空间都初始化为零值。
2、对象的内存布局:P47 分三块
1)对象头(Header)P47:第一部分用于存储对象自身的运行时数据;另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
2)示例数据 (Instance Data)P48:对象真正存储的有效信息,也是在程序代码中定义的各种类型的字段内容。
3)对齐填充(padding)P48:仅起占位符作用;
3、主流的对象访问方式:使用句柄、直接指针 P48;

OutOfMemoryError异常:
1、Java堆溢出 P51:Java heap space;
2、虚拟机栈和本地方法栈溢出 P53:StackOverflowError、OutOfMemoryError;
3、方法区和运行时常量池溢出 P56:OutOfMemoryError:PerGen space;
4、本机直接内存溢出 P59:在heap Dump文件中不会看到明显的异常(OOM之后Dump文件很小,而程序中又直接或间接使用了NIO);

第三章 垃圾收集器与内存分配策略
判断对象是否存活:
1、引用计数器 P62:
2、可达性分析算法 P64:基本思想是通过一系列的被称为“GC Roots”的对象为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
Java中可作为GC Roots的对象包括:
1)虚拟机栈(栈帧中的本地变量表)中引用的对象;
2)方法区中静态属性引用的对象;
3)方法区中常量引用的对象
4)本地方法栈中JNI(native方法)引用的对象;

引用 P65:强引用、软引用、弱引用、虚引用。

回收方法区:
1、永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类;

垃圾收集算法:
1、标记-清除算法 P69:分为“标记”和“清除”两个阶段。
不足:
1)效率不高;
2)空间问题,会产生大量不连续的内存碎片;
2、复制算法 P70:将可用内存按容量分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后把已使用过的内存一次清理掉。
将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活的对象一次性的复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。需要额外的空间进行分配担保。
3、标记-整理算法 P71:标记完后让所有存活的对象都向一段移动,然后直接清理掉端边界以外的内存。
4、分代收集算法 P72:根据对象存活周期的不同将内存划分为几块。一般是把java堆分为新生代和老年代,根据各个年代的特点采用不同的算法。新生代用复制算法,老年代用“标记-清除”或“标记-整理”算法。


HotSpot算法的实现
1、标准式GC P72;
2、OopMap P72:存储栈和寄存器中哪些位置是引用;
3、安全点(Safepoint) P73:程序执行时并非在所有地方都能停顿下来开始GC,只有在到达安全点时才能暂停;
抢先式中断 P74:先全部中断,再让中断地方不在安全点上的线程恢复“跑到”安全点;
主动式中断 P74:设置标志,各线程主动轮询标志,中断标志为真时线程自己中断挂起;
4、安全区域 P74:指在一段代码片段中,引用关系不会发生变化;

垃圾收集器:
新生代收集器:
1、Serial收集器 P76:采用复制算法,进行垃圾收集时,必须暂停其他工作线程;
2、parNew收集器 P77:采用复制算法,Serial收集器的多线程版本;
3、Parallel Scavenge收集器 P79:使用复制算法,并行多线程。目标是达到一个可控制的吞吐量。
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间);

老年代收集器:
1、Serial Old收集器 P80:采用标记-整理算法,Serial收集器的老年代版本,单线程;
2、Parallel-Old收集器 P80:采用标记-整理算法,Parallel Scavenge收集器的老年代版本,多线程,JDK 1.6开始使用;
3、CMS收集器(Concurrent Mark Sweep) P81:以获取最短回收停顿时间为目标,采用标记-清除算法。
1)初始标记:只标记GC Roots能直接关联到的对象;
2)并发标记:进行GC Roots Tracing的过程;
3)重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录;
4)并发清除;
1)、3)需要停止其他运行线程。
i-CMS(Incremental Concurrent Mark Sweep)增量式并发收集器。
4、G1收集器(Garbage-First) P84:从JDK6u14开始。面向服务端应用。
将整个java堆划分为多个大小相等的独立区域,
特点:
1)并行与并发:
2)分代收集:
3)空间整合:
4)可预测的停顿:
运作步骤:
1)初始标记;
2)并发标记;
3)最终标记:
4)筛选删除:

内存分配:
1、对象优先在Eden分配 P91:
1)新生代GC(Minor GC):发生在新生代的垃圾收集动作,频繁,回收速度快;
2)老年代GC(Major GC/Full GC):发生在老年代的GC,发生Major GC,经常会伴随至少一次的Minor GC(但并非绝对),速度比Minor GC慢10被以上;
2、大对象直接进入老年代 P93:
3、长期存活的对象将进入老年代 P95:
4、动态对象年龄判定 P97:
5、空间分配担保 P98:

JDK命令行工具:
1、jps (JVM Process Status Tool)P104:虚拟机进程状况工具。可以列出正在运行的虚拟机进程,并显示虚拟机执行主类名称及这些进程的本地虚拟机唯一ID;
jps [options] [hostid]
2、jstat (JVM Statistics Monitoring Tool)P105:虚拟机统计信息监视工具。用于监视虚拟机各种运行状态信息;
jstat [option vmid [interval [s | ms] [count] ] ]
3、jinfo(Configuration Info for java)P106:java 配置信息工具。实时地查看和调整虚拟机各项参数;
4、jmap(Memory Map of Java)P107:Java内存映像工具。用于生成堆存储快照(一般称为heapdump或dump文件),还可以查询finalize执行队列、Java堆和永久代的详细信息,如空间实用率、当前用的是那种收集器等;
5、jhat(JVM Heap Analysis Tool)P108:虚拟机堆转储快照分析工具。分析jmap生成的堆转储快快照;
6、jstack(Stack Trace for Java)P108:Java堆栈跟踪工具。用于生成虚拟机当前时刻的线程快照(一般称为threaddump 或者javacore文件);
7、HSDIS P111:JIT生成代码反编译。

JDK的可视化工具
1、JConsole(Java Monitoring and Managment Console) P115:java监视与管理控制台。是一种基于可视化监视、管理工具。
1)内存监控 P116:相当于可视化的jstat命令。用于监视受收集器管理的虚拟机内存(java堆和永久代)的变化趋势;
2)线程监控 P118:相当于可视化的jstack命令。遇到线程停顿时可以使用这个页签进行监控分析;
2、VisualVM P122:多合一故障处理工具。

第7章 虚拟机类加载机制
类加载的时机
1、类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载。其中,验证、准备和解析统称为连接。
1)加载:
a.通过一个类的全限定名来获取定义此类的二进制字节流;
b.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
c.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口;
2)验证:目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害到虚拟机自身的安全;
a.文件格式验证:第一阶段验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理;该验证阶段的主要目的是保证输入的字节流能正确的解析并存储于方法区之内,格式上符合描述一个Java类型信息的要求;
b.元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求;
c.字节码验证:主要目的是通过对数据流和控制流分析,确定程序语义是合法的、符合逻辑的;
d.最后一阶段的验证发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段发生--解析阶段中发生;目的是确保解析动作能正常执行;
3)准备:准备阶段是正式为变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。初始值通常情况下是变量的零值。(如public static int value = 123,在准备阶段后值为0);
4)解析:解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程;
5)初始化:初始化时类加载过程的最后一步。前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行Java程序代码(或者说是字节码);
2、加载、验证、准备、初始化和卸载的顺序是确定的,类的家在过程必须按照这种顺序按部就班的开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后在开始,这是为了支持java语言的运行时绑定。
3、类加载器:虚拟机设计团队把类加载阶段中的“通过一个类的全限定类名来获取描述此类的二进制字节流”这个动作放在虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块被称为“类加载器”;
类加载器类型:
从虚拟机角度:
a.启动类加载器:使用C++语言实现,是虚拟机的一部分;
b.所有其他的类加载器:由Java语言实现,独立于虚拟机外部,全部都继承自抽象类java.lang.ClassLoader;
从开发人员角度:
a.启动类加载器:负责将存放在<JAVA_HOME>/lib目录中的,或者被-Xbootclasspath参数所指定的的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中;
b.扩展类加载器:由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>/lib/ext目录中的,或者被java.ext.dirs系统变量所指定得路径中的所有类库,开发者可以直接使用扩展类加载器;
c.应用程序类加载器:由sun.misc.Launcher$AppClassLoader实现,由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器;
1)双亲委派模型:要求除了顶层的启动类加载器之外,其余的类加载器都应当有自己父类加载器;
2)破坏双亲委派模型:

posted @ 2019-07-23 19:54  lym414  阅读(162)  评论(0)    收藏  举报