美文网首页应用开发与调优
《深入理解Java虚拟机》

《深入理解Java虚拟机》

作者: 詹徐照 | 来源:发表于2018-08-05 11:58 被阅读34次

    2 Java内存区域与内存溢出异常

    2.2 运行时数据区域

    JVM运行时数据区.png

    2.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没有任何引用链时,则证明此对象是不可用的。如上图obj
    5-7。

    3.2.3 四种引用类型

    1. 强引用
      Object o = new Object
      只要强引用还存在,垃圾回收期永远不会回收掉被引用的对象。
    2. 软引用
      用SoftReference类来实现。将要发生内存溢出之前,会将这些对象列进回收范围之中。
    3. 弱引用
      WeakReference
      被弱引用的对象只能生存到下一次垃圾回收发生之前。
      当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
    4. 虚引用
      是最弱的引用关系。一个对象是否有虚引用存在,完全不会对其生存构成影响,也无法通过虚引用来取得一个对象的实例。

    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 类加载的时机

    1. 遇到 new, getstatic, putstatic, invokestatic时,如果类没有被初始化,则先触发其初始化。
    2. 使用java.lantg.reflect包的方法对类进行反射调用的时候,如果类没有被初始化,则先触发其初始化。
    3. 初始化一个类时,如果其父类还没有进行过初始化, 则先出发其父类的初始化。
    4. 当去你急启东市,用户需要制定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
    5. 当使用JDK 1.7 的动态语言支持是,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic, REF_putStatic, REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先出发其初始化。
    • 对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会出发父类的初始化而不会出发子类的初始化。
    • 通过数组定义类引用类,不会出发此类的初始化。
    • 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。

    7.4 类的加载

    7.4.1 类与类加载器

    对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。通俗点讲:比较两个类是否相等,只有在这两个类是由容一个类加载器加载的前提下才有意义,否则,即使两个类来源于同一个Class文件,如果不是同一个加载器加载,它们也不相等。

    7.4.2 双亲委派模型

    类加载器:

    1. 启动类加载器(Bootstrap ClassLoader), C++实现,是虚拟机的一部分,无法被Java程序直接引用。负责加载<JAVA_HOME>\lib, -Xbootclasspath指定路径 中的类库。
    2. 扩展类加载器(Extension ClassLoader),Java实现,负责加载<JAVA_HOME>\lib\ext,java.ext.dirs指定的类库。开发者可以直接调用。
    3. 应用程序类加载器(Application ClassLoader),Java实现,负责加载用户类路径(ClassPath)上所指定的类库,是程序默认的类加载器。可发者可以直接使用。


      类加载器双亲委派模型

      双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先把这个请求委派给父类加载器去完成,依次类推,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需要的类)时,子加载器才会尝试自己去加载。

    12 Java内存模型与线程

    12.3 Java 内存模型

    12.3.1 主内存与工作内存

    线程、主内存、工作内存三者的交互关系

    Java每条线程都有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝。
    线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

    12.3.3 对volatile型变量的特殊规则

    当一个变量定义为volatile之后,它将具备两种特性:

    1. 保证此变量对所有线程的可见性,当一条线程修改了这个变量的值,新值对于其它线程来说是立即得知的。volatile保证了新值能立即同步到主内存,以及每次用之前立即从主内存刷新。
    2. 尽职指令重排序优化。

    相关文章

      网友评论

        本文标题:《深入理解Java虚拟机》

        本文链接:https://www.haomeiwen.com/subject/jntbvftx.html