深入理解Java虚拟机(1)

编程入门 行业动态 更新时间:2024-10-05 05:19:22

深入理解Java<a href=https://www.elefans.com/category/jswz/34/1770279.html style=虚拟机(1)"/>

深入理解Java虚拟机(1)

深入理解Java虚拟机(1)

    • 运行时数据区域
    • 对象访问
    • OOM
    • 垃圾收集器
    • 分代
    • 对象的内存分配

运行时数据区域

  1. 程序计数器 ,线程隔离;
  2. 虚拟机栈 ,线程隔离;
    描述的是Java方法执行的内存模型,方法被调用到执行完成对应了栈帧从入栈到出栈的过程。栈帧存储:局部变量表、操作数栈、动态链接、方法出口等信息。局部变量表存放了编译期可知的各种基本数据类型、对象引用、returnAddress类型。局部变量表所需的内存空间在编译期间完成分配且不会改变。
  3. 本地方法栈,线程隔离;
    与虚拟机栈作用非常类似,虚拟机栈执行Java方法,本地方法栈执行Native方法。
  4. Java堆,线程共享;
    唯一目的是存放对象实例。
    进一步划分(新生代、老年代;Eden空间、From Survivor空间、To Survivor空间;线程私有的分配缓冲区TLAB: thread local allocation buffer)是为了更好的回收内存或者更快的分配内存。
  5. 方法区,线程共享;
    存储已被虚拟机加载的类信息、常量、静态变量,即时编译器编译后的代码等数据。内存回收目标主要是对常量池的回收和类型的卸载。
    5.1 运行时常量池
    相对于class文件常量池的一个重要特征是具备动态性,可以在运行期间将新的常量放入池中;另一个重要特征是,Java虚拟机规范没有做任何细节的要求。
  • 直接内存
    不是虚拟机运行时数据区的一部分,在JDK1.4中新加入了NIO类,引入了一种基于通道和缓冲区的I/O方式,使用Native函数库直接分配堆外内存,通过存储在Java堆里的DirectByteBuffer对象作为这块内存的引用进行操作。
    避免了在Java堆和Native堆中来回复制数据,

对象访问

Object obj = new Object();

左边部分的语义反映到Java栈的本地变量表中,作为一个引用类型数据出现;右边部分的语义反映到Java堆中,存储所有实例数据值。
通过引用访问对象有两种主流的方法

  1. 使用句柄
    Java堆中划分一块内存作为句柄池,引用中存储对象的句柄地址,句柄中包含的对象实例数据和类型数据的具体地址信息。
  2. 直接指针
    Java堆对象的布局必须考虑如何放置访问类型数据的相关信息,引用中存储对象地址。

OOM

out of memory

垃圾收集器

哪些内存需要回收?什么时候回收?如何回收?
程序计数器、虚拟机栈、本地方法栈,当方法结束或线程结束时,内存自然就跟着回收。加法堆和方法区只有在程序处于运行期间,才能知道会创建哪些对象。

分代

