详解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 分代模型的资料请关注我们其它相关文章!

(0)

相关推荐

  • jvm内存溢出解决方法(jvm内存溢出怎么解决)

    java.lang.OutOfMemoryError: PermGen space 发现很多人把问题归因于: spring,hibernate,tomcat,因为他们动态产生类,导致JVM中的permanent heap溢出 .然后解决方法众说纷纭,有人说升级 tomcat版本到最新甚至干脆不用tomcat.还有人怀疑spring的问题,在spring论坛上讨论很激烈,因为spring在AOP时使用CBLIB会动态产生很多类. 但问题是为什么这些王牌的开源会出现同一个问题呢,那么是不是更基础的原

  • IDEA设置JVM运行参数的方法步骤

    前言 有时候我们需要在程序运行的时候对程序设置环境变量,恰巧我也遇到了这个问题,所以在此记录一下IDEA是如何设置环境变量的. 作用   -Dproperty=Value 该参数通常用于设置系统级全局变量值,如配置文件路径,保证该属性在程序中任何地方都可访问.当然,也可以通过在程序中使用System.setProperty进行设置. 注意: 1.如果-Dproperty=value的value中包含空格,可以将value使用引号引起来.例如:-Dmyname="hello world"

  • Jvm调优和SpringBoot项目优化的详细教程

    一.Jvm调优. 参考文章 1.先看一下未设置JVM参数的情况,默认情况下,没有设置任何Jvm参数. idea中安装VisualVM监控jvm的图文教程 2.设置Jvm参数. 配置参数: 关于这些设置的JVM参数是什么意思,参考Jvm调优. -XX:MetaspaceSize=128m (元空间默认大小) -XX:MaxMetaspaceSize=128m (元空间最大大小) -Xms1024m (堆最大大小) -Xmx1024m (堆默认大小) -Xmn256m (新生代大小) -Xss256

  • 详解jvm中的标量替换

    概述 通常在java中创建一个对象,大家都认为是在堆中创建. 在jdk6开始有逃逸分析,标量替换等技术,关于在堆中创建对象不再绝对. 关于标量替换,通过以下几点进行概述: 逃逸分析 标量替换是什么 测试标量替换 逃逸分析 逃逸分析是一种分析技术,分析对象的动态作用域,供其他优化措施提供依据.比如分析一个对象不会逃逸到方法之外或线程之外,其它优化措施(栈上分配,标量替换等)根据逃逸程度进行优化. 逃逸分析示例 public class EscapeAnalysis { public Person

  • Android Studio 报错failed to create jvm error code -4的解决方法

    安装完 Android Studio 后启动,却报错如下: 复制代码 代码如下: failed to create jvm error code -4 这一般应是内存不够用所致,解决方法参考如下. 打开 Android Studio 安装目录下的bin目录,查找并打开文件 studio.exe.vmoptions,修改代码: 复制代码 代码如下: -Xmx512m 为 -Xmx256m 保存后应即可正常打开了.

  • 通俗讲解JVM的类加载机制

    前言 我们很多小伙伴平时都是做JAVA开发的,那么作为一名合格的工程师,你是否有仔细的思考过JVM的运行原理呢. 如果懂得了JVM的运行原理和内存模型,像是一些JVM调优.垃圾回收机制等等的问题我们才能有一个更清晰的概念. 为了走进JVM,深入了解底层,王子打算写一个JVM的专题,留下自己对JVM探索的足迹,同时也希望能帮到小伙伴们更好的理解JVM. 那我们开始吧. JAVA代码的运行流程 首先我们就来聊一聊JAVA代码是怎么运行起来的,这部分比较基础相信大家都知道,就当成是个复习吧. 我们编写

  • IntelliJ IDEA设置JVM运行参数的操作方法

    打开 IDEA 安装目录,看到有一个 bin 目录,其中有两个 vmoptions 文件,需针对不同的JDK进行配置: 32 位:idea.exe.vmoptions 64 位:idea64.exe.vmoptions -Xms512m -Xmx1024m -XX:MaxPermSize=512m -XX:ReservedCodeCacheSize=225m -XX:+UseConcMarkSweepGC -XX:SoftRefLRUPolicyMSPerMB=50 -ea -Dsun.io.u

  • JVM---jstack分析Java线程CPU占用,线程死锁的解决

    本文章主要演示在Windows环境,Linux环境也差不多. 一.分析CPU占用飙高 首先写一个Java程序,并模拟一个死循环.让CPU使用率飙高.CPU负载过大的话,新的请求就处理不了了,这就是很多程序变慢了甚至不能访问的原因之一. 下面是我这里的Controller,启动程序之后,开多个请求访问这个方法.死循环代码就不贴了,自己构造.我这里模拟的一个截取字符串的死循环. /** * 演示死循环导致cpu使用率飙高 * */ @RequestMapping("/loop") publ

  • 图解JVM内存模型

    前言 上篇文章我们一起了解了jvm虚拟机类的加载机制,而且是以一种纯大白话进行的一场闲聊,相信小伙伴们应该印象深刻,感兴趣的小伙伴可以重温一下上一篇文章大白话谈JVM的类加载机制. 当jvm加载了类后,会把需要使用的对象放入到内存当中,那么jvm的内存模型是什么样的呢? 今天我们就来探索一下jvm的内存模型.由于有小伙伴反映想加些图更容易理解,王子接下来的文章打算用更多的图例来讲解. 方法区 很多小伙伴之前也了解过jvm的内存模型,知道有方法区这个东西,但可能了解的不是很详细. 其实方法区是在J

  • 详解JVM的分代模型

    前言 上篇文章我们一起对jvm的内存模型有了比较清晰的认识,小伙伴们可以参考JVM内存模型不再是秘密这篇文章做一个复习. 本篇文章我们将针对jvm堆内存的分代模型做一个详细的解析,和大家一起轻松理解jvm的分代模型. 相信看过其他文章的小伙伴们可能都知道,jvm的分代模型包括:年轻代.老年代.永久代. 那么它们分别代表着什么角色呢?我们先来看一段代码 public class Main { public static void main(String[] args) { while (true)

  • 详解JVM系列之内存模型

    1. 内存模型和运行时数据区 这一章学习java虚拟机内存模型(Java Virtual machine menory model),可以这样理解,jvm运行时数据库是一种规范,而JVM内存模型是对改规范的实现 java虚拟机重点存储数据的是堆和方法区,所以本章节也重点从这两个方面进行比较详细描述.堆和方法区是内存共享的,而java虚拟机栈.Native方法栈.程序计数器是线程私有的 2.思维导图和图例 一个是非堆区(方法区),方法区也一般被称之为"永久代".另外一个是堆区,分为you

  • 详解JVM系列之对象的锁状态和同步

    java对象头 Java的锁状态其实可以分为三种,分别是偏向锁,轻量级锁和重量级锁. 在Java HotSpot VM中,每个对象前面都有一个class指针和一个Mark Word. Mark Word存储了哈希值以及分代年龄和标记位等,通过这些值的变化,JVM可以实现对java对象的不同程度的锁定. 还记得我们之前分享java对象的那张图吗? javaObject对象的对象头大小根据你使用的是32位还是64位的虚拟机的不同,稍有变化.这里我们使用的是64位的虚拟机为例. Object的对象头,

  • 详解JVM中的GC调优

    那些GC的默认值 其实GC或者说JVM的参数非常非常的多,有控制内存使用的: 有控制JIT的: 有控制分代比例的,也有控制GC并发的: 当然,大部分的参数其实并不需要我们自行去调整,JVM会很好的动态帮我们设置这些变量的值. 如果我们不去设置这些值,那么对GC性能比较有影响的参数和他们的默认值有哪些呢? GC的选择 我们知道JVM中的GC有很多种,不同的GC选择对java程序的性能影响还是比较大的. 在JDK9之后,G1已经是默认的垃圾回收器了. 我们看一下G1的调优参数. G1是基于分代技术的

  • 详解JVM之运行时常量池

    class文件中的常量池 之前我们在讲class文件的结构时,提到了每个class文件都有一个常量池,常量池中存了些什么东西呢? 字符串常量,类和接口名字,字段名,和其他一些在class中引用的常量. 运行时常量池 但是只有class文件中的常量池肯定是不够的,因为我们需要在JVM中运行起来. 这时候就需要一个运行时常量池,为JVM的运行服务. 运行时常量池和class文件的常量池是一一对应的,它就是class文件的常量池来构建的. 运行时常量池中有两种类型,分别是symbolic refere

  • 详解通过 OSI 七层模型打开计算机网络大门

    目录 正文 分层的体系结构 协议的分层 OSI参考模型 应用层 表示层 会话层 传输层 网络层 链路层 物理层 正文 最近为了准备面试,又再看了一遍 图解TCP/IP,发现很多知识点看了就忘,并没有形成一个系统知识,那么今天开始通过一系列的文章来系统总结一下计算机网络,在接下来的文章中会对重要的那几个模型进行讲解. 分层的体系结构 在开始组织关于因特网体系结构的想法之前,我们先看看一个人类社会与之类比的例子,实际上,在日常生活中我们一直都与复杂系统打交道. 想象一下有人请你描述比如航班系统的情况

  • 详解Jvm中时区设置方式

    这篇文章memo一下Jvm中关于时区设定的基础操作. Java的时区设定 这里列出如下三种方式 方式 说明 TimeZone.setDefault方式 通过java的utils下的TimeZone进行动态设定 user.timezone传递方式 运行时通过传递-Duser.timezone=xxx进行设定 TZ环境变量方式 通过export的TZ环境变量进行设定 TimeZone.setDefault方式 Sample代码如下: sh-4.2# cat TestTimeZone.java imp

  • 详解Java中的线程模型与线程调度

    JAVA线程模型 线程的实现主要有3种方式: 使用内核线程实现(1:1) 使用用户线程实现(1:N) 使用用户线程加轻量级进程实现(N:M) 使用内核线程实现(Kernel-Level Thread, KLT)(1:1) 内核线程就是直接由操作系统内核支持的线程,这种线程由内核来完成线程的切换,内核通过操作调度器对线程进行调度,并负责将线程的任务映射到各个处理器上. 程序一般不会直接去使用内核,而是去使用线程的一种高级接口--轻量级进程(Light Weight Process,LWP),轻量级

  • 详解jvm对象的创建和分配

    对象的创建 创建方式 1. new 关键字直接创建. new ObjectName(). 2.通过 Class 反射对象的 newInstance() 方法.ObjectName  obj  =  ObjectName.class.newInstance(). 3.通过 Class 反射对象获取 Constructor 类,再调用其 newInstance() 方法. ObjectName obj = ObjectName.class.getConstructor.newInstance().

随机推荐