《深入理解Java虚拟机》
2 Java内存区域与内存溢出异常
2.2 运行时数据区域
JVM运行时数据区.png2.2.1 程序计数器
可以看做当前线程所执行的字节码的行号指示器。
线程私有。
2.2.2 Java虚拟机栈
描述的是Java方法执行的内存模型:每个方法在执行的同时会创建一个栈帧(Stack Frame)用于存储局部变量、操作数栈、动态链接、方法出口等信息。
局部变量表存放了编译器克制的各种基本数据类型(bolean, byte, char, short, int, float, long, double)、对象引用(reference类型)
线程私有,生命周期与线程相同。
2.2.3 本地方法栈
与Java虚拟机栈作用类似,区别是本地方发展为Native方法服务。
2.2.4 Java 堆
用于存放对象实例,几乎所有对象实例都这这里分配内存。
是Java虚拟机所管理的内存中最大的一块。
线程共享。
2.2.5 方法区
用于存储已被虚拟机加载的的类信息、常量、【静态变量】、及时编译器编译后的代码等数据。
线程共享。
3 垃圾收集器与内存分配策略
3.2 对象已死吗
3.2.1 引用计数算法
算法描述:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就+1;当引用失效时,计数器值就-1;任何时刻计数器为0的对象就是不可能被使用的。
实现简单,判定效率高,但是很难解决对象之间相互循环引用的问题。
3.2.2 可达性算法
可达性算法.png基本思路:通过一系列成为
GC Roots
的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径成为引用链
,当一个对象到GC Roots没有任何引用链时,则证明此对象是不可用的。如上图obj5-7。
3.2.3 四种引用类型
- 强引用
Object o = new Object
只要强引用还存在,垃圾回收期永远不会回收掉被引用的对象。 - 软引用
用SoftReference类来实现。将要发生内存溢出之前,会将这些对象列进回收范围之中。 - 弱引用
WeakReference
被弱引用的对象只能生存到下一次垃圾回收发生之前。
当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。 - 虚引用
是最弱的引用关系。一个对象是否有虚引用存在,完全不会对其生存构成影响,也无法通过虚引用来取得一个对象的实例。
3.3 垃圾搜集算法
3.3.1 标记-清除算法
标记-清除算法.png首先标记出所有要回收的对象,在标记完成后统一回收被标记的对象。
不足:标记和清除两个过程效率都不高;标记清除之后会产生大量的不连续的内存碎片。
3.3.2 复制算法
复制算法.png将内存按容量分为大小相等的两块,每次只是用其中一块,当一个内存用完了,就将还存活的对象复制到另一个块上面,然后再把已使用过的内存空间一次清理掉。
运行简单高效,没有内存碎片。代价是可用内存缩小为了原来的一半。
3.3.3 标记整理算法
image.png先标记要被清除的对象,然后让所有存过的对象都向一段移动,然后直接清理掉边界以外的内存。
3.3.4 分代收集算法
新生代用复制算法
老年代用标记清理或标记整理算法
7 虚拟机类加载机制
7.1 概述
虚拟机类加载机制 是指 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java 类型。
7.2 类加载的时机
- 遇到 new, getstatic, putstatic, invokestatic时,如果类没有被初始化,则先触发其初始化。
- 使用java.lantg.reflect包的方法对类进行反射调用的时候,如果类没有被初始化,则先触发其初始化。
- 初始化一个类时,如果其父类还没有进行过初始化, 则先出发其父类的初始化。
- 当去你急启东市,用户需要制定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
- 当使用JDK 1.7 的动态语言支持是,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic, REF_putStatic, REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先出发其初始化。
- 对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会出发父类的初始化而不会出发子类的初始化。
- 通过数组定义类引用类,不会出发此类的初始化。
- 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
7.4 类的加载
7.4.1 类与类加载器
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。通俗点讲:比较两个类是否相等,只有在这两个类是由容一个类加载器加载的前提下才有意义,否则,即使两个类来源于同一个Class文件,如果不是同一个加载器加载,它们也不相等。
7.4.2 双亲委派模型
类加载器:
- 启动类加载器(Bootstrap ClassLoader), C++实现,是虚拟机的一部分,无法被Java程序直接引用。负责加载<JAVA_HOME>\lib, -Xbootclasspath指定路径 中的类库。
- 扩展类加载器(Extension ClassLoader),Java实现,负责加载<JAVA_HOME>\lib\ext,java.ext.dirs指定的类库。开发者可以直接调用。
-
应用程序类加载器(Application ClassLoader),Java实现,负责加载用户类路径(ClassPath)上所指定的类库,是程序默认的类加载器。可发者可以直接使用。
类加载器双亲委派模型
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先把这个请求委派给父类加载器去完成,依次类推,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需要的类)时,子加载器才会尝试自己去加载。
12 Java内存模型与线程
12.3 Java 内存模型
12.3.1 主内存与工作内存
线程、主内存、工作内存三者的交互关系Java每条线程都有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝。
线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
12.3.3 对volatile型变量的特殊规则
当一个变量定义为volatile之后,它将具备两种特性:
- 保证此变量对所有线程的可见性,当一条线程修改了这个变量的值,新值对于其它线程来说是立即得知的。volatile保证了新值能立即同步到主内存,以及每次用之前立即从主内存刷新。
- 尽职指令重排序优化。