性能测试中的Java应用程序堆内存
JavaHeap可以使用几种不同的模型、方法、资源和提示来完全优化任何Java应用程序。
每个性能工程师都需要了解Java内存管理在JVM中是如何工作的,以微调JVM性能问题。我们以类、方法、对象、变量的形式创建的任何东西都与JVM中的内存有关。例如,如果我们要创建一个局部变量或全局变量或不同的类对象,则所有内容都存储在JVM堆内存中。
JVM中有大量的内存,它分为两部分,一部分是堆内存,另一部分是堆栈内存。首先,我们将从堆内存开始,解决与内存相关的所有性能潜在问题。Java中堆内存的用途和作用是什么,这是性能测试中比较普遍的问题。
对于所有的性能问题,有几种不同的模型、方法、资源和技巧可用于Java堆来完全优化任何Java应用程序。
堆内存
Java中有许多不同的图表解释了Java中堆内存的设计。每个性能工程师都必须从最近的JDK版本中了解PermGen和Metaspace之间的区别,我们可以参考下面的任何图表:
堆内存主要分为两代,年轻一代和老一代。年轻一代的第一部分称为EDEN空间,第二部分称为SURVIVOR0和SURVIVOR1空间组成的SURVIVOR空间。现在让我们来了解一下年轻一代每个空间的用途。每当我们创建新对象时,所有这些对象都首先存储在EDEN空间中。在JVM中,有一个称为垃圾收集的自动内存管理特性(这里不详细介绍GC的概念)。
如果应用程序很重并且创建了数千个对象,那么EDEN内存将充满对象,然后垃圾回收器将启动并删除所有未使用/未引用的对象,此过程称为Minor GC。这个小GC将把所有SURVIVOR对象移到SURVIVOR记忆空间。Minor GC已自动对年轻一代执行以释放内存。小GC在短时间内表现非常快。
为不同的类创建了这么多对象,所以当对象数量增加时,EDEN空间中的内存就会增加。让我们假设GC1、GC2、GC3等等在GCN之前被触发,当JVM自动调用许多垃圾收集器操作时,小GC会继续检查所有准备好生存的不同对象,JVM立即将所有这些对象转移到SURVIVOR内存中。
年轻一代中幸存下来的对象被移到老一代,当老一代被对象填满时,主GC被触发。何时执行主要GC?当老一代内存中的对象已满时,将执行主GC。大型GC需要长时间来完成。当团队开发任何Java应用程序或Java自动化框架时,如果开发人员要创建这么多对象,他们就必须小心年轻一代和老一代的概念。
开发人员不应该创建任何不必要的对象,如果他们创建了任何对象,垃圾回收器应该在他们完成任务后销毁它们。小GC将在年轻一代上执行,而老一代将在主要GC上执行。例如,如果我们以亚马逊或沃尔玛为例,我们意识到会有太多的请求和点击,我们会看到超时异常,高流量,在任何在线电子商务销售中,大多数时间都会出现超时异常,因为主要的GC会忙于占用大量内存来销毁对象,而且很明显,作为副作用,服务器上的CPU和内存利用率很高。
这表明他们在内部创建了许多特定类的对象。在这个场景中,主要GC将尝试连续大量销毁所有未使用的对象。与次要GC相比,主要GC需要更长的时间。
PermGen(Permanent Generation)-在JDK7之前可用
也许每个性能工程师似乎都有不同的问题,关于这个Permgen是干什么的?
-
Permgen会包含什么? -
在PermGen发生了什么? -
这里存储什么样的数据? -
什么样的属性将存储在 Permanent Generation 中?
PermGen是一个特殊的堆空间,与主内存堆分离。这个Permgen不是堆内存的一部分,它被称为非堆内存。您定义的所有静态变量和常量变量都将存储在方法区域中,而记住方法区域是Perm生成的一部分。这种Permgen的唯一缺点是其有限的内存大小,这将产生OutOfMemoryError。
PermGen中的类装入器没有得到充分的垃圾回收,因此会产生内存泄漏。32位JVM的默认最大内存大小为64 MB,64位版本为82 MB。jdk8已经删除了对Permgen的JVM参数-XX:PermSize和-XX:MaxPermSize的支持
Metaspace-jdk8
Metaspace是从javajdk8开始的一个新的内存空间,它取代了javajdk7中旧的PermGen内存空间。最显著的区别在于它如何处理内存分配。具体地说,这个本机内存区域在默认情况下会自动增长。jdk8的metaspace的优点是,一旦类元数据使用量达到其最大metaspace大小,垃圾收集器会自动触发对死类的删除。
通过新的改进和更改,JVM减少了从metaspace获取OutOfMemoryError的机会。我们有一些JVM参数来调整metaspacexx:MetaspaceSize和XX:MaxMetaspaceSize的内存
堆栈内存
Java堆栈内存用于线程执行,它包含特定的方法值。使用后进先出的数据结构。java中的Stack是一个包含方法、局部变量和引用变量的内存部分。存储在堆中的对象可以全局访问,而其他线程不能访问堆栈内存。当一个方法被调用时,它会在堆栈中为该特定方法创建一个新的块。
新块将具有所有的局部值,以及对该方法正在使用的其他对象的引用。当方法结束时,新块将被删除,并可供下一个方法使用。您在这里找到的对象只能访问该特定函数,并且不会超出该函数。与堆内存相比,堆栈内存的大小非常小。
如果堆栈中没有存储函数调用或局部变量的内存,JVM将抛出java.lang.StackOverflower我们可以使用-Xss来定义堆栈内存大小。
内存相关错误
您需要完全理解的是,这些输出只能说明对JVM的影响,而不是实际的错误。实际错误及其根本原因可能发生在代码中的某个地方,例如内存泄漏、GC问题、同步问题、资源分配,甚至可能是硬件设置。要解决这个问题,解决所有这些错误的简单方法是增加受影响的资源大小。
我们将需要从性能测试和工程环境中监控资源使用情况,分析每个类别,进行多个堆转储,遍历堆转储,检查并调试/优化您的代码,等等,如果您的修复看起来都不起作用,需要换一个思路。
java.lang.StackOverflower-此错误表示堆栈内存已满。
java.lang.OutOfMemoryError-此错误表示堆内存已满。
java.lang.OutOfMemoryError:
java.lang.OutOfMemoryError:Permgen space-此错误表示永久生成空间已满
java.lang.OutOfMemoryError:Metaspace-此错误表示Metaspace已满(因为javajdk8)
java.lang.OutOfMemoryError:Unable to create new native thread-此错误表示JVM本机代码无法再从底层操作系统创建新的本机线程,因为已经创建了太多线程,它们占用了JVM的所有可用内存
java.lang.OutOfMemoryError:request size bytes for reason-此错误表示应用程序已完全占用交换内存空间
java.lang.OutOfMemoryError:
如何设置初始堆和最大堆
初始堆大小-XMS
大于机器上机器物理内存的1/64,或某个合理的最小值。在J2SE5.0之前,一个合理的最小值是默认的初始堆大小,它因平台而异。可以使用命令行选项-Xms覆盖此默认值。
最大堆大小-XMX
小于物理内存的四分之一或1GB。在J2SE5.0之前,默认的最大堆大小是64MB。可以使用-Xmx命令行选项覆盖此默认值。根据oraclejvm人类工程学页面,最大堆大小应该是物理内存的1/4。当在VM和/或Docker容器中运行时,这个阈值也会被扩展(当在同一主机上运行多个Java应用程序时,最大堆大小限制将被聚合)。
建议的JVM设置
建议对大多数生产引擎层服务器使用以下JVM设置示例:
-server-Xms24G-Xmx24G-XX:PermSize=512m-XX:+UseG1GC-XX:maxgcpausemilis=200-XX:ParallelGCThreads=20-XX:congcthreads=5-XX:initiatingeappocationncypercent=70
对于生产副本服务器,请使用示例设置:
-server-Xms4G-Xmx4G-XX:PermSize=512m-XX:+UseG1GC-XX:maxgcpausemilis=200-XX:ParallelGCThreads=20-XX:congcthreads=5-XX:initiatingheapoccurrencypercent=70
对于独立安装,请使用示例设置:
-server-Xms32G-Xmx32G-XX:PermSize=512m-XX:+UseG1GC-XX:maxgcpausemilis=200-XX:ParallelGCThreads=20-XX:congcthreads=5-XX:initialingeapOccupmentcypercent=70
上面的JVM选项具有以下效果
-Xms,-Xmx:为堆大小设置边界,以提高垃圾收集的可预测性。副本服务器中的堆大小是有限的,因此即使是完整的gc也不会触发SIP重传。-Xms设置起始大小以防止由堆扩展引起的暂停。
-XX:+UseG1GC:使用垃圾优先(G1)收集器。
-XX:maxgcpausemilis:设置最大GC暂停时间的目标。这是一个软目标,JVM将尽力实现它。
-XX:ParallelGCThreads:设置垃圾收集器并行阶段使用的线程数。默认值随运行JVM的平台而异。
-XX:congcthreads:并发垃圾回收器将使用的线程数。默认值随运行JVM的平台而异。
-XX:initiatingheapoccurrencypercent:启动并发GC循环的(整个)堆占用率百分比。GC根据整个堆的占用率而不仅仅是一代(包括G1)触发并发GC循环,则使用此选项。值为0表示“do constant GC cycles”。默认值为45。
结论
有600多个参数可以传递给JVM来微调垃圾收集和内存。如果包含其他方面,JVM参数的数量将超过1000+。我们只解释了几个JVM参数,这些参数在Java应用程序的性能测试中是最有用的。