当前位置:首页 > Java技术 > JVM 内存模型及垃圾回收

JVM 内存模型及垃圾回收

2022年08月04日 19:00:44Java技术4

java内存模型

根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。

JVM 内存模型及垃圾回收 _ JavaClub全栈架构师技术笔记

  • 程序计数器:程序计数器是指CPU中的寄存器,它保存的是程序当前执行的指令的地址(也可以说保存下一条指令的所在存储单元的地址),当CPU需要执行指令时,需要从程序计数器中得到当前需要执行的指令所在存储单元的地址,然后根据得到的地址获取到指令,在得到指令之后,程序计数器便自动加1或者根据转移指针得到下一条指令的地址,如此循环,直至执行完所有的指令;(注:JVM中的程序计数器并不像汇编语言中的程序计数器一样是物理概念上的CPU寄存器,但是逻辑作用上是等同的,在JVM中多线程是通过线程轮流切换来获得CPU执行时间的,在任一具体时刻,一个CPU的内核只会执行一条线程中的指令,为了能够使得每个线程都在线程切换后能够恢复在切换之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相被干扰,否则就会影响到程序的正常执行次序。因此,可以这么说,程序计数器是每个线程所私有的)
  • Java栈:Java栈是Java方法执行的内存模型,Java栈中存放的是一个个的栈帧,每个栈帧(包括:局部变量表、操作数栈、运行时常量池(在下文中提到的方法区内)的引用、方法返回地址和一些额外的附加信息)对应一个被调用的方法,当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈;(注:由于每个线程正在执行的方法可能不同,因此每个线程都会有一个自己的Java栈,互不干扰)
  • 本地方法栈:Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的;
  • 堆:Java中的堆是用来存储对象本身的以及数组;
  • 方法区:它与堆一样,是被线程共享的区域,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。(注:在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。)

java堆的内存模型

java 中的堆是 JVM所管理的最大的一块内存空间,主要用于存放各种类的实例对象。

在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。

新生代 ( Young )又被划分为三个区域:

  • Eden
  • From Survivor
  • To Survivor

这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。

堆的内存模型大致为:

     JVM 内存模型及垃圾回收 _ JavaClub全栈架构师技术笔记JVM 内存模型及垃圾回收 _ JavaClub全栈架构师技术笔记

从图中可以看出: 堆大小 =新生代 + 老年代。其中,堆的大小可以通过参数 –Xms、-Xmx 来指定。

默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 (该值可以通过参数 –XX:NewRatio 来指定),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。其中,新生代 ( Young )被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分。

默认的,Edem : from : to = 8 :1 : 1 (可以通过参数–XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。

JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块Survivor区域是空闲着的。因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。

分代垃圾回收

JVM 内存模型及垃圾回收 _ JavaClub全栈架构师技术笔记

1.年轻代(Young Generation):

所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个)(YGC,年轻代垃圾回收),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。

2.年老代(Old Generation):

在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

3.持久代(Permanent Generation):

用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=<N>进行设置。

Java 中的堆也是 GC收集垃圾的主要区域。

由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。

GC 分为两种:

  • Minor GC
  • Full GC ( 或称为 Major GC )

Minor GC发生在新生代中的垃圾收集动作,所采用的是复制算法。新生代几乎是所有 Java 对象出生的地方,即 Java 对象申请的内存以及存放都是在这个地方。Java 中的大部

分对象通常不需长久存活,具有朝生夕灭的性质。当一个对象被判定为 "死亡" 的时候,GC 就有责任来回收掉这部分对象的内存空间。新生代是 GC 收集垃圾的频繁区域。

