Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BeanUtils.copyProperties() consumes large amount of memory #27246

Closed
HashJang opened this issue Aug 6, 2021 · 9 comments
Closed

BeanUtils.copyProperties() consumes large amount of memory #27246

HashJang opened this issue Aug 6, 2021 · 9 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: backported An issue that has been backported to maintenance branches type: regression A bug that is also a regression
Milestone

Comments

@HashJang
Copy link

HashJang commented Aug 6, 2021

After upgrade from 5.2.x to 5.3.x, we observed large increase in YoungGC caused by huge amount of object created. After test we found that is because BeanUtils.copyProperties().

In 5.3.x, using ResolvableType to increase fitness but there is no cache which would cause there are many ResolvableType objects created and result in large memory consuming. And there is a comparasion:

TestBean testBean1 = new TestBean("1", "2", "3", "4", "5", "6", "7", "8", "1", "2", "3", "4", "5", "6", "7", "8");
TestBean testBean2 = new TestBean();
for (int i = 0; i > -1; i++) {
    BeanUtils.copyProperties(testBean1, testBean2);
    System.out.println(i);
}

the VM option is -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -Xmx512m, the program will exit when the memory run out.

For 5.2.x, the last i is:

444489

For 5.3.x, the last i is:

27456

That's a huge memory consuming.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Aug 6, 2021
@alefq
Copy link

alefq commented Aug 8, 2021

Can you post your TestBean source code? What JDK version are you using?

I'm not a spring framework contributor, just trying to help with triage; you could fork the repo; and adapt the unit test to your scenario.

Running corresponding test on both versions, time for test execution is less than 0.0s :

spring-framework/spring-beans$ gradle -v

------------------------------------------------------------
Gradle 5.6.4
------------------------------------------------------------

Build time:   2019-11-01 20:42:00 UTC
Revision:     dd870424f9bd8e195d614dc14bb140f43c22da98

Kotlin:       1.3.41
Groovy:       2.5.4
Ant:          Apache Ant(TM) version 1.9.14 compiled on March 12 2019
JVM:          11.0.11 (Ubuntu 11.0.11+9-Ubuntu-0ubuntu2)
OS:           Linux 5.11.0-25-generic amd64

Time:

  <testcase name="testCopyProperties()" classname="org.springframework.beans.BeanUtilsTests" time="0.0"/>
------------------------------------------------------------
Gradle 6.8.1
------------------------------------------------------------

Build time:   2021-01-22 13:20:08 UTC
Revision:     31f14a87d93945024ab7a78de84102a3400fa5b2

Kotlin:       1.4.20
Groovy:       2.5.12
Ant:          Apache Ant(TM) version 1.10.9 compiled on September 27 2020
JVM:          11.0.11 (Ubuntu 11.0.11+9-Ubuntu-0ubuntu2)
OS:           Linux 5.11.0-25-generic amd64

Time

  <testcase name="copyProperties()" classname="org.springframework.beans.BeanUtilsTests" time="0.0"/>

@HashJang
Copy link
Author

HashJang commented Aug 9, 2021

I am sorry, but I'm afraid that you did not get what I was meaning.

I want to point out that there were large amount of objects created by BeanUtils.copyProperties() after upgrade(And the object is ResolvableType Indeed).

By the way, the TestBean has only 16 String field with getter and setter.

And in my test, I set JVM flag as -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -Xmx512m which means the program will exit when the 512m memory run out and there would not be any GC. The memory run out after 444489 loops for 5.2.x, but 27456 loops for 5.3.x. Therefore, it would cause rather high YoungGC pressure if we switch from 5.2.x to 5.3.x.

@alefq
Copy link

alefq commented Aug 9, 2021

I'm trying to help with triage, help the community.
Normally you would need a unit test (TDD) or a consistent case to reproduce the issue.
BeanUtils rely heavily on introspection, uses cache techniques; profiling might be the way to pinpoint the problem; but a test case scenario is needed.
By using experimental VM options; you are in a very specific case; normally the issue should be reproduced with production ready environment; when making a library it is customary to use the most stable tech stack possible.
This is what triage is all about.
Regards.

@HashJang
Copy link
Author

HashJang commented Aug 9, 2021

Thanks for reply.

BeanUtils rely heavily on introspection, uses cache techniques; profiling might be the way to pinpoint the problem; but a test case scenario is needed.

I checked the source code for 5.3.x, there is no cache for ResolvableType currently which result in Many ResolvableType objects are created while make one call of BeanUtils.copyproeperties

By using experimental VM options; you are in a very specific case; normally the issue should be reproduced with production ready environment; when making a library it is customary to use the most stable tech stack possible.

That's not the point. After upgrade to Spring 5.3.x, we observed large increase in YoungGC frequency (Before upgrade: One YoungGC in every 20s; After upgrade: One YoungGC in every 1s). The objects created(not promoted) is increased from 200MB/s to nearly 2GB/s. Therefore I tried to find the problem and then I found very large amount of ResolvableType objects were created which took up large amount of memory. To prove that, I provided a simple Test here which use Experimental flags (just for limiting the memory and proving there are much more memory used than before). And my Test prove that there are large amount of memory consumed compared with the case not upgraded (If you check jmap -histo, you would find they are taken up mainly by ResolvableType).

@373616885
Copy link

It not only consumes a lot of memory, but also has a lot of performance degradation

@quaff
Copy link
Contributor

quaff commented Sep 16, 2021

Please investigate which commit from history cause such regression.

@aiolosgs
Copy link

found in e74f868.
BTW, is there any alternatives to BeanUtils.copyProperties?

@quaff
Copy link
Contributor

quaff commented Sep 29, 2021

found in e74f868. BTW, is there any alternatives to BeanUtils.copyProperties?

@sbrannen Could you take a look at this, It seems this commit causes performance regression.

@rstoyanchev rstoyanchev added the in: core Issues in core modules (aop, beans, core, context, expression) label Nov 10, 2021
@sdeleuze sdeleuze self-assigned this Sep 13, 2023
@sdeleuze sdeleuze assigned sbrannen and unassigned sdeleuze Oct 18, 2023
@sdeleuze
Copy link
Contributor

@sbrannen I lack of context to be able to analyze efficiently this potential performance regression, could you please take care of it? The data points shared are pretty significant, so it is IMO worth a deeper look.

@jhoeller jhoeller assigned jhoeller and unassigned sbrannen Oct 24, 2023
@jhoeller jhoeller added type: regression A bug that is also a regression and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Oct 24, 2023
@jhoeller jhoeller added this to the 6.0.14 milestone Oct 24, 2023
@github-actions github-actions bot added status: backported An issue that has been backported to maintenance branches and removed for: backport-to-5.3.x labels Oct 24, 2023
jhoeller added a commit that referenced this issue Oct 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: backported An issue that has been backported to maintenance branches type: regression A bug that is also a regression
Projects
None yet
Development

No branches or pull requests

10 participants