history 分布式机器 tensorflow 私有云平台 volatile ios oop 网络营销推广 datepicker request sas formvalidator.js vue下载 vue版本 js获取月份 网络游戏server编程 linux管道符 kubernetes架构 python报错 python取随机数 python中def的用法 python学习文档 java包 java有哪些数据类型 java学习文档 java的for循环 java时间转换 java创建对象 vbs脚本 python封装 max电池容量 millenium 7个人 刷新页面 流程图工具 微信猜拳 方正兰亭粗黑字体下载 ug拔模 工程地质手册 正则表达式替换
当前位置: 首页 > 学习教程  > 编程语言

JVM内存模型及常见问题

2020/12/28 19:21:09 文章标签:

JVM内存模型及常见问题JVM内存模型堆内存模型展示对象创建过程常见问题及解答如何理解各种GC为什么需要survivor区?只有Eden不行吗?为什么需要两个S区?为什么Eden:s1:s2是8:1:1?堆内…

JVM内存模型及常见问题

      • JVM内存模型
        • 堆内存模型展示
        • 对象创建过程
      • 常见问题及解答
        • 如何理解各种GC
        • 为什么需要survivor区?只有Eden不行吗?
        • 为什么需要两个S区?
        • 为什么Eden:s1:s2是8:1:1?
        • 堆内存中都是线程共享区域吗?
        • TLAB工作流程
        • TLAB有什么缺点?
        • 为什么要分代

JVM内存模型

上一篇运行时数据区讲了整个运行时数据区的内容,但重点存储数据的是堆和方法区(非堆),所以内存设计重点应该朝这两方面展开。

可以这么理解,JVM运行时数据区是一种规范,而JVM内存模式是对该规范的实现

堆内存模型展示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bV5XEKBl-1609154204031)(.\图片\堆内存模型.PNG)]

特别一提,一般情况下按内存空间大小划分,Old、Eden、s0、s1=20:8:1:1。但实际上并不总是这样,具体还要视不同的垃圾收集器而定,比如JDK8出现的G1(garbage first)和JDK11出现的ZGC(zero garbage collector)这两款垃圾收集器就不是这样划分堆内存的,详情在后面的笔记中会特别提起这两款垃圾收集器。

另外一个值得一提的点是,实际上如果我们调用参数去讲堆内存的大小打印出来会发现old区的大小并不是刚好位young区的两倍,这是因为young区的大小是JVM计算出来的,但old区则是JVM用整个堆内存的大小减去young区大小获得的,假设young区计算出来的大小不为整数,那么计算时会去除后面的尾数。

对象创建过程

一般情况下,新创建的对象都会被分配到Eden区,一些特殊的大的对象会直接分配到old区

我是一个普通的Java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了 挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor 区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我15岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的。

在这里插入图片描述

常见问题及解答

如何理解各种GC

Partial GC

Partial其实也就是部分的意思.那么翻译过来也就是回收部分GC堆的模式,他并不会回收我们整个堆.而我们的young GC以及我们的Old GC都属于这种模式 ,还有个别垃圾收集器会回收整个young区和部分old区,这种情况叫mixed GC,也属于Partial GC情况中的一种

young GC 只回收young区 也叫minor GC

old GC 只回收Old区 也叫major GC,但很少会这样叫,因为很少情况会单独回收old区

full GC 实际上就是对于整体回收,非常耗时,但对于内存大的服务器即使进行youngGC的次数很多,但full GC的频率依旧很低。

对于hotspot虚拟机,full GC会连同matespace一起回收,是全局的回收。反过来讲,matespace空间不足会立刻引发full GC,这是一种悲观策略。

为什么需要survivor区?只有Eden不行吗?

如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。 这样一来,老年代很快被填满,触发Major GC(因为Major GC一般伴随着Minor GC,也可以看做触发了 Full GC)。

老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多。 执行时间长有什么坏处?频发的Full GC消耗的时间很长,会影响大型程序的执行和响应速度。

可能你会说,那就对老年代的空间进行增加或者较少咯。 假如增加老年代空间,更多存活对象才能填满老年代。虽然降低Full GC频率,但是随着老年代空间加大,一 旦发生Full GC,执行所需要的时间更长。

假如减少老年代空间,虽然Full GC所需时间减少,但是老年代很快被存活对象填满,Full GC频率增加。