当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,在经过一次 Minor GC后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳(上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域 ( 即 to 区域 ) 中,然后清理所使用过的 Eden以及 Survivor 区域 ( 即from 区域 ),并且将这些对象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,可以通过参数 -XX:MaxTenuringThreshold 来设定),这些对象就会成为老年代。但这也不是一定的,对于一些较大的对象 (即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代

Full GC 是发生在老年代的垃圾收集动作,所采用的是标记-清除算法

现实的生活中,老年代的人通常会比新生代的人"早死"。堆内存中的老年代(Old)不同于这个,老年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 "死掉" 了的。因此,Full GC发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长。

另外,标记-清除算法收集垃圾的时候会产生许多的内存碎片 (即不连续的内存空间 ),此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作。

有如下原因可能导致Full GC:

  • 年老代(Tenured)被写满
  • 持久代(Perm)被写满
  • System.gc()被显示调用
  • 上一次GC之后Heap的各域分配策略动态变化

GC 日志实例        

    publicstaticvoid main(String[] args) {   

         Object obj= new Object();  

         System.gc();   

         System.out.println();   

         obj= new Object();  

         obj=new Object();   

         System.gc();   

         System.out.println();

   }

JVM 内存模型及垃圾回收 _ JavaClub全栈架构师技术笔记

设置 JVM 参数为 -XX:+PrintGCDetails,使得控制台能够显示 GC 相关的日志信息,执行上面代码,下面是其中一次执行的结果。Full GC 信息与 Minor GC的信息是相似的,这里就不一个一个的画出来了。从 Full GC 信息可知,新生代可用的内存大小约为 18M,则新生代实际分配得到的内存空间约为 20M(为什么是20M? 请继续看下面...)。老年代分得的内存大小约为 42M,堆的可用内存的大小约为 60M。

可以计算出: 18432K( 新生代可用空间 ) + 42112K ( 老年代空间 ) = 60544K ( 堆的可用空间 )

新生代约占堆大小的 1/3,老年代约占堆大小的 2/3。也可以看出,GC 对新生代的回收比较乐观,而对老年代以及方法区的回收并不明显或者说不及新生代。并且在这里 Full GC 耗时是 Minor GC 的 22.89 倍。

参数调优

针对分代垃圾回收调整部分参数

JVM 内存模型及垃圾回收 _ JavaClub全栈架构师技术笔记

 

JVM内存的系统级的调优主要的目的是减少Minor GC的频率和Full GC的次数,过多的Minor GC和Full GC是会占用很多的系统资源,影响系统的吞吐量。

1.  年轻代分三个区,一个Eden区,两个Survivor区(from和to区),可以通过-XXSurvivorRatio调整比例

  • 作用:默认-XX:SurvivorRatio=8,表示Survivor区与Eden区的大小比值是1:1:8,在MinorGC过程,如果survivor空间不够大,不能够存储所有的从eden空间和from suvivor空间复制过来活动对象,溢出的对象会被复制到old代,溢出迁移到old代,会导致old代的空间快速增长

2.  大部分对象在先在Eden区中申请内存。

  • 作用:可以通过设置-XX:PreTenureSizeThreShold大小,令大于这个值的对象直接保存到年老代,避免在Eden区与Survivor区之间频繁地通过复制算法回收内存

3.  当Eden区满时,无法为新的对象分配内存时,会进行Minor GC对其回收无用对象占用的内存,如果还有存活对象,则将存活的对象复制到Survivor From区(两个中Survivor对称);然后从Eden区存活下来的对象,就会被复制到From,当这个From区满时,此区的存活对象将被复制到To区,接下来Eden区存活下来的对象就会被复制到To区,经历一定的次数Minor GC后,还存活的对象,将被复制“年老区(Tenured)”。

  • 作用:Minor默认15次,可通过-MaxTenuringThreshold参数调整年轻代回收次数,防止对象过早进入年老代,降低年老代溢出的可能性

4.  年轻代和年老代的默认比例为1:2,即年轻代占堆内存的1/3,年老代占2/3,可调整-XX:NewRatio的大小设置年轻和年老的比例。

  • 作用:默认-XX:NewRatio=2,即young:tenured=1:2,适当调整年轻代大小,可以一定层度上较少Full GC出现的概率

其余性能调优常用参数设置

  1. -Xms and -Xmx (or: -XX:InitialHeapSize and -XX:MaxHeapSize):指定JVM的初始和最大堆内存大小,两值可以设置相同,以避免每次垃圾回收完成后JVM重新分配内存。
  2. -Xmn:设置年轻代大小。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
  3. -Xss:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
  4. -XX:+HeapDumpOnOutOfMemoryError and -XX:HeapDumpPath:让JVM在发生内存溢出时自动的生成堆内存快照(堆内存快照文件有可能很庞大,推荐将堆内存快照生成路径指定到一个拥有足够磁盘空间的地方。)
  5. -XX:OnOutOfMemoryError:当内存溢发生时,我们甚至可以可以执行一些指令,比如发个E-mail通知管理员或者执行一些清理工作($ java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof -XX:OnOutOfMemoryError ="sh ~/cleanup.sh" MyApp)
  6. -XX:PermSize and -XX:MaxPermSize:设置永久代大小的初始值和最大值(默认:最小值为物理内存的1/64,最大值为物理内存的1/16,永久代在堆内存中是一块独立的区域,这里设置的永久代大小并不会被包括在使用参数-XX:MaxHeapSize 设置的堆内存大小中)
  7. -XX:PretenureSizeThreshold :令大于这个设置值的对象直接在老年代分配。这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存复制

JVM 参数选项

下面只列举其中的几个常用和容易掌握的配置选项

 

-Xms

初始堆大小。如:-Xms256m

-Xmx

最大堆大小。如:-Xmx512m

-Xmn

新生代大小。通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor,即 90% 

-Xss

JDK1.5+ 每个线程堆栈大小为 1M,一般来说如果栈不是很深的话, 1M 是绝对够用了的。

-XX:NewRatio

新生代与老年代的比例,如 –XX:NewRatio=2,则新生代占整个堆空间的1/3,老年代占2/3

-XX:SurvivorRatio

新生代中 Eden 与 Survivor 的比值。默认值为 8。即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10 

-XX:PermSize

永久代(方法区)的初始大小

-XX:MaxPermSize

永久代(方法区)的最大值

-XX:+PrintGCDetails

打印 GC 信息

-XX:+HeapDumpOnOutOfMemoryError

让虚拟机在发生内存溢出时 Dump 出当前的内存堆转储快照,以便分析用

 

参考:

https://www.cnblogs.com/junwangzhe/p/6282550.html

https://www.cnblogs.com/handsomeye/p/5442879.html

作者:吊车尾88
来源链接:https://www.cnblogs.com/yyy-blog/p/6930345.html

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。


本文链接:https://www.javaclub.cn/java/17410.html

分享给朋友:

“JVM 内存模型及垃圾回收” 的相关文章

性能优化|一张图带你了解JVM是如何进行内存分配

性能优化|一张图带你了解JVM是如何进行内存分配

对象优先在新生代分配 如果对象能够在eden区分配,那么就直接在eden区分配 如果eden区域大小不够新对象存储的话,则会触发一次minor gc, 如果minor gc后发现对象也无法在s区分配,则会直接在老年代中分配...

IDEA启动失败,报Failed to create JVM错误的解决办法

IDEA启动失败,报Failed to create JVM错误的解决办法

IDEA启动失败,报Failed to create JVM错误的解决办法 从网上下载的破解版IDEA,照网上的pojie方法进行破解时,很多人是用Windows自带的记事本添加并保存的。但是用记事本保存的文件是带有BOM的,从而导致启动IDEA报...

[JVM教程与调优] 为什么要学习JVM虚拟机?

[JVM教程与调优] 为什么要学习JVM虚拟机?

JVM在我们开发阶段不会用到,但是到了生产环境中,那么就会变得非常重要了。 为什么这么说呢? 一方面,因为我们的生产环境是比较复杂的。各种可能的问题都会出现,比如说:硬盘坏了、网络坏了、CPU利用率高了等问题层次不穷。 另外一方面,在我们生产环境出现问题,还不好进行定位。因为没...

JVM 从入门到精通(二)JVM和Java体系结构

JVM 从入门到精通(二)JVM和Java体系结构

写在前面:我是「云祁」,一枚热爱技术、会写诗的大数据开发猿。昵称来源于王安石诗中一句 [ 云之祁祁,或雨于渊 ] ,甚是喜欢。 写博客一方面是对自己学习的一点点总结及记录,另一方面则是希望能够帮助更多对大数据感兴趣的朋友。如果你也对 数据中台...

JVM中有哪些垃圾收集器?

写在前面 本文隶属于专栏《100个问题搞定Java虚拟机》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和文献引用请见100个问题搞定Java虚拟机 解答 新生代垃圾...

深入理解JVM(1)——JVM内存模型

Java虚拟机的内存空间分为五个部分,分别是: 程序计数器; Java虚拟机栈 本地方法栈 堆 方法区 接下来对这五部分分别进行详细的介绍 1、程序计数器:   a)什么是程序计数器:程序计数器是内存中的一个很小...

