一、深入理解JVM的内存结构及GC机制

1、JVM把内存分成如下区域:

(1)方法区(Method Area)

(2)堆区(Heap)

(3)虚拟机栈(VM Stack)

(4)本地方法栈(Native Method Stack)

(5)程序计数器(Program Counter Register)

其中的方法区和堆区,线程共享。

1.1 方法区(Method Area)

方法区存放了要加载的类的信息(如雷鸣、修饰符等)、静态变量、构造函数、final定义的常量、类中的字段和方法等信息。方法区是全局共享的、在一定条件下一会被GC。当方法区超过他允许的大小时,就会抛出OutOfMemory:PermGen Space异常。

在Hotspot虚拟机中,这块区域对应持久代(permanment Generation),一般来说,方法区上执行GC的情况很少,这是方法区被称为持久代的原因之一,但这不代表方法区上完全没有GC,其上的GC主要针对常量池的回收和已加载类的卸载。在方法区上GC,条件相当苛刻而且困难。

运行时常量池(Runtime Constant Pool)是方法去的一部分,用于存储编译器生成的常量和引用。一般来说,常量的分配在编译时就能确定,但也不完全是,也可以存储在运行时期产生的常量。比如String类的intern()方法,作用是String类维护了一个常量池,如果调用的字符“hello”已经在常量池中,则直接返回常量池中的地址,否则新建一个常量加入池中,并返回地址。

1.2 堆区(Heap)

堆区是GC最频繁的,也是理解GC机制最重要的区域。堆区由所有线程共享,在虚拟机启动时创建。堆区主要用于存放对象实例和数组,所有new出来的对象都存储在该区域。

1.3 虚拟机栈(VM Stack)

虚拟机栈占用的是操作系统内存,每个线程对应一个虚拟机栈,他是线程私有的,生命周期和线程一样,每个方法被执行时产生一个栈帧(Stack Frame),栈帧用于存储局部变量表、动态链接、操作数和方法出口等信息,当方法被调用时,栈帧入栈,当方法调用结束时,栈帧出栈。

局部变量表中存储着方法相关的局部变量,包括各种基本数据类型及对象的引用地址等,因此他有个特点:内存空间可以在编译期间就确定,运行时不再改变。

虚拟机栈定义了两种异常类型:StackOverFlowError(栈溢出)和OutOfMemoryError(内存溢出)。如果线程调用的栈深度大于虚拟机允许的最大深度,则抛出StackOverFlowError;不过大多数虚拟机都允许动态扩展虚拟机栈的大小,所以线程可以一直申请栈,直到内存不足时,抛出OutOfMemoryError。

1.4 本地方法栈(Native Method Stack)

本地方法栈用于支持native方法的执行,存储了每个native方法的执行状态。本地方法栈和虚拟机栈他们的运行机制一致,唯一的区别是,虚拟机栈执行Java方法,本地方法栈执行native方法。在很多虚拟机中(如Sun的JDK默认的HotSpot虚拟机),会将虚拟机栈和本地方法栈一起使用。

1.5 程序计数器(Program Counter Register)

程序计数器是一个很小的内存区域,不在RAM上,而是直接划分在CPU上,程序猿无法操作它,它的作用是:JVM在解释字节码(.class)文件时,存储当前线程执行的字节码行号,只是一种概念模型,各种JVM所采用的方式不一样。字节码解释器工作时,就是通过改变程序计数器的值来取下一条要执行的指令,分支、循环、跳转等基础功能都是依赖此技术区完成的。

每个程序计数器只能记录一个线程的行号,因此它是线程私有的。

如果程序当前正在执行的是一个java方法,则程序计数器记录的是正在执行的虚拟机字节码指令地址,如果执行的是native方法,则计数器的值为空,此内存区是唯一不会抛出OutOfMemoryError的区域。

2、GC机制

在上面介绍的五个内存区域中,有3个是不需要进行垃圾回收的:本地方法栈、程序计数器、虚拟机栈。因为他们的生命周期是和线程同步的,随着线程的销毁,他们占用的内存会自动释放。所以,只有方法区和堆区需要进行垃圾回收,回收的对象就是那些不存在任何引用的对象。

2.1查找算法

经典的引用计数算法,每个对象添加到引用计数器,每被引用一次,计数器+1,失去引用,计数器-1,当计数器在一段时间内为0时,即认为该对象可以被回收了。但是这个算法有个明显的缺陷:当两个对象相互引用,但是二者都已经没有作用时,理应把它们都回收,但是由于它们相互引用,不符合垃圾回收的条件,所以就导致无法处理掉这一块内存区域。因此,Sun的JVM并没有采用这种算法,而是采用一个叫——根搜索算法,如图:

