共计 2308 个字符,预计需要花费 6 分钟才能阅读完成。
抽时间重新读了一遍《深入理解 JVM》一书。以下为摘录内容。
1 java 内存区域
java 虚拟机运行时数据区
1.1 程序计数器
是一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。每条线程都有一个独立的程序计数器,各条线程之间计数器互不影响。
1.2 java 虚拟机栈
描述的是 java 方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
局部变量表存放了编译器可知的各种基本数据类型、对象引用和 returnAddress 类型。
1.3 本地方法栈
虚拟机栈为虚拟机执行 java 方法服务,二本地方法栈为虚拟机使用到的 Native 方法服务。
1.4 java 堆
被所有线程共享的一块内存区域,在虚拟机启动时创建。java 堆是垃圾收集器管理的主要区域,因此很多时候也被叫做 GC 堆。
1.5 方法区
各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
1.6 运行时常量池
方法区的一部分。需要注意的是 string 的 intern 方法在 jdk1.6 前后的不同。jdk1.6 之后常量池放到了堆中。
1.7 直接内存
并不是虚拟机运行时数据区的一部分,也不是 java 虚拟机规范中国定义的内存区域。NIO 引入的通道和缓冲区可以使用 native 函数库直接分配对外内存。
2 垃圾收集器与内存分配策略
2.1 判断对象是否存活的算法:
引用计数算法:很难解决对象之间相互循环引用的问题
可达性分析算法:通过一系列 GC Roots 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路线称为引用链,当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的。
2.2 垃圾收集算法
标记 - 清除算法:效率低,空间碎片化
复制算法:运行简单高效,代价高,降低了一半的使用率
标记 - 整理算法
分代收集:新生代用复制算法,老年代用标记整理算法
3 虚拟机类加载机制
加载、验证、准备、解析、初始化。
3.1 有且只有 5 种情况必须立即对类进行初始化
1)遇到 new、getstatic、putstatic 或 invokestatic 这 4 条指令字节码时,如果类没有进行过初始化,则需要先触发其初始化。
2)使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
3)当初始化一个类的时候,如果发现其父类还没有进行过初始化,需要先触发其父类的初始化。
4)当虚拟机启动时,需要制定 main,虚拟机会先初始化 main 类。
5)当使用 jdk1.7 的动态语言支持时,如果 java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
3.2 类加载的过程
3.2.1 加载
1)通过一个类的全限定名来获取定义此类的二进制字节流
2)将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构
3)在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。
3.2.2 验证
确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
文件格式验证——元数据验证——字节码验证——符号引用验证
3.2.3 准备
正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这时候进行内存分配的仅包括类变量(被 static 修饰的变量),二不包括实例变量。
3.2.4 解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
类和接口的解析、字段解析、类方法解析、接口方法解析
3.2.5 初始化
类初始化阶段是类加载过程的最后一步。在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序员通过程序指定的主观计划去初始化类变量和其他资源,或者可以从另一个角度来表达:初始化阶段是执行类构造器 <clinit>() 方法的过程。
4 java 内存模型
硬件的效率与一致性
java 内存模型(JMM)
线程、主内存、工作内存之间的交互关系 java 内存模型规定了所有的变量都存储在主内存中,每条线程有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。线程间变量值的传递均需要通过主内存来完成。
java 内存模型时围绕着在并发过程中如何处理原子性、可见性和有序性这三个特征来建立的。
原子性:通过 read、load、assign、user、store、write 操作来保证。通过 lock 和 unlock 也可以满足。
可见性:可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。Java 内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论普通变量还是 volatile 变量都是如此,普通变量与 volatile 变量的区别是,volatile 的特殊规则保证了新值能够立即同步到主内存,以及每次使用前立即从主内存刷新。volatile 保证了多线程操作时变量的可见性,二普通变量不能保证这一点。(synchronized 和 final 关键字)
有序性:volatile 和 synchronized 保证线程之间操作的有序性,volatile 本身就包含了禁止指令重排序的语义。
先行发生原则 保证了我们大多数情况下不用关心太多。