浅谈JVM组成

    xiaoxiao2022-07-05  157

    文章目录

    1. JVM内存结构分区2. 对象存活判断3. 对象死亡(被回收)前的最后一次挣扎4.常用的垃圾收集算法4.1 标记 -清除算法4.2 复制算法4.3 标记-压缩(整理)算法4.4 分代收集算法 5.jvm中一次完整的GC流程是怎样的,对象如何晋升到老年代,说说你知道的几种主要的jvm参数。

    1. JVM内存结构分区

    程序计数器:当前线程所执行的字节码的行号指示器,用于记录正在执行的虚拟机字节指令地址,线程私有JVM栈:++存放编译期间可知的8种基本数据类型,及对象引用和指令地址++、方法出口等,线程私有 虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。会有两种异常StackOverFlowError和 OutOfMemoneyError。当线程请求栈深度大于虚拟机所允许的深度就会抛出StackOverFlowError错误;虚拟机栈动态扩展,当扩展无法申请到足够的内存空间时候,抛出OutOfMemoneyError。 本地方法栈:本地方法栈和虚拟机栈类似,只不过本地方法栈为Native方法服务 虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务 堆:堆被所有线程共享区域,java内存最大的一块,在虚拟机启动时创建,唯一目的存放对象实例。 Java堆是垃圾收集器管理的主要区域,有时候也被称为“GC堆”。因为现在收集器基本都采用分代收集算法,所有Java堆还可以细分为:新生代和老年代,再细致一点有Eden空间、From Survivor空间、To Survivor空间等,默认情况下各自占比 8:1:1。 方法区: 各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,习惯是也叫它永久代(permanment generation) 垃圾回收很少光顾这个区域,不过也是需要回收的,主要针对常量池回收,类型卸载。常量池用于存放编译期生成的各种字节码和符号引用,常量池具有一定的动态性,里面可以存放编译期生成的常量;运行期间的常量也可以添加进入常量池中,比如string的intern()方法。

    2. 对象存活判断

    判断对象是否存活一般有两种方式:

    引用计数算法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。

    可达性分析算法:从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。不可达对象。

    程序把所有的引用关系看做一张图,从一个节点 GC ROOT 开始,寻找对应的引用节点,找到这个节点后,继续寻找这个节点的引用节点,把所有的引用节点寻找完毕后,剩余的节点则会被人为是没有被引用到的节点,就是没有用的节点,没有用的节点就会被认为是可以回收的对象。在Java语言中,GC Roots包括: 虚拟机栈中引用的对象方法区中类静态属性引用的对象方法区中常量引用的对象本地方法栈引用的对象

    3. 对象死亡(被回收)前的最后一次挣扎

    在可达性分析算法中不可达的对象并不是被马上回收,要真正回收要经历过两次标记过程。

    第一次标记:在对对象进行可达性分析后发现没有与 GC ROOT 相连的引用链,就会被第一次标记。第二次标记:在第一次标记后,如果对象有执行 finalize() 方法,如果在执行 finalize() 方法后没有重新与上面所说的引用链建立关系,就会被第二次标记。

    第二次被标记的对象就会真的被回收,如果 finalize() 方法执行后重新连上引用链就不会被回收。

    4.常用的垃圾收集算法

    4.1 标记 -清除算法

    “标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。之所以说它是++最基础的收集算法,是因为后续的收集算法都是基于这种思路并对其缺点进行改进而得到的++。它的主要缺点有两个: 一个是效率问题,标记和清除过程的效率都不高;另外一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

    4.2 复制算法

    “复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。

    4.3 标记-压缩(整理)算法

    标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但是在清除时不同,在回收不存活的对象占用空间后,将所有的内存中对象往左端空闲空间移动,并更新对应的指针。

    4.4 分代收集算法

    现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。++在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法++。++老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记-整理 或者 标记-清除++。 新生代回收算法:新生代按 8:1:1 比例划分为一个 eden 区和两个 surivor 区。大部分对象在 eden 区生成。将 eden 区存活的对象复制到 surivor0 区,然后清空 eden 区,survor0 区满了就复制到 survor1 区老年代回收算法:新生代经历了 N 次算法后仍然存活的对象,当老年代内存满了之后就触发 FULL GC ,频率比较低,存活率比较高。持久代的回收算法:持久代也称为方法区,用于存放静态文件,如:Java 类、方法等,持久代对垃圾回收没有显著影响。

    5.jvm中一次完整的GC流程是怎样的,对象如何晋升到老年代,说说你知道的几种主要的jvm参数。

    图解:

    对象晋升老生代一共有三个可能:

    1.大对象:所谓的大对象是指需要大量连续内存空间的java对象,++最典型的大对象就是那种很长的字符串以及数组++,大对象对虚拟机的内存分配就是坏消息,尤其是一些朝生夕灭的短命大对象,写程序时应避免。

    2.长期存活的对象:当对象达到成年,经历过15次GC(默认15次,可配置),对象就晋升为老生代

    虚拟机给每个对象定义了一个对象年龄(Age)计数器,++如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1,。对象在Survivor区中每熬过一次Minor GC,年龄就增加1++,++当他的年龄增加到一定程度++(默认是15岁), 就将会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。

    3.动态对象年龄判定:如果survivor空间某个年龄对象的大小大于survivor空间的一半,年龄大于或等于的直接进入老年代

    为了能更好地适应不同程度的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升到老年代,如果在Survivor空间中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

    jvm参数:

    Xms:初始堆大小Xmx:堆最大内存Xss:栈内存XX:PermSize 初始永久带内存XX:MaxPermSize 最大永久带内存
    最新回复(0)