在 Lambda 中创建 AmazonS3Client 时出现 OutOfMemoryError
OutOfMemoryError when creating AmazonS3Client in Lambda
我有一个 AWS Lambda 函数,配置只有 128MB 内存,由 SNS 触发(它本身由 S3 触发)并将从 S3 下载文件。
在我的函数中,我有以下内容:
public class LambdaHandler {
private final AmazonS3Client s3Client = new AmazonS3Client();
public void gdeltHandler(SNSEvent event, Context context) {
System.out.println("Starting");
System.out.println("Found " + eventFiles.size() + " event files");
}
我已将所有逻辑注释掉并从 post 中排除,因为我收到了一个 OutOfMemoryError,我已将其与 AmazonS3Client 对象的创建隔离开来。当我取出那个物体时,我没有得到错误。上面的代码会导致 OutOfMemoryError。
我为该函数分配了 128MB 的内存,这真的不足以简单地获取凭据并实例化 AmazonS3Client 对象吗?
我试过给 AmazonS3Client 构造函数
new EnvironmentVariableCredentialsProvider()
以及
new InstanceProfileCredentialsProvider()
结果相似。
创建 AmazonS3Client 对象是否只需要更多内存?
下面是堆栈跟踪:
Metaspace: java.lang.OutOfMemoryError java.lang.OutOfMemoryError:
Metaspace at
com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder.build(BeanDeserializerBuilder.java:347)
at
com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:242)
at
com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:143)
at
com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:409)
at
com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:358)
at
com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:265)
at
com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:245)
at
com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:143)
at
com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:439)
at
com.fasterxml.jackson.databind.ObjectReader._prefetchRootDeserializer(ObjectReader.java:1588)
at
com.fasterxml.jackson.databind.ObjectReader.(ObjectReader.java:185)
at
com.fasterxml.jackson.databind.ObjectMapper._newReader(ObjectMapper.java:558)
at
com.fasterxml.jackson.databind.ObjectMapper.reader(ObjectMapper.java:3108)
当我尝试提供 InstanceProfileCredentialsProvider 或 EnvironmentVariableCredentialsProvider 时,我得到以下堆栈跟踪:
Exception in thread "main" java.lang.Error:
java.lang.OutOfMemoryError: Metaspace at
lambdainternal.AWSLambda.(AWSLambda.java:62) at
java.lang.Class.forName0(Native Method) at
java.lang.Class.forName(Class.java:348) at
lambdainternal.LambdaRTEntry.main(LambdaRTEntry.java:94) Caused by:
java.lang.OutOfMemoryError: Metaspace at
java.lang.ClassLoader.defineClass1(Native Method) at
java.lang.ClassLoader.defineClass(ClassLoader.java:763) at
java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467) at
java.net.URLClassLoader.access0(URLClassLoader.java:73) at
java.net.URLClassLoader.run(URLClassLoader.java:368) at
java.net.URLClassLoader.run(URLClassLoader.java:362) at
java.security.AccessController.doPrivileged(Native Method) at
java.net.URLClassLoader.findClass(URLClassLoader.java:361) at
java.lang.ClassLoader.loadClass(ClassLoader.java:424) at
java.lang.ClassLoader.loadClass(ClassLoader.java:357) at
lambdainternal.EventHandlerLoader$PojoMethodRequestHandler.makeRequestHandler(EventHandlerLoader.java:421)
at
lambdainternal.EventHandlerLoader.getTwoLengthHandler(EventHandlerLoader.java:777)
at
lambdainternal.EventHandlerLoader.getHandlerFromOverload(EventHandlerLoader.java:802)
at
lambdainternal.EventHandlerLoader.loadEventPojoHandler(EventHandlerLoader.java:888)
at
lambdainternal.EventHandlerLoader.loadEventHandler(EventHandlerLoader.java:740)
at
lambdainternal.AWSLambda.findUserMethodsImmediate(AWSLambda.java:126)
at lambdainternal.AWSLambda.findUserMethods(AWSLambda.java:71) at
lambdainternal.AWSLambda.startRuntime(AWSLambda.java:219) at
lambdainternal.AWSLambda.(AWSLambda.java:60) ... 3 more START
RequestId: 58837136-483e-11e6-9ed3-39246839616a Version: $LATEST END
RequestId: 58837136-483e-11e6-9ed3-39246839616a REPORT RequestId:
58837136-483e-11e6-9ed3-39246839616a Duration: 15002.92 ms Billed
Duration: 15000 ms Memory Size: 128 MB Max Memory Used: 50 MB
2016-07-12T14:40:28.048Z 58837136-483e-11e6-9ed3-39246839616a Task
timed out after 15.00 seconds
编辑 1 如果我将分配给函数的内存增加到 192MB,它工作得很好,但奇怪的是,在 cloudwatch 日志中报告只使用了 59MB 的内存。我只是失去了剩下的记忆吗?
我在 Lambda 函数中使用 AWS Java SDK 时一直观察到这一点。
看起来在创建任何 AWS 客户端(同步或异步)时您可能会退出 Metaspace。
我认为这是由于 Amazon 客户端在实例化时执行的操作,包括 AmazonHttpClient 创建以及请求处理程序链的动态加载(AmazonEc2Client#init()
私有方法的一部分)。
报告的内存使用情况可能是针对堆本身的,但可能不包括元空间。 AWS 论坛上有几个主题,但 AWS 没有对此事作出回应。
尝试将分配给 lambda 的内存从 128 MB 增加到 256 MB
减少冷启动的一种方法是将内存设置为 1536 mb 并将超时设置为 15 分钟。这将为 运行 只有您的 lambda 提供专用主机,而不是 运行 在共享主机上设置您的 lambda + 当必须启动新实例时,它将从主机上的缓存中复制代码而不是复制来自 S3.
不过这样做会更贵,如果您不想这样做,请继续阅读下文。
如何减少冷启动时间?
遵循 Lambda 最佳实践
https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html
通过为您的函数选择更大的内存设置
将内存视为“电源”设置,因为它还决定了您的函数将接收多少 CPU。
通过减小 ZIP 函数的大小
这可能意味着减少您在函数 ZIP 中包含的依赖项的数量。
Java 使用 ProGuard 可以进一步减小 JAR 的大小
[Java Only] 使用字节流接口,而不是 POJO 接口。
Lambda 内部使用的 JSON 序列化库可能需要一些时间才能启动。这将需要您进行开发工作,但您可以通过使用字节流接口和轻量级 JSON 库来改进这一点。以下是一些可能有帮助的链接:
http://docs.aws.amazon.com/lambda/latest/dg/java-handler-io-type-stream.html
https://github.com/FasterXML/jackson-jr
[Java Only] Don’t use Java 8 feature that replaces anonymous 类 (lambdas, method references, constructor references等)
我们在内部注意到 Java 8 Lambda 相关的字节码似乎会导致次优的启动性能。如果您的代码使用任何替代匿名 类 的 Java 8 功能(lambda、方法引用、构造函数引用等),您可以通过返回匿名 类 来获得更好的启动时间。
使用不同的运行时间
不同的运行时间有不同的冷启动时间,不同的运行时间性能。虽然 NodeJS 可能更适合繁重的 IO 工作,但 Go 可能更适合执行大量并发工作的代码。客户已经完成了一些基本的基准测试来比较 Lambda 上的语言性能,这里是对不同编程语言性能的更通用的比较。没有放之四海而皆准的答案,请根据您的要求选择合适的答案。
一般比较:https://benchmarksgame-team.pages.debian.net/benchmarksgame/which-programs-are-fast.html
我使用了一种有助于基于 Java 的 lambda 的策略。任何只需要单个(可重用)实例的 class 资源都可以声明为 static
class 成员,并在静态初始化块中初始化。当 lambda 创建 class 的新实例来处理执行时,那些昂贵的资源已经初始化。这是一个简单的例子:
package com.mydomain.myapp.lambda.sqs;
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
import com.amazonaws.services.sns.AmazonSNS;
import com.amazonaws.services.sns.AmazonSNSClientBuilder;
import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Objects;
public class MyLambdaFunctionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(MyLambdaFunctionHandler.class);
// These values come from the 'Environment' property for the lambda, defined in template.yaml
private static final String ENV_NAME = System.getenv("ENV_NAME");
// Declare these as static properties so they only need to be created once,
// rather than on each invocation of the lambda handler method that uses them
private static final ObjectMapper OBJECT_MAPPER;
private static final AmazonSNS SNS;
private static final AmazonSQS SQS;
static {
LOGGER.info("static initializer | START");
Objects.requireNonNull(ENV_NAME, "ENV_NAME cannot be null");
OBJECT_MAPPER = new ObjectMapper();
SNS = AmazonSNSClientBuilder.defaultClient();
SQS = AmazonSQSClientBuilder.defaultClient();
LOGGER.info("static initializer | END");
}
public MyLambdaFunctionHandler() {
LOGGER.info("constructor invoked");
}
public void handlerMethod(SQSEvent event) {
LOGGER.info("Received SQSEvent with {} messages", event.getRecords().size());
event.getRecords().forEach(message -> handleOneSQSMessage(message));
}
private void handleOneSQSMessage(SQSEvent.SQSMessage message) {
// your SQS message handling code here...
}
}
我声明为静态的属性将保留在内存中,直到 lambda 实例被 AWS 销毁。
这不是我通常编写 Java 代码的方式。基于 Lambda 的代码被区别对待,所以我认为在这里打破一些传统模式是可以的。
我有一个 AWS Lambda 函数,配置只有 128MB 内存,由 SNS 触发(它本身由 S3 触发)并将从 S3 下载文件。
在我的函数中,我有以下内容:
public class LambdaHandler {
private final AmazonS3Client s3Client = new AmazonS3Client();
public void gdeltHandler(SNSEvent event, Context context) {
System.out.println("Starting");
System.out.println("Found " + eventFiles.size() + " event files");
}
我已将所有逻辑注释掉并从 post 中排除,因为我收到了一个 OutOfMemoryError,我已将其与 AmazonS3Client 对象的创建隔离开来。当我取出那个物体时,我没有得到错误。上面的代码会导致 OutOfMemoryError。
我为该函数分配了 128MB 的内存,这真的不足以简单地获取凭据并实例化 AmazonS3Client 对象吗?
我试过给 AmazonS3Client 构造函数
new EnvironmentVariableCredentialsProvider()
以及
new InstanceProfileCredentialsProvider()
结果相似。
创建 AmazonS3Client 对象是否只需要更多内存?
下面是堆栈跟踪:
Metaspace: java.lang.OutOfMemoryError java.lang.OutOfMemoryError: Metaspace at com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder.build(BeanDeserializerBuilder.java:347) at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:242) at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:143) at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:409) at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:358) at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:265) at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:245) at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:143) at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:439) at com.fasterxml.jackson.databind.ObjectReader._prefetchRootDeserializer(ObjectReader.java:1588) at com.fasterxml.jackson.databind.ObjectReader.(ObjectReader.java:185) at com.fasterxml.jackson.databind.ObjectMapper._newReader(ObjectMapper.java:558) at com.fasterxml.jackson.databind.ObjectMapper.reader(ObjectMapper.java:3108)
当我尝试提供 InstanceProfileCredentialsProvider 或 EnvironmentVariableCredentialsProvider 时,我得到以下堆栈跟踪:
Exception in thread "main" java.lang.Error: java.lang.OutOfMemoryError: Metaspace at lambdainternal.AWSLambda.(AWSLambda.java:62) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:348) at lambdainternal.LambdaRTEntry.main(LambdaRTEntry.java:94) Caused by: java.lang.OutOfMemoryError: Metaspace at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:763) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) at java.net.URLClassLoader.defineClass(URLClassLoader.java:467) at java.net.URLClassLoader.access0(URLClassLoader.java:73) at java.net.URLClassLoader.run(URLClassLoader.java:368) at java.net.URLClassLoader.run(URLClassLoader.java:362) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:361) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at lambdainternal.EventHandlerLoader$PojoMethodRequestHandler.makeRequestHandler(EventHandlerLoader.java:421) at lambdainternal.EventHandlerLoader.getTwoLengthHandler(EventHandlerLoader.java:777) at lambdainternal.EventHandlerLoader.getHandlerFromOverload(EventHandlerLoader.java:802) at lambdainternal.EventHandlerLoader.loadEventPojoHandler(EventHandlerLoader.java:888) at lambdainternal.EventHandlerLoader.loadEventHandler(EventHandlerLoader.java:740) at lambdainternal.AWSLambda.findUserMethodsImmediate(AWSLambda.java:126) at lambdainternal.AWSLambda.findUserMethods(AWSLambda.java:71) at lambdainternal.AWSLambda.startRuntime(AWSLambda.java:219) at lambdainternal.AWSLambda.(AWSLambda.java:60) ... 3 more START RequestId: 58837136-483e-11e6-9ed3-39246839616a Version: $LATEST END RequestId: 58837136-483e-11e6-9ed3-39246839616a REPORT RequestId: 58837136-483e-11e6-9ed3-39246839616a Duration: 15002.92 ms Billed Duration: 15000 ms Memory Size: 128 MB Max Memory Used: 50 MB
2016-07-12T14:40:28.048Z 58837136-483e-11e6-9ed3-39246839616a Task timed out after 15.00 seconds
编辑 1 如果我将分配给函数的内存增加到 192MB,它工作得很好,但奇怪的是,在 cloudwatch 日志中报告只使用了 59MB 的内存。我只是失去了剩下的记忆吗?
我在 Lambda 函数中使用 AWS Java SDK 时一直观察到这一点。 看起来在创建任何 AWS 客户端(同步或异步)时您可能会退出 Metaspace。
我认为这是由于 Amazon 客户端在实例化时执行的操作,包括 AmazonHttpClient 创建以及请求处理程序链的动态加载(AmazonEc2Client#init()
私有方法的一部分)。
报告的内存使用情况可能是针对堆本身的,但可能不包括元空间。 AWS 论坛上有几个主题,但 AWS 没有对此事作出回应。
尝试将分配给 lambda 的内存从 128 MB 增加到 256 MB
减少冷启动的一种方法是将内存设置为 1536 mb 并将超时设置为 15 分钟。这将为 运行 只有您的 lambda 提供专用主机,而不是 运行 在共享主机上设置您的 lambda + 当必须启动新实例时,它将从主机上的缓存中复制代码而不是复制来自 S3.
不过这样做会更贵,如果您不想这样做,请继续阅读下文。
如何减少冷启动时间?
遵循 Lambda 最佳实践
https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html通过为您的函数选择更大的内存设置
将内存视为“电源”设置,因为它还决定了您的函数将接收多少 CPU。通过减小 ZIP 函数的大小
这可能意味着减少您在函数 ZIP 中包含的依赖项的数量。 Java 使用 ProGuard 可以进一步减小 JAR 的大小[Java Only] 使用字节流接口,而不是 POJO 接口。
Lambda 内部使用的 JSON 序列化库可能需要一些时间才能启动。这将需要您进行开发工作,但您可以通过使用字节流接口和轻量级 JSON 库来改进这一点。以下是一些可能有帮助的链接: http://docs.aws.amazon.com/lambda/latest/dg/java-handler-io-type-stream.html https://github.com/FasterXML/jackson-jr[Java Only] Don’t use Java 8 feature that replaces anonymous 类 (lambdas, method references, constructor references等)
我们在内部注意到 Java 8 Lambda 相关的字节码似乎会导致次优的启动性能。如果您的代码使用任何替代匿名 类 的 Java 8 功能(lambda、方法引用、构造函数引用等),您可以通过返回匿名 类 来获得更好的启动时间。使用不同的运行时间
不同的运行时间有不同的冷启动时间,不同的运行时间性能。虽然 NodeJS 可能更适合繁重的 IO 工作,但 Go 可能更适合执行大量并发工作的代码。客户已经完成了一些基本的基准测试来比较 Lambda 上的语言性能,这里是对不同编程语言性能的更通用的比较。没有放之四海而皆准的答案,请根据您的要求选择合适的答案。
一般比较:https://benchmarksgame-team.pages.debian.net/benchmarksgame/which-programs-are-fast.html
我使用了一种有助于基于 Java 的 lambda 的策略。任何只需要单个(可重用)实例的 class 资源都可以声明为 static
class 成员,并在静态初始化块中初始化。当 lambda 创建 class 的新实例来处理执行时,那些昂贵的资源已经初始化。这是一个简单的例子:
package com.mydomain.myapp.lambda.sqs;
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
import com.amazonaws.services.sns.AmazonSNS;
import com.amazonaws.services.sns.AmazonSNSClientBuilder;
import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Objects;
public class MyLambdaFunctionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(MyLambdaFunctionHandler.class);
// These values come from the 'Environment' property for the lambda, defined in template.yaml
private static final String ENV_NAME = System.getenv("ENV_NAME");
// Declare these as static properties so they only need to be created once,
// rather than on each invocation of the lambda handler method that uses them
private static final ObjectMapper OBJECT_MAPPER;
private static final AmazonSNS SNS;
private static final AmazonSQS SQS;
static {
LOGGER.info("static initializer | START");
Objects.requireNonNull(ENV_NAME, "ENV_NAME cannot be null");
OBJECT_MAPPER = new ObjectMapper();
SNS = AmazonSNSClientBuilder.defaultClient();
SQS = AmazonSQSClientBuilder.defaultClient();
LOGGER.info("static initializer | END");
}
public MyLambdaFunctionHandler() {
LOGGER.info("constructor invoked");
}
public void handlerMethod(SQSEvent event) {
LOGGER.info("Received SQSEvent with {} messages", event.getRecords().size());
event.getRecords().forEach(message -> handleOneSQSMessage(message));
}
private void handleOneSQSMessage(SQSEvent.SQSMessage message) {
// your SQS message handling code here...
}
}
我声明为静态的属性将保留在内存中,直到 lambda 实例被 AWS 销毁。
这不是我通常编写 Java 代码的方式。基于 Lambda 的代码被区别对待,所以我认为在这里打破一些传统模式是可以的。