During memory analysis, we found high transient memory usage (8MB)
in the Catalog.normalizeURI usage of StringBuilder.append:
4.5% - 7,841 kB - 57,052 alloc.
com.sun.org.apache.xml.internal.resolver.Catalog.resolveSystem
4.4% - 7,735 kB - 56,164 alloc.
com.sun.org.apache.xml.internal.resolver.Catalog.normalizeURI
1.9% - 3,253 kB - 7,486 alloc. java.lang.StringBuilder.append(char)
1.0% - 1,765 kB - 8,670 alloc.
java.lang.StringBuilder.append(java.lang.String)
3.2% - 5,626 kB - 45,912 alloc.
com.sun.org.apache.xml.internal.resolver.Catalog.resolvePublic
3.1% - 5,507 kB - 44,460 alloc.
com.sun.org.apache.xml.internal.resolver.Catalog.normalizeURI
1.3% - 2,255 kB - 5,738 alloc. java.lang.StringBuilder.append(char)
0.7% - 1,245 kB - 6,794 alloc.
java.lang.StringBuilder.append(java.lang.String)
The culprit is the following for loop that creates new StringBuilder instances with 0 size, so that each call to append creates a new StringBuilder instance, leaving the
old one on the heap, hence the high transient memory usage.
for(int count = 0; count < bytes.length; count++)
{
int ch = bytes[count] & 0xff;
if(ch <= 32 || ch > 127 || ch == 34 || ch == 60 || ch == 62 || ch
== 92 || ch == 94 || ch == 96 || ch == 123 || ch == 124 || ch == 125 || ch ==
127)
newRef = (new
StringBuilder()).append(newRef).append(encodedByte(ch)).toString();
else
newRef = (new
StringBuilder()).append(newRef).append((char)bytes[count]).toString();
}
Recommendation:
1. create StringBuilder with estimated capacity before the for loop
StringBuilder sb = new StringBuilder(bytes.length);
for(int count = 0; count < bytes.length; count++)
{
int ch = bytes[count] & 0xff;
if(ch <= 32 || ch > 127 || ch == 34 || ch == 60 || ch == 62 || ch
== 92 || ch == 94 || ch == 96 || ch == 123 || ch == 124 || ch == 125 || ch ==
127)
newRef =
sb.append(newRef).append(encodedByte(ch)).toString();
else
newRef =
sb.append(newRef).append((char)bytes[count]).toString();
}