运转统算数据区的JVM广度预测
操作方式统算数据区T5450大统算数据夏令营2021新一代
download:https://www.zxit666.com/4631/
两个十进制而已储存有那儿的两个十进制文档。假如你想在jvm中运行,你要有两个运转的缓存自然环境。
这是他们所言的jvm运转时统算数据区。
1)运转时统算数据区的边线
运转时统算数据区是jvm最重要的部份,也是发动机时常操作方式的部份。类的调用,和他们前面要谈及的第一类内部空间的重新分配和废弃物拆解,都出现在那个地区。
2)地区分割
依照Java软件包的规范化,缓存有运转时统算数据区被行业龙头为两个部份。
缓存专有:Java软件包栈、流程计时器暂存器和邻近地区方式栈。
Common:方式区,Java堆。
接下去,他们详尽嘿嘿每一块是做甚么的,假如外溢会好不好。
1.1流程计时器
简述
流程算数暂存器(流程算数暂存器)
每一缓存两个。是两个小的缓存内部空间,代表当前缓存执行的十进制码指令的地址。
当十进制码解释器工作时,它通过改变那个计时器的值来选择下两个要执行的十进制码指令。因此,整个流程的基本功能,如分支、循环、跳转、异常处理、缓存恢复等。,需要依靠那个计时器来完成。
因为多缓存并行执行,指令互不相同,所以每一缓存都需要有独立的流程计时器。每一缓存的计时器互不影响,并且是独立储存的。他们称这种缓存地区为“缓存专有”缓存。
假如是本机方式,则为空。
外溢异常
不要!
在软件包规范化中,那个地区没有缓存外溢规范化,是唯一不会外溢的地区。
1.1.3案例
因为不会外溢,所以他们不能为它建两个,但是可以从class类中找到痕迹。
回头看上面javap的反汇编,代码对应的数字可以理解为计时器中记录的执行数。
1.2软件包栈
简述
也是缓存专有!生命周期与缓存相同。
它描述了由Java方式执行的当前缓存的缓存模型。每一方式执行时,Java软件包都会同步创建两个栈框架,用来储存局部变量表、操作方式数栈、动态连接、方式出口等信息。每一方式被调用直到被执行的过程对应于从栈到软件包栈中的栈帧的过程。
外溢异常
1)堆叠广度超过设定值。
假如创建的栈广度大于软件包允许的广度,则抛出。
缓存“main”Java . lang . stack overflow error中出现异常
2)缓存应用不足
假如栈允许缓存扩展,但缓存请求不足,则抛出OutOfMemoryError。
立正!这与具体的软件包有关。hotspot软件包不支持栈内部空间扩展。因此,在单缓存自然环境中,当两个缓存被创建时,它被重新分配两个固定大小的栈。在那个固定的栈内部空间中,不会出现需要扩展应用缓存的情况,也不会出现应用少于两个字的问题,而已因为广度问题超过了固定的内部空间,导致了上面的StackOverflowError。
假如切换到多缓存并无限制地创建缓存,仍可能导致OutOfMemoryError。但这与Xss栈内部空间无关。因为有太多的缓存和栈,系统重新分配给jvm进程的物理缓存被耗尽。
这时,软件包会伴随着相关的提示:
缓存“main”Java . lang . out of memory中出现异常错误:无法创建本机缓存
Ps:每一缓存默认重新分配1M内部空间(64位linux,hotspot自然环境)
问题:改变Xss的值有可能得到栈内部空间外溢吗?
答:依照上面的预测,假如在hotspot下做不到,还是会抛出两个StackOverflowError,而已广度更小了。
1.2.3情况1:进入和离开堆栈的顺序
1.3邻近地区方式栈
简述
邻近地区方式栈的功能和特性与软件包栈类似,都具有缓存隔离的特性。
区别在于邻近地区方式栈服务于JVM执行的原生方式,而软件包栈服务于JVM执行的java方式。
规范化中对那个块的语言和统算数据结构没有强制规定,软件包可以自由实现。
Hotspot甚至将其与软件包栈合并为两个。
外溢异常
与软件包栈一样,也有两种:
假如创建的栈的广度大于软件包允许的广度,则会引发StackOverFlowError。
应用流程不足时抛出OutOfMemoryError。
1.4堆
简述
和上面3个不一样,堆是所有缓存共享的!所谓的缓存安全也是从这里来的。
在软件包启动时创建。那个缓存地区的唯一目的是储存第一类实例。Java世界中几乎所有的第一类实例都在这里重新分配缓存。
需要注意的是,Java软件包规范化并没有对堆进行详尽的分割,所以对堆的解释要基于具体的软件包。他们以使用最多的热点软件包为例。
Java堆是废弃物收集器管理的缓存地区,所以也叫“GC堆”,是JVM调优的重点地区。
jdk1.7
jvm的缓存模型与1.7和1.8有很大不同。虽然目前1.7用的比较少,但是他们也需要了解一下1.7的缓存模型,所以接下去他们就先学习一下1.8之前1.7的缓存模型。
青年区(一代)
扬区被分为三个部份,伊甸园区和两个严格相同大小的幸存者区。
其中,在幸存者区间,某个时间只使用其中两个,另两个留作废弃物收集时复制第一类。
当Eden间隔变满时,GC会将幸存第一类移动到空闲的幸存者间隔。依照JVM的策略,经过几次废弃物拆解后,仍然生活在Survivor中的第一类将被移动到下面的Tenured interval中。
终身旧区
Tenured地区主要储存生命周期长的第一类,通常是一些老第一类。当一些第一类在Young中复制转移到一定次数后,这些第一类就会转移到Tenured地区。一般来说,假如系统中使用了应用级缓存,缓存中的第一类往往会被转移到那个地区。
烫发永久区
热点1.6只有那个产品,现在已经是历史了。
Perm生成主要储存类、方式、归档第一类,这部份内部空间一般不会外溢,除非一次加载很多类。但是说到热部署中的应用服务器,有时候会遇到java.lang.out of memory错误:perm genspace。那个错误的两个很大原因可能是每次都要重新部署。但是重新部署后,该类的类并没有卸载,导致perm中保存了大量的类第一类。在这种情况下,重启应用服务器通常可以解决问题。还有一种可能是创建了大量的jsp文档,导致类信息外溢超过了perm的上限。这种重启也解决不了。你只能增加内部空间。
虚拟地区:
jvm参数可以设置两个范围,最大缓存和初始缓存之差是虚拟地区。
jdk1.8
从上图可以看出,jdk1.8的缓存模型由两部份组成,年轻一代和老一代。生成被永久终止,并被元内部空间(元统算数据内部空间)所取代
年轻一代:伊甸园+2 *幸存者(不变)
老年:老年(不变)
元内部空间:原烫发区(重点!)
需要注意的是,Metaspace占用的缓存内部空间不在软件包,而是在邻近地区缓存内部空间,这是和1.7永久代最大的区别。
外溢异常
缓存不足时抛出。
Java . lang . out of memory error:Java堆内部空间
1.4.5情况:堆外溢
1)代码
在jvm指定的堆范围之外重新分配大量第一类。
1.5方式地区
简述
同样,由缓存共享。
主要用来储存类的信息,类中定义的常数,静态变量,编译器编译的代码缓存。
立正!方式地区是软件包规范化中的两个逻辑概念,对于它在该地区中的具体边线没有严格的规则。
于是,hotspot 1.7把它放在堆的永久生成中,1.8+开辟了两个单独的块叫metaspace来储存一些内容(不是全部!定义的类第一类在堆中)
具体方式区存放的主要是甚么东西?大致可分为两类:
类信息:主要指版本、字段、方式、接口描述、引用等。与班级有关。
运转时常量池:编译阶段生成的常量和符号引用和运转时添加的动态变量。
(常量池中的类变量,比如第一类或者字符串,比较特殊,1.6和1.8的边线不一样,下面会讨论)
小贴士:
它时常与上面那堆中的永久一代混淆。其实这是两码事。
永久生成是hotspot前后的设计,1.8+等虚拟机不存有。
可以说永久一代是1.7 hotspot偷懒的结果。他在堆里分了一块来实现方式区的功能,叫做永久代。这样就可以通过堆的废弃物拆解来管理方式区的缓存,而不是单独为方式区写两个缓存管理流程。懒!
其他当代的软件包,比如J9和Jrockit,都没有那个概念。后来hotspot意识到永久做不是个好主意。1.7已经从永久生成中拿走了一些统算数据,直到1.8+完全去掉了永久生成,大部份方式地区都移到了metaspace(还是那句话,不是全部!)
结论:
方式要存有,这是软件包规定的,但这是两个逻辑概念,软件包自己决定。
但是永久一代不一定存有(只在热点1.7),这已经成为历史。
外溢异常
1.6:out of memory错误:PermGen内部空间
1.8:out of memory错误:元内部空间
1.5.3案例:1.6方式区外溢
1)原则
在1.6中,字符串常量是运转时常量池的一部份,它属于方式地区,放在永久生成中。
因此,在1.6自然环境中,假如方式区外溢,只需要在字符串常量池中创建字符串。这里使用了一种方式:
/*
假如字符串常量池中有那个字符串,直接返回引用,不要再添加了。
假如没有,添加并返回新创建的引用。
*/
String.intern()
复制代码
2)代码
/**
*方式区外溢,注意限制永久生成的大小。
*编译时注意pom中的版本,设置为1.6,否则启动会有问题。
* JDK 1.6:-XX:PermSize = 6M-XX:MaxPermSize = 6M
*/
公共类常量{
公共静态void main(String[] args) {
ConstantOOM oom = new ConstantOOM();
set stringSet = new HashSet();
int I = 0;
while (true) {
system . out . println(++I);
stringSet.add(String.valueOf(i))。实习生());
}
}
}
复制代码
3)创建启动自然环境。
4)异常信息:
…
19118
19119
19120
缓存“main”Java . lang . out of memory中出现异常错误:PermGen space
at java.lang.String.intern(原生方式)
位于com . ithe IMA . JVM . demo . constant toom . main(constant toom . Java:19)
复制代码
1.5.4案例:1.8方式地区外溢
1)到了1.8,情况出现了变化。
可以测试,在1.8下,无论指定下面哪个参数,常量池操作方式都不会外溢,一直打印。
-XX:PermSize = 6M-XX:MaxPermSize = 6M
-XX:MetaspaceSize = 10M-XX:MaxMetaspaceSize = 10M
复制代码
2)配置运转自然环境。
3)控制台信息
不会抛出异常,只要你的jvm堆有足够的缓存,理论上可以一直敲下去。
4)为甚么?
为他们永久增加了限制,但结果毫无意义,因为1.8英里内没有这种商品。
元内部空间也受到限制,同样没有意义。这意味着字符串常量池不在元内部空间中!
那么,它在哪里?
jdk1.8之后,字符串常量池被移到堆内部空间,和其他第一类一样,由堆控制。
其他运转时类信息和基本统算数据类型在元内部空间中。
他们可以通过向上述运转时参数添加另两个堆上限来验证这一点:
-Xms10m
-Xmx10m
复制代码
操作方式自然环境如下:
运转时间不长,您会得到以下异常:
……
84014
84015
84016
84017
84018
84019
缓存“main”Java . lang . out of memory中出现异常错误:超出了GC开销限制
at Java . lang . integer . tostring(integer . Java:403)
位于Java . lang . string . value of(string . Java:3099)
位于com . ithe IMA . JVM . demo . constant toom . main(constant toom . Java:18)
复制代码
注意:在1.8中,字符串inter()被放在堆中,这受到最大堆内部空间的限制。
5)那如何才能让元内部空间外溢呢?
既然字符串常量池不在这里,那就换两个吧。班级的基本信息总是在元内部空间吗?让他们试一试。
Cglib是apache下的十进制码库,可以在运转时生成大量的第一类。让他们在限制元内部空间的同时尝试循环:
p . s . gitee.com/mirrors/cgl……(我想知道更多关于这个工具的slam向左,所以我不会在这里讨论太多)
包com . ithe IMA . JVM . demo;
导入net . SF . cglib . proxy . enhancer;
导入net . SF . cglib . proxy . method interceptor;
导入net . SF . cglib . proxy . method proxy;
导入Java . lang . reflect . method;
/**
* jdk8方式区外溢
*-XX:MetaspaceSize = 10M-XX:MaxMetaspaceSize = 10M
*/
公共类常量M8 {
公共静态void main(final String[] args) {
while (true) {
Enhancer enhancer =新增强子();
enhancer . set super class(oom . class);
enhancer . setuse cache(false);
enhancer . set callback(new method interceptor(){
@覆盖
公共第一类拦截(第一类o,方法Method,第一类[]第一类,方式代理方式代理)抛出Throwable {
返回method proxy . invoke super(objects,args);
}
});
增强器. create();
}
}
静态类OOM{
}
}
复制代码
6)运转设置
7)运转结果
原因:Java . lang . out of memory error:Metaspace
at Java . lang . class loader . define class 1(邻近地区方式)
位于Java . lang . class loader . define class(class loader . Java:763)
复制代码
结论:
jdk8引入元内部空间储存方式区后,缓存外溢的风险比版本历史小很多,但当类失控时,还是会爆发方式区。
1.6两个案例
为了你的理解和记忆,他们用两个案例来串一下上面的区。
假设有两个Bootstrap类,执行main方式。在jvm中,从类文档到运转要经历以下两个步骤:
首先,JVM会将Bootstrap.class信息加载到缓存中的方式地区。
然后,主缓存打开两个缓存内部空间,准备好流程计时器pc、软件包栈和邻近地区方式栈。
然后,JVM将在堆Heap上为Bootstrap.class创建Bootstrap.class的类实例。
JVM开始执行main方式,然后在软件包栈中为main方式创建两个栈框架。
当main方式在执行过程中调用greeting方式时,JVM将为greeting方式创建另两个栈框架,并将其推到软件包栈的顶部。在main之上,一次只有两个栈框架是活动的,当前是greeting。
当greeting方式结束时,greeting方式将从栈中弹出,当前活动框架将指向main,并且该方式将继续向下运转。
1.7摘要
1)独家/共享视角:
独占:流程计时器、软件包栈、邻近地区方式栈
共享:堆,方式地区
2)角度2)误差:
流程计时器:不会外溢,很特别,别人会
两种栈:可能出现两种外溢。一种是广度超过,报告StackOverflowError。内部空间不足:OutOfMemoryError。
Heap:只有当空间不足时,才会报告OutOfMemoryError,提示heapSpace。
方式:内部空间不足时,报告OutOfMemoryError,提示不同。1.6是permspace,1.8是元内部空间,这取决于它在哪里。
3)所有权:
计时器、软件包栈、邻近地区方式栈:缓存创建要申请匹配的、真实的物理内部空间。
堆:真实的物理内部空间,但是内部结构的分割出现了变化,1.6有永久生成,1.8被杀。
方式:最没有归属感是因为这是一个逻辑概念。1.6被放在堆的永久生成中,1.8被拆分,一部份放在元内部空间,一部份(方式区的运转时常量池中的类第一类,包括字符串常量,被设计为放在堆中)
直接缓存:这一块实际上不是运转时统算数据区的一部份,而是直接操作方式物理缓存。在nio操作方式中,DirectByteBuffer类可以在native上操作方式,避免在堆内外复制流。他们下一步调优就不涉及了,知道就好。