4年前 (2020-11-19)  jvm |   抢沙发  2491 
文章评分 0 次,平均分 0.0

Java内部结构

我们的许多微服务都运行在Java虚拟机(JVM)上,在本文的其余部分中,有必要充分理解这一主题。如果您已经熟悉编译、内存管理和垃圾收集的概念,可以跳过这些部分。

与其他语言相比,Java的优势之一是它的跨平台可移植性。这意味着在安装了Java运行时环境(JRE)的任何地方都可以执行相同的字节码。然而,它需要相当多的幕后工作来实现。一个重要的组件允许Java以它的方式运行,它是定义编译和运行过程的体系结构。编译Java应用程序不会生成二进制文件,而是会生成字节码(一种可移植的、紧凑的程序表示形式),在执行时,字节码将转换为特定于平台的机器代码。由于从字节码到机器码的转换成本很高,JVM的just-in-time(JIT)编译器只编译频繁访问的代码路径。只执行一次的块(例如:在应用程序启动期间)可能会被解释为效率低下,但不会被编译,因为它不会提供相关的性能改进。

因此,Java源代码必须“编译”两次才能执行:

  1. 源代码被编译成字节码(Main.java到Main.class)
  2. 转换编译器主类(字节码)文件到本机计算机代码

元空间的垃圾回收

Java中的内存区域

既然我们已经阐明了Java程序如何执行的细节,我们将研究JVM中的内存管理。当JVM启动时,操作系统(OS)为进程分配内存,进程分为堆内存和非堆内存。非堆内存进一步细分为元空间、代码缓存、线程堆栈和共享库。

Heap Space 堆空间–此内存区域类型用于在运行时动态分配对象的内存。对象总是在堆空间中创建,而相应的引用包含在堆栈中。堆大小可以使用JVM参数配置:XmsXmx,它们分别定义初始大小和最大大小。一旦应用程序超过上限,则java.lang.OutOfMemoryError引发异常。

根据JVM实现的不同,堆可以分解为所谓的几代:

  1. 年轻一代:这里分配了新的对象。大多数对象的生命周期很短,在创建之后可以很快将其删除。当一个对象达到某个阈值时,它将被移到下一代。
  2. 旧一代:在多个垃圾回收周期中幸存下来的对象驻留在这里。对于老一代来说,不断流失也是一个很好的指标。

Metaspace元空间—Metaspace位于本机内存中—进程地址空间中的一个位置,不在堆中,存储类定义。默认情况下,此区域没有限制,其容量可以超过物理内存容量。在这种情况下,操作系统分配虚拟内存来利用交换空间,这会显著影响应用程序的性能。因此,我们建议为元空间定义一个上限。但是,必须仔细定义该值,因为超过限制将导致java.lang.OutOfMemoryError例外情况。

Code Cache 代码缓存——如前所述,JVM使用JIT编译器将字节码转换为特定于平台的机器代码。为了提高性能优化,将缓存已编译的机器代码块,以便更快地执行它们。

线程堆栈–静态分配(如指向堆上对象的基元值和引用)位于堆栈中。变量只存在于堆栈中已定义的相应方法的执行期间。除了堆之外,堆栈不需要收集垃圾,因为当方法返回时,它会自动收缩。但是,可以使用JVM参数调整默认堆栈大小;我们不建议进行这些调整,因为太小的限制会导致栈溢出.

垃圾收集

自从采用高级语言以来,内存利用已经成为软件工程的一个重要部分。在现代内存管理方法可用之前,开发人员必须手动确定应该分配多少内存以及何时可以将其返回系统。虽然这个手动过程允许在任何给定的时间点完全控制内存使用,但不幸的是,它很容易出现人为错误。然而,没有垃圾收集(GC)的编程语言在今天仍然被广泛使用,特别是在面向性能的或靠近硬件的应用程序中。

Java的一个核心好处是存在垃圾收集器(GC)。GC定期检查哪些对象仍在使用,哪些对象没有使用。将删除(或存档)未使用的对象,以便为新分配提供空间。作为一个软件开发的流线型程序不再是一个日常的内存消耗问题。当对高级语言(如Java)的内存问题进行故障排除时,我们建议您熟悉垃圾收集的概念以及如何以最佳方式配置它。

垃圾收集算法经过多年的发展,因此,Java提供了从一组不同gc中进行选择的选项。这一点很重要,因为每个GC都有其优缺点,因此应该根据特定的工作负载需求来选择GC。每个Java版本都有一个默认的GC,可以在Oracle的官方文档数据表中找到。假设GC是根据每个应用程序选择的,那么可以通过将JVM参数传递给java命令来更改GC,如下所示。

java -XX:+Use<GCName> -jar Application.jar

在上面的命令中,<GCName>必须替换为相应的GC。下面列出了Java8中提供的一些GC。

  • G1GC
  • ParallelGC
  • ConcMarkSweepGC

除了选择正确的GC之外,还可以使用各种调优参数进行进一步的定制。重要的是要明白,虽然GC的设计很好,但它们不能保证对内存泄漏的完全保护。即使在今天,使用现代保护层,仍然有可能遇到严重的性能问题。

内存渗漏

让我们最后谈谈泄漏,首先,需要澄清的是,尽管Java是一种垃圾收集语言,但仍然会发生内存泄漏。GC只确保未引用(无法访问)的对象被清除。与矩形和正方形非常相似,所有未引用的对象都是未使用的(因此可以安全地收集),但并非所有未使用的对象都是未引用的。一个简单的例子是类中的静态列表。一旦列表被填充,它的条目将永远不会被垃圾回收,因为JVM的垃圾收集器不知道该列表是否会再次被访问。下图说明了这一概念。

元空间的垃圾回收

一般来说,在前面描述的各种存储区域中都可能出现内存泄漏。但是,Java应用程序中最常见的泄漏源是堆空间,通常可以追溯到简单的编程错误,例如:

  • 获取对象引用的单例对象的静态字段或成员字段
  • 未关闭的流或连接
  • 将没有hashCode()equals()实现的对象添加到哈希集,因为这样可以反复使用同一个键向哈希集添加条目。
  • 效率低下的SQL查询,频繁执行,大数据集被读入内存

臭名昭著的OutOfMemoryError异常

Java中的内存泄漏不一定要在java.lang.OutOfMemoryError例外情况。这个异常更像是一种症状,也是一个很好的指示,表明某处可能有泄漏。如果没有足够的空间在Java堆中分配对象,则通常会发生这种情况,但也有其他原因(线程堆栈太大、GC开销过大等)导致日志中出现此异常。下面的枚举列出了所有类型的OOM异常,可以在这里找到有关各个异常的含义的更多信息。

  • java.lang.OutOfMemoryError: Java heap space
  • java.lang.OutOfMemoryError: GC Overhead limit exceeded
  • java.lang.OutOfMemoryError: Requested array size exceeds VM limit
  • java.lang.OutOfMemoryError: Metaspace
  • java.lang.OutOfMemoryError: request size bytes for reason. Out of swap space?
  • java.lang.OutOfMemoryError: Compressed class space
  • java.lang.OutOfMemoryError: reason stack_trace_with_native_method
 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/848.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册