【发布时间】:2018-04-17 13:43:28
【问题描述】:
我对这类电台的性能有一些疑问(现在我的项目使用 Spring Boot)。正如您很快就会明白的那样,我在一次学习大量信息时有点困惑。因此,我将分享我的不同类型的问题和一些测试,以了解流程并帮助像我一样困惑的其他人:
1) 像这样读取 application.properties:
@Value("${foo.name}")
private String name;
vs 以 poo 开头的属性读取到类中并使用 getter 获取其值。
@ConfigurationProperties(prefix = "foo")
public class FooBean {
private String name;
private String age;
...
// setters and getters
}
1a) 那么哪种方法更快,为什么?如果有差异,你能用记忆性能来解释吗 1b) 还可以说我使用 FooBean 方法,在 Example1 类中我只需要 FooBean 的名称。我应该将 FooBean 注入到 Example1 类中,而我只需要它的 1 个字段,还是应该使用 @Value 样式并只获取特定字段,现在性能更好?
2) 我应该将 FooBean 的字段声明为静态的吗?让我们看一个例子:
配置类:
@Component
@ConfigurationProperties(prefix = "openstack")
@Data
@Validated
public class OpenStackBean {
@NotNull
@Valid
private String container;
@NotNull
@Valid
private String keystoneEndpoint;
@NotNull
@Valid
private String password;
@NotNull
@Valid
private String swiftEndpoint;
@NotNull
@Valid
private String tenantName;
@NotNull
@Valid
private String userName;
}
假设这个类只被它的服务使用:
@Service
public class OpenStackService implements IOpenStackService {
private OpenStackBean openStackBean;
@Autowired
public OpenStackService(OpenStackBean openStackBean) {
this.openStackBean = openStackBean;
}
...
...
private OSClientV2 authenticate(Facing perspective) throws AuthenticationException {
return OSFactory
.builderV2()
.endpoint(openStackBean.getKeystoneEndpoint())
.credentials(openStackBean.getUserName(), blowfish.decryptString(openStackBean.getPassword()))
.tenantName(openStackBean.getTenantName())
.perspective(perspective)
.withConfig(Config
.newConfig()
.withConnectionTimeout(applicationBean.getConnectionTimeout())
.withReadTimeout(applicationBean.getReadTimeout())
.withMaxConnections(10)
.withMaxConnectionsPerRoute(2))
.authenticate();
}
如果我是正确的,场景将是这样的:
1) 在这种情况下,我将注入这些配置作为实例。 2)所以每个类(假设我在一分钟内有 10.000 个用户,并且这个类将被创建 10.000 次)将在堆栈中创建自己的配置,它会在它完成时删除它,所以内存再次空闲。 3)但是因为会有这么多请求,是不是内存问题?
如果此流程正确,我应该将 OpenStackBean 的字段创建为静态并将其注入为 private static OpenStackBean 吗?这将在堆中保存一次,所有用户都将从那里得到它。哪个性能更好?
3) 这是我的一些测试结果,这让我开始思考字符串操作和注入的性能。这是使用 JMeter 和使用 Java VisualVM 进行可视化的示例压力测试
发送 10.000 个请求
但我不明白为什么堆使用量总是增加。 APP的工作原理如下:
@RequestMapping(method = RequestMethod.GET)
public ResponseEntity all(@ApiIgnore GameParam param) {
try {
// get games by categories by default
if (param.getPageNo() == null) {
return new ResponseEntity<>(gameService.getGamesByCategories(), HttpStatus.OK);
}
所有请求都进入这个条件并调用:
2) 调用此函数。由于分类游戏列表是静态的(在APP启动时初始化,每天由一个Scheduler从远程服务器更新),所有请求直接返回2个步骤。 GameList 在堆中,所以不会创建新的实例。
GameList --> 14 KB(14173 字节)
@Override
public List<CategoryVO> getGamesByCategories() {
if (categorizedGameList != null) {
return categorizedGameList;
} else {
return getAndCategorizeGames();
}
}
3) 那么为什么在 10000 个请求之后堆会增加 300MB(从 100 到 400)?这意味着每个请求都会在内存中创建 30 KB。
3a) 当请求到达端点时究竟会发生什么? APP的所有实例变量都会被创建还是只创建访问的类?我是这样想的:
- 用户创建到我的端点的连接
- 用户将被路由到我的控制器类,所以这个类是在内存中创建的。 (不是应用程序的所有类)。它的实例变量仅为该用户创建,静态字段将从内存中提供给所有用户。
- 调用了控制器的服务。所以这个服务也是为这个用户创建的。
- 将静态列表返回给该用户。这不会增加我的内存 14KB,因为它已经在堆中。
- 但是每次请求我的内存都增加了 30 KB。
我知道这对阅读来说太长了,但我试图在此过程中提供我所有的经验。我对提高我的表现有点困惑,因为我意识到我从来没有想过它。感谢您的耐心等待。
【问题讨论】:
-
如果这段代码运行良好,那么这个问题在 Stack Overflow 上是题外话,但可能对我们的姊妹网站 Code Review 有好处。
-
你太担心性能了,以至于你想知道你是否不应该让你的代码可读性降低、不正确或无法测试,只是为了在启动您的应用程序。使您的代码干净、易读且符合规范。 Spring 架构师为您担心性能,而 JVM 在收集短期对象方面非常快。不要在没有性能问题时尝试优化。您使用的堆从 100 MB 开始,并在您的 10K 请求后回落到 100MB。您的 CPU 达到 40% 的峰值。没有问题。
-
默认情况下,Spring bean 是在应用程序范围内创建的,这意味着该 bean 在应用程序上下文中只有一个实例。因此,无论请求数量如何,您都将只有一个 OpenStackBean 和 OpenStackService 实例。关于每个连接的内存,不要忘记 servlet 容器必须为每个请求创建对象,例如 HttpServletRequest 和 HttpServletResponse。对于其余的观点,我同意 JB Nizet。
-
感谢您的建议。我担心内存是如何增加的,只是想从较低的角度了解工作流程
标签: java spring performance memory jvm