美文网首页
【Java】虚拟机浅显分析

【Java】虚拟机浅显分析

作者: Stalary | 来源:发表于2018-02-02 14:25 被阅读0次

    普及一下虚拟机中的一些区域,垃圾收集算法,垃圾收集器等。

    运行时数据区域

    程序计数器

    1. 线程私有
    2. 一块较小的内存空间
    3. 记录正在执行的java方法的虚拟机字节码指令的地址(native方法为空)
    4. 没有OutOfMemoryError的情况

    Java虚拟机栈

    1. 线程私有

    2. 为虚拟机执行java方法服务

    3. 与线程的生命周期相同

    4. 当线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError,当虚拟机扩展时无法申请到足够的内存,将会抛出OutOfMemoryError

    5. 方法执行时创建的栈帧用于存储局部变量表,操作数栈,动态链接,方法出口

    6. 方法调用时分配栈帧,离开时撤销栈帧

    7. 局部变量表:

      • 存放基本数据类型
      • 对象引用

    本地方法栈

    1. 为虚拟机执行native方法服务
    2. 和虚拟机栈相同,会抛出StackOverflowError和OutOfMemoryError

    Java堆

    1. 线程共享
    2. 内存中最大的一块
    3. 存放对象实例以及数组
    4. 垃圾收集器管理的主要区域(GC堆)
    5. 堆中分为新生代和老年代
    6. 堆无法再扩展时,抛出OutOfMemoryError

    方法区

    1. 线程共享
    2. 存储已被虚拟机加载的类信息,常量,静态变量,编译器编译后的代码
    3. 方法区无法满足内存需求时抛出OutOfMemoryError
    4. 使用永久代实现

    运行时常量池

    1. 方法区的一部分
    2. 存储字面量和符号引用
    3. 无法再申请到内存时抛出OutOfMemoryError

    对象的访问

    句柄访问

    1. 引用存储对象的句柄地址
    2. 对象被修改时只会改变句柄中的实例数据指针,引用本身不改变

    直接指针访问

    1. 引用存储对象的地址
    2. 速度更快,Sun HotSpot虚拟机中使用

    异常

    堆溢出

    1. 不断创建对象实例并且GC Roots到对象之间有可达路径避免垃圾回收时会产生(OOME Java heap space)

    栈溢出

    1. 栈深度大于虚拟机所允许的深度(SOE)
    2. 虚拟机扩展时无法申请到足够的内存
    3. 多线程下导致的内存溢出,可以通过减少最大堆和减少栈容量来换取更多的线程(OOME)

    方法区和运行时常量池溢出

    1. 产生大量的动态类(OOME PermGen space)

    判断对象是否已经死亡

    引用计数算法

    1. 有一个地方引用,计数器加1,引用失效时减1,当为0时即代表不可能被使用
    2. 缺点是无法解决循环引用的问题
      A.instance = B;B.instance = A。两个对象都已经不可能被访问

    可达性分析算法

    1. GC Roots对象作为起点,判断是否存在路径(引用链)可以到达对象

    2. 可以作为GC Roots的对象有:

      • 虚拟机栈中引用的对象
      • 方法区中类静态属性引用的对象
      • 方法区中常量引用的对象
      • native方法引用的对象

    引用

    强引用

    A a = new A();
    

    只要强引用存在,垃圾回收器永远不会回收被引用的对象

    软引用

    1. 有用但非必需的对象
    2. 在将要发生内存溢出之前,进行回收,这次回收内存还没有足够的内存,才会抛出异常
    3. 使用SoftReference来实现

    弱引用

    1. 非必需对象
    2. 只能生存到下一次垃圾收集发生之前
    3. 使用WeakReference来实现

    虚引用

    1. 无法获得对象实例
    2. 唯一目的是被垃圾回收时收到一个系统通知
    3. 使用PhantomReference来实现

    垃圾收集算法

    标记-清除算法

    1. 首先标记需要回收的对象
    2. 清除标记的对象
    3. 缺点是会产生大量不连续的内存碎片,当需要分配较大对象时,会提前触发另一次垃圾回收

    复制算法

    1. 将内存按容量划分为大小相等的两块,每次只使用其中的一块,使用完成后将存活的对象复制到另一块,然后清理使用过的内存空间
    2. 缺点是浪费内存

    标记-整理算法

    1. 首先标记需要回收的对象
    2. 将所有存活的对象向一端移动
    3. 清理掉边界以外的内存

    分代收集算法

    1. 分为新生代和老年代进行收集
    2. 新生代每次都有大批对象死去,可以选用复制算法,老年代中对象存活率高,使用标记-清除算法或者标记-整理算法

    垃圾收集器

    Serial收集器

    1. 单线程收集器,垃圾收集时,需停止其他的所有工作线程(Stop The World)
    2. 在Client模式下时很好的选择
    3. 新生代采用复制算法,老年代采用标记-整理算法
    4. 由于单线程的环境,所以简单高效
    5. 新生代收集器

    Serial Old收集器

    1. Serial收集器的老年代版本
    2. 在jdk1.5之前和Parallel Scavenge收集器搭配使用(1.5之后才有Parallel Old收集器)。
    3. 作为CMS收集器的后备预案(发生Concurrent Mode Failure时使用)
    4. 老年代收集器

    ParNew收集器

    1. Serial收集器的多线程版本
    2. 新生代采用复制算法,老年代采用标记-整理算法
    3. 与CMS收集器搭配使用
    4. 并发收集器
    5. 新生代收集器

    Parallel Scavenge收集器

    1. 吞吐量优先
    2. 可以直接设置最大垃圾收集停顿时间(缩短停顿时间,同时也会缩短停顿间隔,减小吞吐量)和吞吐量(代码运行时间/代码运行时间+垃圾回收时间)大小
    3. 可以使用自适应的调节策略来控制停顿时间和吞吐量(与ParNew的主要区别)
    4. 年轻代收集器
    5. 多线程

    Parallel Old收集器

    1. 多线程
    2. 老年代收集器
    3. jdk1.6中开始提供

    CMS收集器

    1. 停顿时间最短化

    2. 分为四个步骤:

      • 初始标记
      • 并发标记
      • 重新标记
      • 并发清除

    初始标记和重新标记需要Stop The World但是时间并不长,剩下两个步骤可以和用户线程并发执行

    1. 基于标记-清除(注意),会产生大量的内存碎片
    2. 缺点是并发占用一部分用户线程,造成吞吐量降低应用程序变慢,无法处理浮动垃圾(垃圾回收时产生的垃圾)

    G1收集器

    1. 分代收集

    2. 使用多个cpu并行执行垃圾回收(缩短Stop The World的时间),通过并发使其他线程和垃圾回收线程一起执行

    3. 整体上标记-整理算法,局部上基于复制算法

    4. 可预测何时停顿以及停顿时间,通过后台维护一个垃圾回收优先列表来实现

    5. 与其他收集器不同,不再以新生代和老年代做区分,而是划分为多个大小相同的区域。

    6. 通过Remembered Set来避免全堆扫描

    7. 分为四个步骤:

      • 初始标记
      • 并发标记
      • 最终标记
      • 筛选回收

    每一本书,都是我们升华的阶梯

    相关文章

      网友评论

          本文标题:【Java】虚拟机浅显分析

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