当前位置: 首页 > 网络应用技术

JVM |越过记忆管理的墙壁

时间:2023-03-06 23:32:31 网络应用技术

  对于程序员,借助虚拟机的自动内存管理机制,不再需要释放每个记忆以编写正确的代码,并且在内存泄漏和内存溢出中不容易发生。,一切都有两个侧面,虚拟机管理内存,一切都很漂亮,但这正是由于控制记忆的功能的处理。一旦内存泄漏和溢出发生,他们就必须从角度研究问题。因此,我们需要了解虚拟机如何使用内存来准确定位错误,从而正确解决问题。

  主要内容:

  在执行过程中,IT管理的内存分为几个不同的数据区域。在虚拟机过程的开始时,某些领域总是存在。

  私人记忆:

  因为它是通过线程旋转和分配处理器执行时间来实现的,因此,在任何时候,处理器(对于核心处理器是内核),仅在线程中执行指令。

  因此,为了在切换线程后返回正确的执行位置,每个线程都需要具有一个独立的程序计数器。各个线程之间的计数器不会彼此影响,并独立存储。

  程序表程序计数器是一个较小的内存空间,可以将其视为当前线程执行的字节代码的行号指标。这是程序控制流的索引。基本功能,例如分支,循环,跳跃,异常处理和线程恢复都需要依靠此计数器才能完成。

  字节码解释器可用于选择需要通过更改此计数器值执行的下一个字节代码指令。

  如果执行线程是一种方法,则该计数器记录正在执行的虚拟机字节代码指令的地址。

  如果正在执行本机方法,则该计数器值应为空。

  Java虚拟机堆栈描述了执行线程内存模型,这也是线程私有内存区域,生命周期和线程。

  当执行堆栈的每种方法时,将同时创建一个堆栈框架,以存储诸如本地变量表,操作号码堆栈,动态连接和方法导出等信息。调用each方法直到完成该过程,与该过程相对应虚拟机中的堆栈框架从输入堆栈到堆栈中的堆栈框架。

  1.本地变量表

  局部变量表可以在汇编期间知道:基本数据类型,对象引用和类型(到字节代码指令的地址)

  本地变量表中的存储空间由局部变量插槽表示。在编译期间分配了局部变量表所需的存储空间。输入方法时,需要完全由需要在堆栈框架中分配的局部变量空间完全确定(此处提到的“大小”是指可变插槽的数量,以及实现可变凹槽的大小通过特定的虚拟机)

  2.异常情况

  1. stackoverflowerror例外:线程请求的深度大于虚拟机允许的深度

  2. outofmemoryror例外:可以动态扩展Java虚拟机堆栈容量,并且在堆栈扩展时无法应用足够的内存。由于虚拟机堆栈的故障,虚拟机将不会引起异常。空间是成功的,它将无法使用,但是如果失败,它仍然会出现异常。

  本地方法堆栈和虚拟机堆栈的作用非常相似。本地方法堆栈是虚拟机使用的局部(本机)方法。

  热点虚拟机将本地方法堆栈和虚拟机堆叠直接结合到一个

  Java堆是由虚拟机管理的最大内存,所有线程共享的内存区域。它是由垃圾收集器管理的内存区域。

  它将在虚拟机启动时创建。此内存区域的唯一目的是存储对象实例。世界上的所有对象实例在这里分配内存。

  从回收记忆的角度来看,因为大多数现代垃圾收集者都是基于世代的“新一代”,“老年”,“永久一代”,“伊甸园”的理论设计,经常出现在Java中。PILESSPACE““到幸存者空间”和其他术语。

  在(G1收集器作为边界的出现)之前收藏家技术与十年前不同。还有一些新的垃圾收集器,这些收集器不是在热点中设计的,并且根据上述提及的地方有很多地方可以讨论。

  分配缓冲区TLAB(线程本地分配缓冲区)如果从内存分配的角度,所有线程都可以分为多线程的私有分配缓冲区(TLAB)。

  无论如何将其分开,存储中内容的共同点都不会更改。无论哪个区域,存储都只能是对象的实例。细分的目的只是更好地恢复内存或更快地分配内存。

  可以将Java桩的尺寸设置作为固定尺寸或可扩展性实现,但是根据可扩展(通过参数和设置)实现当前主流。扩展,将抛出一个例外。

  该方法区域称为“非pile”。它用于存储数据,例如类型信息,常数,静态变量和代码编码的代码缓存,由虚拟机汇编。它是各个线程共享的存储区域。

  许多人更愿意将方法区域称为“永久循环”,或者混合两者。本质上,这两个并不平等。因为这只是热点虚拟机设计团队选择将收集器的分布扩展到方法区域,或使用永久生成来实现方法区域,以便热点垃圾收集器可以像Java Piles的管理一样管理此部分。Memory,消除了专门为方法区域编写内存管理代码的工作。

  相对而言,垃圾收集行为在这一领域确实显得较少,但并非通过输入方法区域的数据永久存在。根据该区域的回收效应,很难令人满意,尤其是卸载类型和类型条件非常严峻,但是有时需要对该地区的这一部分进行回收。

  运行恒定池运行频率池是方法区域的一部分。在类,字段,方法,接口等的描述信息中,还有一个恒定的池表,用于存储由该池,以存储由该台词生成的各种文字和符号参考汇编周期。

  运行常数池的另一个重要功能是动态的,不需要常数必须仅由编译期生成。将新常数输入池中,开发人员使用此特性来使用字符串类。

  在 - 弦#实习生的 - 深度分析中

  在Java中,双引号的对象直接存储在常数池中。它不是双引号的对象,并且可以使用提供的方法。

  方法:如果字符串常数池已经包含一个等于此的字符串,则它将返回代表池中字符串的引用;否则,此字符串中包含的字符串将添加到常数池并返回引用。

  类似于上述介绍的JVM数据区域类似:数据区域:

  以上介绍程序计数器,虚拟机堆栈和本地方法堆栈都是线程私有区域。这三个领域是由线程的常规诞生的。无需考虑如何在这些领域恢复。当方法结束或线程结束时,内存自然会遵循恢复。

  例如,堆栈中的堆栈帧执行堆栈并以有序的方式输入堆栈操作,并以方法的输入和退出。在确定类结构时,每个堆栈框架中分配的内存量基本上是知道的(尽管有些有些(尽管有些即时编译器将在基于概念模型的讨论中优化优化,可以将其视为编译。它可以知道),因此,这些区域的内存分配和回收是确定的。

  但是,该方法区域的两个区域显着不确定:

  1.接口中多个实现类所需的内存可能不同。通过方法执行的不同条件分支所需的内存可能不同。只有在操作期间,我们才能知道程序将创建和创建许多对象,这部分内存的分配和回收是动态的。垃圾收集器所关注的是如何管理内存的这一部分。

  2.方法区域垃圾收集主要回收了两个部分:不再使用的废弃常数和类型。归因放弃常数与恢复中的物体非常相似。

  例如,没有字符串对象可以在常数池中引用一定数量的常数,并且虚拟机中没有其他位置。将通过系统清理。其他类型(接口),方法和常数池中的字段符号引用与此相似。

  方法通常相对较低垃圾收集的“成本效益”:在Java堆中,尤其是在新一代中,可以通过常规应用可以恢复70%至99%的记忆空间。方法区域中的回收方法是囿在严格的判断条件下,区域垃圾收集的回收通常远低于此。

  确定对象生存的对象是死亡的对象,因此在恢复前要做什么是确定该对象是否仍然生存。有两种算法确定生存对象的方法:参考计数算法和可访问的分析算法。

  参考计数算法在对象中添加了一个参考计数器。每当有一个地点参考时,添加了计数器值;当引用无效时,不可能在任何时候降低一个零对象。

  该算法的缺点是,当两个对象相互引用时,它们将导致其无法恢复;因为它们彼此引用,导致其参考计数不为零,并且无法回收参考计数算法。

  尽管参考计数(参考计数)尽管使用了一些其他内存空间来计数,但其原理很简单,判断效率也很高。在大多数情况下,这是一个很好的算法。也有一些众所周知的应用程序案例,例如Microsoft Com(组件对象模型)技术,使用ActionScript 3,Python语言和Squirrel在游戏脚本领域中使用参考计数中的flashplayer记忆管理算法。但是,在Java领域,至少未从参考数算法中选择主流Java虚拟机来管理内存。

  当前主流商业程序语言(Java,C#,LISP)的内存管理子系统确定该对象是否通过可及性分析算法得以生存。

  该算法的基本思想是使用一系列称为“ GC根”作为起始节点集的根对象。从这些节点开始,根据参考关系进行搜索,搜索过程称为“参考链”(参考链”(参考链”(参考链”(参考链,如果某个对象都没有任何参考链连接,则在图的情况下,当该对象未从该对象达到该对象时,不可能使用该对象。

  有许多类型的对象,常见的物体:

  通过参考计数算法确定对象的参考数量的几种参考方法,或确定是否可以通过可访问分析算法的对象达到链,并确定对象是否幸存下来,与“参考”密不可分。

  根据原因引起的强度从强度到弱分类:

  标记算法算法算法的垃圾回收算法分为两个阶段:“ mark”和“ clearing”:首先标记所有需要回收的对象。标记完成后,所有标记的对象都可以均匀地收集。对象,均匀地恢复所有未安装对象。

  缺点:

  标签复制算法通过记忆量分为相同大小的两个部分,每次只有一个。使用的记忆空间立即清洁。

  优势:

  解决标记方法的缺点。每个时间被回收,无需考虑浪费记忆。

  缺点:

  大多数商用Java虚拟机当前使用此系列算法来恢复新一代。

  新一代中98%的物体无法在第一轮中收集。因此,无需根据1:1:1:Hotspot之类的新代收藏家的比率来分配新一代的记忆空间。虚拟机,Parnew和其他新一代收藏家使用此策略来设置新一代的内存布局。Appel回收的特定方法是将新一代分为较大的伊甸园和两个较小的幸存者空间。每个记忆只使用伊甸园和幸存者之一。当收集垃圾时,仍然在伊甸园和幸存者中生存的物体一次被复制到另一个幸存者空间,然后直接清理伊甸园和所使用的幸存者。热点虚拟机默认伊甸园和幸存者是8:1,即,在新一代中可以使用的记忆空间可以是整个新一代能力的90%(占伊甸园的80%加10%的幸存者),只有一个生存空间,即新一代的10%将被“浪费”。

  标记整理方法该算法允许所有存活的对象移动到存储空间的一端,然后直接清除边界外的内存。

  优点:t标签没有问题来组织记忆浪费。

  缺点:复制收集算法将在对象存活率高时复制操作,并且移动操作将更多,并且效率将变得较低。

  标记清除和标记组织方法的选择是一个平衡:

  通过移动生存对象(尤其是在老年)进行标记的组织方法,每个回收都有大量的物体生存区域。移动生存对象并更新所有引用这些对象的地方将是一个非常繁重的操作,并且这种对象的移动操作必须在整个用户应用程序(停止世界)中暂停。

  如果您根本不考虑移动和组织幸存的物体,则如果您清除算法,则堆积在堆中的幸存物体引起的空间碎片问题只能依靠更复杂的内存分配者和记忆访问者来解决。例如,通过解决了“分区设置链接”,记忆分布问题(计算机硬盘存储大文件不需要物理连续磁盘空间,并且可以通过硬盘分区在碎片硬盘上存储和访问)。Memory访问是最常见的操作用户程序。如果将额外的负担添加到此链接中,则不可避免地会直接影响应用程序的应用。

  基于上述两个点,有缺点是移动对象是否为,并且当内存回收利用时,内存将更加复杂,当内存分布不移动时,它将变得更加复杂。从垃圾收集的停顿,如果没有移动对象的停止,它将更短,甚至无法暂停,但是从整个程序的吞吐量中,移动对象将更具成本效益。

  即使不移动对象会提高收集器的效率,它也比内存分布的频率和访问频率高于垃圾收集频率。该部分的时间消耗增加了,总吞吐量仍会减少。

  热点虚拟机对吞吐量平行的清除收集器表示关注基于标记组织算法,而延迟的CMS收集器基于标记清除算法。

  为了平衡两者的缺点,有一种中性的方式。在大多数情况下,虚拟机使用标记清晰的算法,暂时容忍内存片段的存在,直到存储空间的碎片程度如此它会影响对象的分布,然后使用标记 - 组织算法收集常规存储空间以获得常规存储空间,例如,使用标记清除算法的CMS收集器用于使用此方法处理此方法。空间碎片太多。

  当前商业虚拟机收集算法的大多数垃圾收集器,其中大多数遵循“相互依存收集”的理论。

  多个常用垃圾收集器的一致设计原理:收集器应将Java堆分成不同的区域,然后根据其年龄(即年龄,即通过垃圾收集过程的对象的时间)分发回收对象)贮存。

  这样的优点是:

  在分开了不同的区域之后,一次垃圾收集器一次只能恢复该区域的一个或某些部分。因此,这种回收类型的划分。只能安排与与存在相匹配的垃圾收集算法在不同区域的存储对象。

  收集概念的区别:

  次要GC/Young GC:指目标只是新一代的垃圾收集

  GC/Old GC少校:指的是仅垃圾收集的目标。请注意,“主要GC”的说法现在有些混乱。不同的信息通常是指它。读者需要区分上下文是将上下文与老年或整个集合区分开的。

  全GC:在整个Java堆和方法区域收集垃圾收集。

  爪哇桩被分为新一代和旧一代。在新一代中,在每个垃圾收集期间都发现了大量物体,并且在每次恢复后逐渐将少量存活的物体逐渐晋升为老年。

  PS:这些区域的划分只是某些垃圾收集器的共同特征或设计样式,而不是固有内存布局的特定实现,而不是“ Java虚拟机规范”的进一步详细划分。该行业,所有的内部垃圾收集器都是基于“经典部门”设计的,它要求新一代和较老的收藏家可以工作。但是到今天,还有一些新的垃圾收集器。

  下面介绍的回收策略基于“经典划分”设计回收过程:

  1.新一代的分配和恢复1.在大多数情况下,对象是在新一代中分配的。当没有足够的空间分配空间时,虚拟机将启动一次。新一代分为较大的一代伊甸园空间和两个较小的幸存者空间。每个记忆只使用伊甸园和幸存者之一。当收集垃圾时,仍然在伊甸园和幸存者中生存的物体一次将其复制到另一个幸存者空间,然后直接清理伊甸园和所使用的幸存者空间。默认情况下,热点虚拟机的比率为8:1

  2.大物体直接进入老年2.大型物体直接进入老年。较大的对象是指大量连续的内存空间。最典型的大物体是长字符串或大量元素。

  您为什么这样做?目的是避免恢复系统和两个内存复制操作。

  大对象是虚拟机的内存分布的坏消息。比遇到一个大对象更糟糕的消息是遇到一组简短的“死亡”大型对象。当我们编写程序时,我们应该注意避免大对象。Java虚拟机是,当分配空间时,它很容易导致内存触发垃圾收集,当仍然有很多空间以获得足够的连续空间来设置它们。在同一时间,大对象意味着高内存复制费用。

  3.长期存活的物体将进入老年。如果它们在第一次之后仍然可以生存并可以容纳,则该物体将被移至空间并将其目标年龄设置为1岁。每个物体生存的时间都会存活,年龄都会增加1年。当它的年龄增加到一定年龄的阈值(默认15)时,它将被提升为老年。对象可以晋升为老年人的年龄阈值,并可以通过参数设置。