所以Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。

特别一提,并不是一定要经过指定次数的GC(一般默认是15次,第16次GC送入old区)还存活的对象才会被送到老年代,特殊情况一:大对象Eden区分配不下,直接进入老年代 特殊情况二:young GC后存活下来的同一年龄的对象总占用内存空间很大,超过设置的某一阈值就会在某一次young GC后送入old区

特殊情况二的对象要进入old区至少要经过一次GC,而情况一的大对象会直接进入old区,年龄为0。

为什么需要两个S区?

最大的好处就是解决了碎片化。也就是说为什么一个Survivor区不行?上一个问题中,我们知道了必须设置 Survivor区。假设现在只有一个Survivor区,我们来模拟一下流程: 刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor 区。这样继续循环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,Eden和Survivor各有一些 存活对象,如果此时把Eden区的存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的, 也就导致了内存碎片化。

两个S区就可以保证永远有一个Survivor space是空的,另一个非空的Survivor space无碎片。

为什么Eden:s1:s2是8:1:1?

新生代中的可用内存:复制算法用来担保的内存为9:1

可用内存中Eden:S1区为8:1

即新生代中Eden:S1:S2 = 8:1:1

现代的商业虚拟机都采用这种收集算法来回收新生代,IBM公司的专门研究表明,新生代中的对象大概98%是 “朝生夕死”的

堆内存中都是线程共享区域吗?

JVM默认为每个线程在Eden上开辟一个buffer区域,用来加速对象的分配,称之为TLAB,全称:Thread Local Allocation Buffer。

如果设置了虚拟机参数 -xx:UseTLAB,在线程初始化时,同时也会申请一块指定大小的内存,只给当前线程使用,这样每个线程都单独拥有一个空间,如果需要分配内存,就在自己的空间上分配,这样就不存在竞争的情况,可以大大提升分配效率。对象优先会在TLAB上分配,但是TLAB空间通常会比较小,如果对象比较大,那么还是在共享区域分配。

TLAB的本质是三个指针管理的区域,start top 和end,其中start 和end是用来占位的,标识这块区域是TLAB管理的区域,卡住Eden一块空间不让其他线程来这里分配,当分配指针top 撞上分配极限 end时,就会新申请一块TLAB,被分配的对象无法感知自己是否是从TLAB中分配的,他们只关心自己是在Eden中分配的。

注意,这块区域仍然是线程共享,但只有该线程可以分配该区域

TLAB工作流程

在这里插入图片描述

TLAB有什么缺点?

事务总不是完美的,TLAB也又自己的缺点。因为TLAB通常很小,所以放不下大对象。
1,TLAB空间大小是固定的,但是这时候一个大对象,我TLAB剩余的空间已经容不下它了。(比如100kb的TLAB,来了个110KB的对象)
2,TLAB空间还剩一点点没有用到,有点舍不得。(比如100kb的TLAB,装了80KB,又来了个30KB的对象)
所以JVM开发人员做了以下处理,设置了最大浪费空间。
当剩余的空间小于最大浪费空间,那该TLAB属于的线程在重新向Eden区申请一个TLAB空间。进行对象创建,还是空间不够,那你这个对象太大了,去Eden区直接创建吧!
当剩余的空间大于最大浪费空间,那这个大对象请你直接去Eden区创建,我TLAB放不下没有使用完的空间。

当然,又会造成新的病垢。
3,Eden空间够的时候,你再次申请TLAB没问题,我不够了,Heap的Eden区要开始GC,
4,TLAB允许浪费空间,导致Eden区空间不连续,积少成多。以后还要人帮忙打理。

为什么要分代

我们进行垃圾回收,第一件事情是确定哪些对象是我们不需要的,

在不分代的情况下,每进行一次垃圾回收我们都需要遍历整个堆内存空间来确定哪些对象是我们不需要的。这一过程非常的慢,导致GC时间非常长。

有了分代之后,新创建的对象放在young区,经过多次GC仍然没有被回收的对象放在old区,潜台词是old区的对象基本不需要被回收,这部分的对象就不需要扫描确定是否要回收,如此需要被扫描的对象的数量就大大减少。这样做的另外一个依据是98%的对象朝生夕死。


本文链接: http://www.dtmao.cc/news_show_550212.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?