专栏首页生活不止眼前的代码基于JDK8的JVM内存模型详解与GC策略

基于JDK8的JVM内存模型详解与GC策略

JVM内存模型总览

首先看一下JVM内存模型图

  • 程序计数器Program Counter Register 程序计数器是一块较小的内存区,可以看做是当前线程所执行的字节码的行号指示器,如果线程正在执行一个JAVA方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是NATIVE方法,这个计数器值为空(Undefined),此内存区域是唯一一个在JAVA虚拟机规范中没有规定任何OutOfMemoryError的区域 注:这里有问题是计数器值为空,程序怎么往下执行 参考C++理解是:当线程中调用native方法的时候,则重新启动一个新的线程,那么新的线程的计数器为空则不会影响当前线程的计数器,相互独立。
  • 虚拟机栈VM Stack 描述的是JAVA方法执行的内存模型,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法接口等信息 局部变量表存储了编译期可知的各种基本数据类型(boolean, byte, char, short, int, float, double, long), 对象引用(reference类型和returnAddress类型(指向一条字节码指令的地址) 线程请求的栈深度不够会报StackOverflowError异常 栈动态扩展的容量不够会报OutOfMemoryError异常
  • 本地方法栈Native Stack 本地方法栈类似于虚拟机栈,只不过本地方法栈使用的是本地方法
  • 堆Heap 几乎所有的对象实例都在堆上分配内存, 图示关于堆的结构
  1. JAVA对象优先在Eden区分配,当Eden区没有足够的空间时触发一次Minor GC ,触发Minor GC时,Eden和from区中的存活对象会被复制到to区,然后from和to交换指针,以保证下次Minor GC时,to区还是空的,如果survival区无法容纳的对象将通过分配担保机制直接进入老年区
  2. 分配担保机制可以通过HandlePromotionFailure配置,如果不允许的话,则直接发生FULL GC
  3. 新生代(Young Generation)的最大大小将根据总堆的最大大小和NewRatio参数的值来计算。参数的“不受限制”默认值MaxNewSize意味着计算值不受限制,MaxNewSize除非MaxNewSize在命令行中指定了值
  4. 一般情况下,不允许-XX:Newratio值小于1,即Old要比Young大
  5. 大对象直接进入老年区的判断是根据PretenureSizeThreshold设置的阈值,所谓大对象时指需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组(笔者列出的例子中的byte[]数组就是典型的大对象)
  6. 发生full GC的条件是:
(1)调用System.gc时,系统建议执行Full GC,但是不必然执行
(2)老年代空间不足
(3)方法去空间不足
(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
  1. 对象存活判断
- 引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题  
- 可达性分析:从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象  
  1. GC Roots对象包括
> 虚拟机栈(栈帧中的本地变量表)中引用的对象  
> 方法区中类静态属性引用的对象
> 方法区中常量引用的对象  
> 本地方法栈中JNI(即一般说的Native方法)引用的对象
> 已启动且未停止的java线程
  1. TLAB(Thread Local Allocation Buffer),即线程本地分配缓存区,这是一个线程专用的内存分配区域,可以使用参数 -XX:+UseTLAB,默认开启,这个是用于解决多线程竞争堆内存分配问题,核心原理是每个线程可以向JAVA虚拟机申请一段连续的内存,作为线程私有的TLAB,这个操作需要加锁

参数

默认值

作用

MinHeapFreeRatio

40

GC后,如果发现空闲堆内存小于整个预估堆内存的40%,则放大堆内存的预估最大值,但不超过固定最大值

MaxHeapFreeRatio

70

GC后,如果发现空闲堆内存占到整个预估堆内存的70%,则收缩堆内存预估最大值

Xms

物理内存的1/64(<1GB)

初始堆大小

Xmx

物理内存的1/4(<1GB)

最大堆大小

NewRatio

2

年轻代(包括Eden和两个Survivor区)与年老代的比值

NewSize

1310M

设置年轻代大小

MaxNewSize

不限

设置年轻代大小最大值

SurvivorRatio

8

Eden区与Survivor区的大小比值

MaxTenuringThreshold

15

垃圾最大年龄

PretenureSizeThreshold

0

超过这个值直接在old区分配,默认值是0,意思是不管多大都是先在eden中分配

  • 方法区Method Area JAVA虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个Non-Heap的别名,用于存储已被虚拟机加载的类信息,常亮,静态变量, 即时编译器编译后的代码等数据

在JDK 8中,永久代被删除,类元数据在本机内存中分配。默认情况下,可用于类元数据的本机内存量是无限制的。使用该选项MaxMetaspaceSize可以为用于类元数据的本机内存量设置上限。

metaspace其实由两大部分组成

Klass Metaspace就是用来存klass的,klass是我们熟知的class文件在jvm里的运行时数据结构,不过有点要提的是我们看到的类似A.class其实是存在heap里的,是java.lang.Class的一个对象实例。这块内存是紧接着Heap的,和我们之前的perm一样,这块内存大小可通过-XX:CompressedClassSpaceSize参数来控制,这个参数前面提到了默认是1G,但是这块内存也可以没有,假如没有开启压缩指针就不会有这块内存,这种情况下klass都会存在NoKlass Metaspace里,另外如果我们把-Xmx设置大于32G的话,其实也是没有这块内存的,因为会这么大内存会关闭压缩指针开关。还有就是这块内存最多只会存在一块。

NoKlass Metaspace专门来存klass相关的其他的内容,比如method,constantPool等,这块内存是由多块内存组合起来的,所以可以认为是不连续的内存块组成的。这块内存是必须的,虽然叫做NoKlass Metaspace,但是也其实可以存klass的内容,上面已经提到了对应场景。

Klass Metaspace和NoKlass Mestaspace都是所有classloader共享的,所以类加载器们要分配内存,但是每个类加载器都有一个SpaceManager,来管理属于这个类加载的内存小块。如果Klass Metaspace用完了,那就会OOM了,不过一般情况下不会,NoKlass Mestaspace是由一块块内存慢慢组合起来的,在没有达到限制条件的情况下,会不断加长这条链,让它可以持续工作。

元空间的特点

- 充分利用了Java语言规范中的好处:类及相关的元数据的生命周期与类加载器的一致。
- 每个加载器有专门的存储空间
- 只进行线性分配
- 不会单独回收某个类
- 省掉了GC扫描及压缩的时间
- 元空间里的对象的位置是固定的
- 如果GC发现某个类加载器不再存活了,会把相关的空间整个回收掉

元空间的内存分配模型

- 绝大多数的类元数据的空间都从本地内存中分配
- 用来描述类元数据的类(klasses)也被删除了
- 分元数据分配了多个虚拟内存空间
- 给每个类加载器分配一个内存块的列表。块的大小取决于类加载器的类型; sun/反射/代理对应的类加载器的块会小一些
- 归还内存块,释放内存块列表
- 一旦元空间的数据被清空了,虚拟内存的空间会被回收掉
- 减少碎片的策略
  • 运行时常量池Runtime Constant Pool 运行时常量池时方法区中的一部分,用于存放编译期生成的各种字面量和符号引用,并不是只有编译期才能产生常量,运行期间也有可能将新的常量放入常量池,因此也会有可能抛出OutOfMemoryError异常 常见的有字符串常量池

参考: [1] 深入理解Java虚拟机第二版

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • SpringDataJPA笔记(6)-value注解的使用

    yingzi_code
  • RabbitMQ笔记(六)-BlockedListener

    这个监听器在服务器内存报警或者硬盘报警的情况下,使用发送接口会产生阻塞,如果发送和接受等使用同一个连接,则会都阻塞,因此我们应该为消费者和生产者使用不同的Cac...

    yingzi_code
  • SpringDataJPA笔记(15)--Inheritance注解详解之JOINED

    SpringDataJPA笔记(15)–Inheritance注解详解之JOINED

    yingzi_code
  • Java中堆的内存泄漏和内存溢出 及问题解决 参数设置

    1、内存泄漏:严格来说,只有对象不会再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄漏,由于这些对象不能被垃圾收集掉,这样的对象多了,有可能就会OOM。

    一个长不胖的程序Yuan
  • 2020-12-28:java中,生产环境服务器变慢,如何诊断处理?

    使用 top 指令,服务器中 CPU 和 内存的使用情况,-H 可以按 CPU 使用率降序,-M 内存使用率降序。排除其他进程占用过高的硬件资源,对 Java ...

    福大大架构师每日一题
  • 【MIG专项测试组】腾讯手机管家实战分析:内存突增是为神马?

    应用版本升级后使用内存突增?如何跟踪?这次MIG专项测试组为大家分享内存问题跟踪实战过程! MIG专...

    腾讯Bugly
  • 深入理解java虚拟机-第二章:java内存区域与内存泄露异常

    java将内存的管理(主要是回收工作),交由jvm管理,确实很省事,但是一点jvm因内存出现问题,排查起来将会很困难,为了能够成为独当一面的大牛呢,自然要了解...

    用户1134788
  • 一次完整的JVM堆外内存泄漏故障排查记录

    记录一次线上JVM堆外内存泄漏问题的排查过程与思路,其中夹带一些JVM内存分配机制以及常用的JVM问题排查指令和工具分享,希望对大家有所帮助。

    后端技术漫谈
  • Android性能优化(三)之内存管理

    在Android的性能优化的各个部分里,内存的问题绝对是最令人头疼的一部分,虽然Android有垃圾自动回收机制不需要手动干预,但也恰因为此,出现内存问题如内存...

    用户2898788
  • 一次完整的JVM堆外内存泄漏故障排查记录

    记录一次线上JVM堆外内存泄漏问题的排查过程与思路,其中夹带一些「JVM内存分配的原理分析」以及「常用的JVM问题排查手段和工具分享」,希望对大家有所帮助。

    Rude3Knife的公众号

扫码关注云+社区

领取腾讯云代金券