新创建的对象会在新生代 中分配内存;
经过多次回收仍然存活下来的对象存放在老年代中;
静态属性、类信息等存放在永久代中。
新生代:频繁;老年代:频率相对较低;永久代:一般不进行垃圾回收。
还可以根据不同年代的特点采用合适的垃圾收集算法

  1. 判断对象是否存活
    (引用计数算法——难以解决对象间相互循环引用)
    根搜索算法,GC Roots Tracing。一系列名为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连,证明其对象是不可用的。
    引用分类:强引用strong、软引用soft、弱引用weak、虚引用phantom,依次减弱。
  2. 两次标记
    在进行根搜索后没有发现引用链会被第1次标记,并且进行一次筛选:是否有必要执行finalize()方法?yes!放入F-Queue队列,稍后GC对队列中对象进行第2次标记。任何一个对象的finalize()方法只会被系统自动调用1次,
    方法区:(有必要的话)废弃常量&无用的类
  3. 垃圾收集算法
    3.1 标记-清除算法
    基础;效率不高、内存碎片。
    3.2 复制算法
    将内存容量划分为大小相等的2块,每次只使用1块。用完将存活对象复制到另1块。移动栈顶指针,顺序分配。
    简单高效;内存缩小一半。
    根据统计数据98%回收率,新生代:内存分为1块较大的Eden空间+2块较小的Survivor空间。使用1块Eden+1块Survivor,拷贝到1块Survivor。当survivor不够,需要依赖老年代进行分配保证(Handle Promotion)。
    不适用存活率较高的情况,比如老年代。
    3.3 标记-整理算法
    标记过程和标记-清除算法一样,后续所有存活对象向一端移动,直接清理端边界以外的内存。
    3.4 分代收集算法
    Java堆划分。新生代使用复制算法;老年代使用标记-清除/标记-整理算法
  4. 垃圾收集器
    4.1 Serial收集器-Serial Old收集器
    单线程,工作时必须暂停其他所有工作线程(Stop the World)。仍是虚拟机运行在Client模式下默认的新生代收集器。
    简单高效;收集100+M的新生代,停顿时间可以控制在最多100+毫秒内。
    4.2 ParNew收集器
    Serial的多线程版本。虚拟机运行在Server模式下默认的新生代收集器。并发收集器。
    4.3 Parallel Scavenge收集器-Parallel Old收集器
    可控制吞吐量。2个参数:最大垃圾收集停顿时间;吞吐量大小。
    4.4 CMS收集器
    获取最短回收停顿时间。全称,Concurrent Mark Sweep,基于标记-清除算法实现。并发收集,低停顿。过程分为4个步骤:
    (1)初始标记:单线程。标记GC Roots能直接关联到的对象,速度快。
    (2)并发标记
    (3)重新标记:单线程。修正(2)期间因用户程序继续操作导致标记产生变动的对象的标记记录,时间比(1)稍长,远比(2)短。
    (4)并发清除
    缺点:
    (1)CPU资源占用,默认回收线程(CPU数量+3)/4。尽量减少GC线程占用资源的时间(抢占式模拟多任务机制,即在并发阶段让GC线程、用户线程交替进行)。
    (2)浮动垃圾。并发阶段预留内存空间给用户线程,一旦无法满足,出现Concurrent Mode Failure,启动后备方案——临时启用Serial Old收集器。
    (3)空间碎片,无法找到足够大的连续空间,提前触发Full GC。碎片整理过程,单线程。
    4.5 G1收集器
    基于标记-整理算法,可以精确控制停顿,近实时。
    可以在基本不牺牲吞吐量的前提下,完成低停顿的内存回收:避免全区域的垃圾收集。将Java堆划分为多个大小固定的独立区域,跟踪其垃圾堆积程度,后台维护一个优先列表。
    根据允许的收集时间优先回收垃圾最多的区域(Garbage First)。

对象的内存分配

1、优先在新生代Eden区,如果没有空间,JVM发起一次Minor GC;
2、大(内存空间超过阈值)对象直接进入老年代;
3、长期存活(对象年龄计数器——熬过一次Minor GC,+1)的对象将进入老年代;(静态)超过阈值/(动态)Survivor空间相同年龄所有对象大小的总和大于Survivor空间的一半。
在新生代进行内存分配时,发现没有足够的空间,则会进行一次Minor GC,Minor GC之后仍然有大量对象存活,需要老年代进行分配担保,让无法进入新生代空间的对象直接进入老年代。在进入老年代之前,先会取每一次回收到老年代的对象容量的平均值,与老年代的剩余空间进行比较决定是否进行Full GC。但是还是出现远高于平均值的水平,会导致担保失败,在失败后重新发起一次Full GC。

更多推荐

深入理解Java虚拟机(1)

本文发布于:2024-03-14 05:30:12,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1735736.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:虚拟机   Java

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!