基本思想是:从一个叫GC Roots的根节点出发,向下搜索,如果一个对象不能达到GC Roots的时候,说明该对象不再被引用,可以被回收。如上图中的Object5、Object6、Object7,虽然它们三个依然相互引用,但是它们其实已经没有作用了,这样就解决了引用计数算法的缺陷。

补充概念,在JDK1.2之后引入了四个概念:强引用、软引用、弱引用、虚引用。

强引用:new出来的对象都是强引用,GC无论如何都不会回收,即使抛出OOM异常。

软引用:只有当JVM内存不足时才会被回收。

 弱引用:只要GC,就会立马回收,不管内存是否充足。

虚引用:可以忽略不计,JVM完全不会在乎虚引用,你可以理解为它是来凑数的,凑够”四大天王”。它唯一的作用就是做一些跟踪记录,辅助finalize函数的使用。

最后总结,什么样的类需要被回收:

①.该类的所有实例都已经被回收;

②.加载该类的ClassLoad已经被回收;

③.该类对应的反射类java.lang.Class对象没有被任何地方引用。

2.2 内存分区

内存主要被分为三块:新生代(Youn Generation)、旧生代(Old Generation)、持久代(Permanent Generation)。三代的特点不同,导致他们使用的GC算法不同,新生代适合生命周期短,快速创建和销毁的对象,旧生代适合生命周期较长的对象,持久代在Sun Hotspot虚拟机中指方法区(有些JVM根本没有持久代这一说法)。

新生代(Youn Generation):大致分为Eden区和Survivor区,Survivor区又分为大小相同的两部分:FromSpace和ToSpace。新建的对象都是从新生代分配内存,Eden区不足的时候,会把存活的对象转移到Survivor区。当新生代进行垃圾回收时会出发Minor GC(也称作Youn GC)。

旧生代(Old Generation):旧生代用于存放新生代多次回收依然存活的对象,如缓存对象。当旧生代满了的时候就需要对旧生代进行回收,旧生代的垃圾回收称作Major GC(也称作Full GC)。

 持久代(Permanent Generation):在Sun 的JVM中就是方法区的意思,尽管大多数JVM没有这一代。

2.3 GC算法

常见的GC算法:复制、标记-清除、标记-压缩、分代收集算法

复制:复制算法采用的方式为从根集合进行扫描,将存活的对象移动到一块空闲的区域,如图所示:

当存活的对象较少时,复制算法会比较高效(新生代的Eden区就是采用这种算法),其带来的成本是需要一块额外的空闲空间和对象的移动。

标记-清除:该算法采用的方式是从跟集合开始扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,并进行清除。标记和清除的过程如下:

上图中蓝色部分是有被引用的对象,褐色部分是没有被引用的对象。在Marking阶段,需要进行全盘扫描,这个过程是比较耗时的。

清除阶段清理的是没有被引用的对象,存活的对象被保留。

标记-清除动作不需要移动对象,且仅对不存活的对象进行清理,在空间中存活对象较多的时候,效率较高,但由于只是清除,没有重新整理,因此会造成内存碎片。

标记-压缩:该算法与标记-清除算法类似,都是先对存活的对象进行标记,但是在清除后会把活的对象向左端空闲空间移动,然后再更新其引用对象的指针,如下图所示:

由于进行了移动规整动作,该算法避免了标记-清除的碎片问题,但由于需要进行移动,因此成本也增加了。(该算法适用于旧生代)

分代收集算法:当前商业虚拟机都采用这种算法。首先根据对象存活周期的不同将内存分为几块即新生代、老年代,然后根据不同年代的特点,采用不同的收集算法。在新生代中,每次垃圾收集时都有大量对象死去,只有少量存活,所以选择了复制算法。而老年代中因为对象存活率比较高,所以采用标记-整理算法(或者标记-清除算法)。

Minor GC

  一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Minor GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。

Full GC

  对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Minor GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:

  1.年老代(Tenured)被写满;

  2.持久代(Perm)被写满;

  3.System.gc()被显示调用;

4.上一次GC之后Heap的各域分配策略动态变化。

Java常见的内存泄漏

  1. 数据库连接,网络连接,IO连接等没有显示调用close关闭,会导致内存泄露;
  2. 监听器的使用,在释放对象的同时没有相应删除监听器的时候也可能导致内存泄露。

