【问题标题】:How to improve my Java APP for better performance?如何改进我的 Java APP 以获得更好的性能?
【发布时间】: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


【解决方案1】:
  1. 在这种情况下,我将注入这些配置作为实例。 2)所以每节课(假设我在一分钟内有 10.000 个用户,这个 类将被创建 10.000 次)将创建自己的 堆栈中的配置,将它以及完成时删除它, 所以内存又是空闲的。 3)但是因为会有这么多的请求, 它不会造成记忆问题吗?

不,这不会是一个问题。 Spring bean 默认是“Singleton”范围(除非您明确设置了另一个范围),因此只会创建 OpenStackBean 的一个实例。 所以不需要创建静态字段。

那么为什么在 10000 个请求之后堆会增加 300MB(从 100 到 400)?这意味着每次请求都会在内存中创建 30 KB。

因为 tomcat 需要创建很多临时对象来处理请求,但是在请求被处理后它们就会消失(另外请注意,您不会看到堆使用量减少,因为 java 只在垃圾收集后删除未使用的对象)

控制器的服务被调用。所以这个服务是为此而创建的 用户也是。

这也是错误的,如果你的控制器是 Singletone 作用域,它只会被创建一次。

当请求到达端点时究竟会发生什么?是创建APP的所有实例变量还是只创建访问的类?

诅咒不!当 tomcat 满足您的请求时,只需要实例化所需的类。

一般来说,您对性能问题过于关注了。你应该关心的是当一个对象没有从内存中删除时(内存使用量在 GC 之后增加而不是减少,直到应用程序崩溃)。内存占用也是真实存在的,但不是关于通过次要 GC 创建和销毁的一些小对象。

【讨论】:

    猜你喜欢
    • 2014-11-19
    • 1970-01-01
    • 2015-09-20
    • 1970-01-01
    • 1970-01-01
    • 2011-11-26
    • 2016-08-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多