zoukankan      html  css  js  c++  java
  • 轻松理解JVM的分代模型

    前言

    上篇文章我们一起对jvm的内存模型有了比较清晰的认识,小伙伴们可以参考JVM内存模型不再是秘密这篇文章做一个复习。

    本篇文章我们将针对jvm堆内存的分代模型做一个详细的解析,和大家一起轻松理解jvm的分代模型。

    相信看过其他文章的小伙伴们可能都知道,jvm的分代模型包括:年轻代、老年代、永久代。

    那么它们分别代表着什么角色呢?我们先来看一段代码

    public class Main {
        public static void main(String[] args) {
            while (true){
                load();
            }
        }
    
        public static void load(){
            SysUser sysUser = new SysUser();
            sysUser.setAvatar("1");
        }
    
    }

    这段代码本身没有什么特殊的含义,主要是理解jvm的运行机制。

    首先一旦执行main()方法,就会把main()方法的栈帧压入main线程的虚拟机栈,然后调用load()方法后,又会把load()方法的栈帧压入虚拟机栈。

    接着在执行load()方法时,会在java堆内存中创建一个SysUser对象实例,而栈帧中会有sysUser局部变量引用堆内存中的SysUser对象实例。

    如下图:

     到这里上篇文章都讲解过,相信大家都能看懂。

    变量的存活时间

    现在我们思考一下会发现,这个SysUser对象实际上属于一个短暂存活的对象,因为在load()方法执行完毕后,load()方法的栈帧就会出栈。

    而一旦出栈,就没有了sysUser这个局部变量来引用SysUser这个对象的实例。

    所以,其实这个SysUser对象已经没有用了,但是它还在占用着堆内存的空间,那么对于这种没有引用的对象实例jvm是如何处理的呢?

    这就要说到jvm的垃圾回收机制了,jvm本身是有垃圾回收机制的,它是一个后台线程,会把没有人引用的SysUser对象实例给回收掉,不断的释放内存空间。

    所以这个SysUser对象实例是一个存活时间很短的对象,可能在执行load()方法的时候被创建出来,执行之后就被垃圾回收掉了。

    而这种对象在我们平时的开发中是很常见的,占绝大多数比例。

    现在我们将上边的代码改造一下:

    public class Main {
        private static SysUser sysUser = new SysUser();
        public static void main(String[] args) {
            while (true){
                load();
            }
        }
    
        public static void load(){
            sysUser.setAvatar("1");
        }
    }

    其实就是把局部变量sysUser变成了静态变量,这样修改后,sysUser不在作为局部变量保存在栈中,而是和class类文件一起保存在方法区中,这样SysUser对象实例就会一直被这个静态变量引用,所以不会被垃圾回收,一直保存在堆内存中。如下图:

    分代模型

    接下来我们进入核心内容,就是jvm的分代模型了。

    上文中我们发现,根据我们的编码方式的不同,采用不同的方式创建和使用对象,对象的存活时间是不同的。

    所以jvm将内存区分为两个区域:年轻代和老年代

    年轻代就是我们的第一种局部变量的示例,创建和使用完毕后会被垃圾回收掉。

    老年代就是第二种静态变量的示例,创建后需要长期在堆内存中存活。

    相信到这里大家就应该理解了什么样的对象是短期存活的对象,什么样的对象是长期存活的对象,那么它们是如何分别存在年轻代和老年代中的呢?为什么要这么区分呢?

    其实这与垃圾回收机制是密不可分的。

    对于年轻代里的对象,他们的特点是创建后很快就会被回收,而对于老年代里的对象,他们的特点是需要长期存活,所以这两种对象是不能用一种垃圾回收算法进行回收的,所以需要区分成两个。

    对于长期存在的静态变量sysUser,其实刚开始的时候也是在年轻代的,那它是什么时候进入老年代的呢?我们下文会讲解这个问题。

    那永久代又是什么呢?其实永久代就是我们说的jvm的方法区,用于存放一下类信息的,这部分之后的文章涉及到会详解,现在理解到这就可以了。

    新生代的垃圾回收

    前文我们了解了,当load方法执行完毕出栈后,里面的局部变量sysUser就没了,堆内存中的SysUser对象就没有引用了,所以会被垃圾回收掉。

    那么问题来了,是没有引用后就会立即发生垃圾回收,回收掉没有被引用的对象实例吗?

    其实不是这样的,垃圾回收是有触发条件的。

    有一个比较常见的场景是这样的,假设我们的代码中创建了大量的对象,导致堆内存中囤积了大量的对象,然后这些对象现在都没有人引用了。

    这个时候,如果新生代预先分配的内存空间被占满了,那么我们的代码此时要新创建一个对象的时候,发现新生代空间满了,怎么办?

    这个时候就会触发一次新生代的垃圾回收,也称为“Minor GC”或"Young GC",它会尝试把新生代中没有人引用的对象给回收掉,释放空间。

    下图表达了这一过程:

    长期存活的对象什么时候进入老年代

    接下来我们谈论一个话题,静态变量引用的长期存活的对象是什么时候进入老年代的。

    上文我们了解到,新生代的对象会经历一次次的垃圾回收,而被静态变量引用的对象因为一直被引用,所以一直不会被回收,所以此时jvm就有了一条规定。

    如果新生代中的对象,在经历了15次垃圾回收后,依然坚挺的存活着,那就证明它是个"老年人"了,然后它会被转移到老年代中。

    老年代就是存放这些年龄比较大的对象的。

    那么老年代中的对象会被垃圾回收吗?

    答案是肯定的,因为老年代里的对象随着代码的运行,也是可以不再被任何人引用的,就需要垃圾回收了。

    或者说,随着越来越多的对象进入老年代,老年代的内存也会被占满,所以一定是要对老年代进行垃圾回收的。

    我们暂时不用考虑具体是怎么回收的,这个内容在之后的文章中我们会有详细的解析。

    总结

    今天就给大家准备了这么多内容,可能有些小伙伴觉得还没看够,这些内容都比较简单,我已经会了,有没有更深入的东西呢?

    别急,学习是循序渐进的事情,王子是想要用最简单的大白话来和小伙伴们一起讨论jvm的原理的,同时也想找一些案例来和大家一起探讨,印象会更深刻。

    所以今天小伙伴们了解到这里就可以了,让我们在后续的文章中不见不散,深入讨论些更深层的内容吧。

    往期文章推荐:

    大白话谈JVM的类加载机制

    JVM内存模型不再是秘密

  • 相关阅读:
    vsftp关于"550 create directory operation failed"问题解决
    CentOS 5.5 Samba服务器安装总结
    Centos 5.5下安装samba
    iptables里filter表前面几个数字的意思
    Linux误删C基本运行库libc.so.6急救方法
    Linux升级C基本运行库CLIBC
    MySQL的Grant命令
    Apache Options指令详解
    Apache的Order Allow,Deny 详解
    Python 中 open()文件操作的方式
  • 原文地址:https://www.cnblogs.com/lm970585581/p/13793407.html
Copyright © 2011-2022 走看看