深入理解JVM的内存结构及GC机制相关推荐

  1. 浅谈 JVM 内存结构及 GC 机制

    前言 JAVA GC(Garbage Collection,垃圾回收)机制是区别C++的一个重要特征,C++需要开发者自己实现垃圾回收的逻辑,而JAVA开发者则只需要专注于业务开发,因为垃圾回收这件繁 ...

  2. JVM的内存结构,Eden和Survivor比例;JVM中一次完整的GC流程,对象如何晋升到老年代,说说你知道的几种主要的JVM参数;CMS 常见参数解析;.你知道哪几种垃圾收集器,各自的优缺点

    47.JVM的内存结构,Eden和Survivor比例 49.JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代,说说你知道的几种主要的JVM参数 50.-XX:+CMSScavengeBefo ...

  3. JVM之内存结构图文详解

    点击蓝色"程序猿DD"关注我 回复"资源"获取独家整理的学习资料! 对于开发人员来说,如果不了解Java的JVM,那真的是很难写得一手好代码,很难查得一手好bu ...

  4. JVM之内存结构详解

    对于开发人员来说,如果不了解Java的JVM,那真的是很难写得一手好代码,很难查得一手好bug.同时,JVM也是面试环节的中重灾区.今天开始,<JVM详解>系列开启,带大家深入了解JVM相 ...

  5. JVM(一)JVM虚拟机内存结构 和 JAVA内存模型(JMM)

    本文转自:浅析java内存模型--JMM(Java Memory Model) - 路易小七 - 博客园,尊重作者,转载请注明出处~ JVM虚拟机内存结构 和 JAVA内存模型 是两个不同的概念 JV ...

  6. 一文解析JVM的内存结构,身为程序员还不弄懂JVM怎么行

    欢迎关注专栏:Java架构技术进阶.里面有大量batj面试题集锦,还有各种技术分享,如有好文章也欢迎投稿哦.微信公众号:慕容千语的架构笔记.欢迎关注一起进步. 前言 Jvm的内存结构是由<jav ...

  7. JVM初探- 内存分配、GC原理与垃圾收集器

    JVM初探- 内存分配.GC原理与垃圾收集器 标签 : JVM JVM内存的分配与回收大致可分为如下4个步骤: 何时分配 -> 怎样分配 -> 何时回收 -> 怎样回收. 除了在概念 ...

  8. jvm虚拟机内存结构_JVM体系结构101:了解您的虚拟机

    jvm虚拟机内存结构 Java虚拟机(JVM)架构和Java字节码101的初学者速成班 Java应用程序无处不在,它们在我们的手机,平板电脑和计算机上. 在许多编程语言中,这意味着多次编译代码以使其在 ...

  9. Android内存泄露和GC机制

    Android内存泄露和GC机制 本文先对Android内存垃圾回收机制进行介绍,之后对分析.定位内存泄露常用的测试方法进行总结,分享给大家. 一.Android内存垃圾回收(GC机制) 1.综述 A ...

  10. 深入理解JVM(4)——如何优化Java GC「译」

    本文翻译自Sangmin Lee发表在Cubrid上的"Become a Java GC Expert"系列文章的第三篇<How to Tune Java Garbage C ...

最新文章

  1. Linux挂载卸载光盘实践
  2. 到了管理层我才发现,这些管理知识应该尽早学起来!
  3. linux rpm 查看安装目录下,Linux如何修改RPM的安装目录?
  4. 【Java进阶】Spring Boot集成ES
  5. python的类程序的结构_python(8)---程序结构
  6. webrender 查看是否开启_想要体验极致顺滑的网页加载体验?手动开启Firefox WebRender渲染引擎...
  7. Actor-ES框架:Ray
  8. 属性拼接转换成字符串_使用一些可选的将字符串配置属性转换为其他类型
  9. hbase 按时刻查询_Hbase查询工具类,根据时间查询数据
  10. html给box加超链接,java – 如何使用pdfbox在pdf中添加超链接
  11. java final一点
  12. 微软威胁情报中心总经理 John Lambert 谈安全
  13. SpringBoot2.0学习第四篇之拦截器过滤器配置
  14. 字符编码Unicode原理
  15. PISCES: A Programmable, Protocol-Independent Software Switch(总结)
  16. python|教你用代码画“社会人”
  17. Mysql的原子性、持久性原理
  18. Linux内核Notifier机制
  19. 已解决:Downloading https://ultralytics.com/assets/Arial.ttf to .......ubuntu18.4-YOLOv5报错[01]
  20. Nature子刊 | AI驱动的药物开发是一把双刃剑

热门文章

  1. torch.backends.cudnn.benchmark的用法
  2. oracle11g认证,ORACLE11g-OCP认证(甲骨文专家级数据库工程师)
  3. wordpress pdf_9个适用于WordPress的最佳PDF插件
  4. Uncaught Exception: Error [ERR REQUIRE ESM]: requireO of ES Modul……
  5. 开发一个简单的Mac应用
  6. Mac Android Studio Flutter环境配置之第一个Futter项目
  7. 发明专利授权:顺源科技模拟信号隔离放大及转换技术
  8. 红米手机开启开发者模式方法
  9. 30天自制操作系统第1天 - Hello World
  10. 那些不回你微信的人都在看什么?