JVM——JVM大厂面试问题与解答

JVM——JVM大厂面试问题与解答

摘要 本博文主要分享在JVM中的大厂面试问题。总结相关面试问题与yu解答。帮助大家更好的学习与理解JVM的原理。同时也是分享一些有关于JVM实战的经验,帮助大家在工作中排查错误。 一、JVM的内存模型 1.1 JVM1.8内存模型 1.2 JVM内存模型中...

jvm性能调优实战 - 31从测试到上线

jvm性能调优实战 - 31从测试到上线

文章目录 Pre 开发好系统之后的预估性优化 系统压测时的JVM优化 对线上系统进行JVM监控 Pre 前面两篇文章,已经给大家介绍...

【Java】JVM内存模型解析

【Java】JVM内存模型解析

   JVM内存模型主要分为五大区域:栈、堆、本地方法栈、程序计数器、方法区。   本地方法栈: 跟虚拟机栈非常相似,也是线程私有的,不过虚拟机栈是针对Java方法,而本地方法栈是针对native方法,也就是底层方...

jvm-java 内存模型  以及各个分区具体内容

jvm-java 内存模型 以及各个分区具体内容

java内存模型,这里其实是指 jvm运行时内存模型 1 每一个应用程序,都有一个JVM 而不是 多个应用程序,共享一个jvm 2 java源文件,首先通过编译器,把java语法的代码,编译成 jvm语法的字节码文件 这个过程,是不涉及到...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。