• [读书笔记]深入理解Java虚拟机——内存自动管理


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

    内存模型

    • 程序计数器

      Program Counter Register 线程私有,较小的内存空间,当前线程所执行的字节码行号指示器。

    • Java虚拟机栈

      Java Virtual Machine Stack 线程私有,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

      局部变量表:

      存储基本数据类型对象引用(指向对象起始地址的指针or代表对象的句柄or与对象相关的位置)、returnAddress类型(一个字节码指令的地址)。

      存储空间以局部变量槽(Slot)分割,64位的long和double用2个槽,其他用一个。

      所需的内存空间均在编译期间分配

      线程请求的栈深度超过虚拟机允许,抛出StackOverflowError异常;如果栈可以动态扩展,则会在无法申请到足够内存时抛出OutOfMemoryError异常。

    • 本地方法栈

      Native Method Stacks 与JVM Stack相似,只不过时为本地方法服务。

    • Java堆

      Java Heap 是虚拟机所管理的内存中最大的,被所有线程共享,作用是存放对象实例。“几乎”所有对象实例都在此存放。

      Java堆是垃圾收集器管理的内存区域,因此被称为GC(Garbage Collected Heap)。

      堆物理可以不连续,但逻辑上应该被视为是连续的。

      当前主流堆都是可扩展的,当堆没有内存可以分配且不能扩展时,抛出OutOfMemoryError异常。

    • 方法区

      Method Area 被所有线程共享,存储已被加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

      垃圾回收比较少见,主要针对常量池的回收和类型的卸载。

      运行时常量池:Runtime Constant Pool,存放编译期生成的各种字面量和符号引用、由符号引用翻译出来的直接引用。

    对象的内存布局

    在HotSpot虚拟机中,对象存储在堆中可以划分为:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)。

    • 对象头:包括

      • 存储对象自身的运行时数据Mark Word,例如HashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等;考虑到虚拟机的空间效率Mark Word被设计为一个有动态定义的数据结构。

      • 类型指针,对象指向它类型元数据的指针,虚拟机通过这个指针来确定该对象是哪个对象的实例。

      • 如果对象是一个数组,还需要记录数组的长度。

    • 实例数据:在代码中定义的各种类型的字段内容。

    • 对齐填充:HotSpot要求对象起始地址是8字节的整数倍。

    对象的访问定位

    Java程序通过栈上的reference数据来操作堆上的具体对象。方法有句柄和直接指针两种:

    • 句柄:在Java堆中划出一块内存存储句柄,reference指向句柄。好处是在对象被移动时(垃圾回收时普遍发送)只需要修改句柄中指针,而不修改reference本身。

    • 直接指针:reference直接指向实例数据,此时对象内部需要存储访问类型数据的相关信息。好处是节约了一次指针定位的开销。

    垃圾收集器与内存分配策略

    程序计数器、虚拟机栈、本地方法栈在方法结束或线程结束时,内存自然回收;

    JAVA堆和方法区,在运行期间动态分配和回收内存。

    判断方法

    • 引用计数法

      Reference Counting 额外占用内存空间进行计数,(但是主流Java虚拟机没有用引用计数法);

      弊端:循环引用导致计数永远不为零。

    • 可达性算法

      Reachability Analysis (Java,C#使用);

      使用GC Roots作为起始节点集,根据引用关系向下搜索,不可达的就被判断为可回收对象;

      GC Roots可以是虚拟机栈中引用的对象、方法区中静态属性引用的对象、方法区中常量应用的对象、本地方法栈中引用的对象、虚拟机内部的引用(Class对象、异常对象、类加载器等)、被synchronized关键字持有的对象、虚拟机内贸部注册的回调缓存等。

    引用(强软弱虚)

    强引用:类似于传统引用的定义,只要强引用关系存在,就不会被回收;

    软引用:还有一定作用,但不是必须的对象。在内存Out Of Memory之前会被回收,如果回收之后还没有足够内存,才抛出溢出异常;

    弱引用:非必须对象,存活到下一次垃圾收集时,无论内存是否足够,都会回收掉弱引用对象;

    虚引用:虚引用不会对对象生存时间有影响,也无法通过虚引用获得对象实例,只能用于在对象被回收时收到一个系统通知。

    两次标记

    在对象可达性分析后,且没有与GC Roots相连接的引用链,就会被第一次标记,然后进行筛选:

     if 对象没有覆盖finalize()方法 or finalize()方法已经被虚拟机调用过一次
      then 判断为 没有必要执行 finalize()方法
      else 有必要执行finalize()方法,对象会被放在一个F-Queue队列中,并被虚拟机建立的、低调度优先级的Finalizer线程执行它的finalize()方法(仅触发运行,不等待结束),然后收集器会对F-Queue中对象进行第二次标记

    这时,如果要拯救自己,则可以在finalize()函数中将自己和引用链上的一个对象关联即可

    两次标记后,对象才会被真正地回收。

    方法区回收

    主要回收:废弃的常量和不再使用的类型

    不再使用的类(只是被允许回收,不是一定会回收):

    • 堆中不存在该类和任何派生子类的实例

    • 加载该类的类加载器已经被回收(通常很难达成)

    • 该类的java.lang.Class对象也没有被引用,无法再任何地方通过反射访问该类的方法

    垃圾收集算法

    下列均为”追踪式垃圾收集“(Tracing GC)

    分代收集理论

    大多数商业虚拟机使用,建立在两个假说之上:

    • 弱分代假说:绝大多数对象都是朝生夕灭的

    • 强分代假说:熬过越多次垃圾收集的对象越难以消亡

    • 跨代引用假说:跨代引用相对于同代引用仅占少数

    由前两个假说得出的设计原则:收集器将堆划分为多个区域,按垃圾收集后存活次数来分配对象存储区域。垃圾收集器每次回收其中一个或某些部分的区域。

    根据第三条假说:我们不会为了少量跨代引用去扫描整个老年代,而是在新生代维护一个数据结构”记忆集”Remembered Set,标志出老年代哪一块内存由跨代引用,Minor GC时只有跨代引用的内存对象才会被加入扫描。

    • Minor GC:新生代收集

    • Major GC:老年代收集(通常没有单独收集老年代,只有CMDS收集器会)

    • Mixed GC:收集整个新生代和部分老年代(只有G1收集器会)

    • Full GC:收集整个堆和方法区

    标记-清除算法

    最早出现也是最基础的算法。标记需要回收的对象,然后统一回收;或标记存活的对象, 然后统一回收未标记的对象。

    缺点有:1.效率不稳定;2.产生内存空间碎片

    标记-复制算法

    理论:将内存分为等大小的两块,一块用完了就将存活对象复制到另一块上,在将用过的一块全部清理。

    实现简单,运行高效,但空间浪费。

    现在商用虚拟机大多采用这种方法回收新生代。

    Appel式回收:按照弱分代假说,HotSpot虚拟机将空间分为Eden:Survivor:Survivor = 8:1:1,每次垃圾收集时,将Eden和一块Survivor中存活的对象复制到另一块Survivor上。如果一块Survivor不够容纳所有存活对象,可以去其他内存区域(大多是老年代)进行分配担保,将对象放入老年代。

    标记-整理算法

    针对老年代的特征,让所有存活对象向一段移动,并清理边界以外的内存

    弊端:存活对象移动需要暂停用户应用程序(标记-清除也需要暂停,但时间相对较短)

    回收策略

    • 对象优先在新生代Eden分配,当Eden没有足够空间时,虚拟机发起一次Minor GC.

    • 大对象直接进入老年代,避免在Eden和Survivor之间来回复制。

    • 长期存活对象进入老年代,默认为存活15次。

    • 动态对象年龄判定,如果Survivor中相同年龄的对象大小总和大于Survivor空间的一半,大于这个年龄的所有对象都可以直接进入老年代。

    • 空间分配担保,在Minor GC之前

       if 老年代最大连续可用空间大于新生代所有对象总空间
        then Minor GC安全
       else if 参数设置允许担保失败 and 老年代最大连续可用空间大于历次晋升到老年代对象的平均大小
        then 尝试 Minor GC
       else 空间小于 or 参数不允许担保失败
        then Full GC

       

  • 相关阅读:
    Flex 学习笔记------组件和视图
    Flex 学习笔记------基于LZMA的文件压缩与上传
    Flex 学习笔记------FLACC & Crossbridge
    Flex 学习笔记------全局事件
    Flex 学习笔记------对象的深层拷贝
    Flex 学习笔记------as 与 js 的通信
    Flex 学习笔记------Local Shared Object 和 Custom Class
    Flex 学习笔记------读取Jpeg图片的width,height和colorSpace
    翻译:eval() 不是魔鬼,只是易被误解
    翻译:javascript 内存管理
  • 原文地址:https://www.cnblogs.com/kubab119/p/15954558.html
Copyright © 2020-2023  润新知