Reduce garbage created when loading fat jars
Refactor fat jar loader classes so that less `char[]` instances are created. This is primarily achieved by adding a new `StringSequence` class that can chop up Strings without needing to copy the underlying array. Since Java 8, calls to `String.subString(...)` always copy the underlying char array. For many of the operations that we need, this is unnecessary. Fixes gh-11405
This commit is contained in:
@@ -30,6 +30,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*/
|
||||
public class AsciiBytesTests {
|
||||
|
||||
private static final char NO_SUFFIX = 0;
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@@ -106,22 +108,6 @@ public class AsciiBytesTests {
|
||||
abcd.substring(3, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appendString() {
|
||||
AsciiBytes bc = new AsciiBytes(new byte[] { 65, 66, 67, 68 }, 1, 2);
|
||||
AsciiBytes appended = bc.append("D");
|
||||
assertThat(bc.toString()).isEqualTo("BC");
|
||||
assertThat(appended.toString()).isEqualTo("BCD");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appendBytes() {
|
||||
AsciiBytes bc = new AsciiBytes(new byte[] { 65, 66, 67, 68 }, 1, 2);
|
||||
AsciiBytes appended = bc.append(new byte[] { 68 });
|
||||
assertThat(bc.toString()).isEqualTo("BC");
|
||||
assertThat(appended.toString()).isEqualTo("BCD");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hashCodeAndEquals() {
|
||||
AsciiBytes abcd = new AsciiBytes(new byte[] { 65, 66, 67, 68 });
|
||||
@@ -163,4 +149,42 @@ public class AsciiBytesTests {
|
||||
assertThat(new AsciiBytes(input).hashCode()).isEqualTo(input.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesSameAsString() {
|
||||
matchesSameAsString("abcABC123xyz!");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesSameAsStringWithSpecial() {
|
||||
matchesSameAsString("special/\u00EB.dat");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesSameAsStringWithCyrillicCharacters() {
|
||||
matchesSameAsString("\u0432\u0435\u0441\u043D\u0430");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesDifferentLengths() {
|
||||
assertThat(new AsciiBytes("abc").matches("ab", NO_SUFFIX)).isFalse();
|
||||
assertThat(new AsciiBytes("abc").matches("abcd", NO_SUFFIX)).isFalse();
|
||||
assertThat(new AsciiBytes("abc").matches("abc", NO_SUFFIX)).isTrue();
|
||||
assertThat(new AsciiBytes("abc").matches("a", 'b')).isFalse();
|
||||
assertThat(new AsciiBytes("abc").matches("abc", 'd')).isFalse();
|
||||
assertThat(new AsciiBytes("abc").matches("ab", 'c')).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesSuffix() {
|
||||
assertThat(new AsciiBytes("ab").matches("a", 'b')).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesSameAsStringWithEmoji() {
|
||||
matchesSameAsString("\ud83d\udca9");
|
||||
}
|
||||
|
||||
private void matchesSameAsString(String input) {
|
||||
assertThat(new AsciiBytes(input).matches(input, NO_SUFFIX)).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.jar;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.loader.jar.JarURLConnection.JarEntryName;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link JarEntryName}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class JarEntryNameTests {
|
||||
|
||||
@Test
|
||||
public void basicName() {
|
||||
assertThat(new JarEntryName("a/b/C.class").toString()).isEqualTo("a/b/C.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nameWithSingleByteEncodedCharacters() {
|
||||
assertThat(new JarEntryName("%61/%62/%43.class").toString())
|
||||
.isEqualTo("a/b/C.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nameWithDoubleByteEncodedCharacters() {
|
||||
assertThat(new JarEntryName("%c3%a1/b/C.class").toString())
|
||||
.isEqualTo("\u00e1/b/C.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nameWithMixtureOfEncodedAndUnencodedDoubleByteCharacters() {
|
||||
assertThat(new JarEntryName("%c3%a1/b/\u00c7.class").toString())
|
||||
.isEqualTo("\u00e1/b/\u00c7.class");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import org.springframework.boot.loader.TestJarCreator;
|
||||
import org.springframework.boot.loader.jar.JarURLConnection.JarEntryName;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -155,6 +156,31 @@ public class JarURLConnectionTests {
|
||||
.isEqualTo(connection.getJarEntry().getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jarEntryBasicName() {
|
||||
assertThat(new JarEntryName(new StringSequence("a/b/C.class")).toString())
|
||||
.isEqualTo("a/b/C.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jarEntryNameWithSingleByteEncodedCharacters() {
|
||||
assertThat(new JarEntryName(new StringSequence("%61/%62/%43.class")).toString())
|
||||
.isEqualTo("a/b/C.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jarEntryNameWithDoubleByteEncodedCharacters() {
|
||||
assertThat(new JarEntryName(new StringSequence("%c3%a1/b/C.class")).toString())
|
||||
.isEqualTo("\u00e1/b/C.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jarEntryNameWithMixtureOfEncodedAndUnencodedDoubleByteCharacters() {
|
||||
assertThat(
|
||||
new JarEntryName(new StringSequence("%c3%a1/b/\u00c7.class")).toString())
|
||||
.isEqualTo("\u00e1/b/\u00c7.class");
|
||||
}
|
||||
|
||||
private String getAbsolutePath() {
|
||||
return this.rootJarFile.getAbsolutePath().replace('\\', '/');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader.jar;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link StringSequence}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class StringSequenceTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void createWhenSourceIsNullShouldThrowException() {
|
||||
this.thrown.expect(NullPointerException.class);
|
||||
this.thrown.expectMessage("Source must not be null");
|
||||
new StringSequence(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createWithIndexWhenSourceIsNullShouldThrowException() {
|
||||
this.thrown.expect(NullPointerException.class);
|
||||
this.thrown.expectMessage("Source must not be null");
|
||||
new StringSequence(null, 0, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createWhenStartIsLessThanZeroShouldThrowException() {
|
||||
this.thrown.expect(StringIndexOutOfBoundsException.class);
|
||||
new StringSequence("x", -1, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createWhenEndIsGreaterThanLengthShouldThrowException() {
|
||||
this.thrown.expect(StringIndexOutOfBoundsException.class);
|
||||
new StringSequence("x", 0, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void creatFromString() {
|
||||
assertThat(new StringSequence("test").toString()).isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subSequenceWithJustStartShouldReturnSubSequence() {
|
||||
assertThat(new StringSequence("smiles").subSequence(1).toString())
|
||||
.isEqualTo("miles");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subSequenceShouldReturnSubSequence() {
|
||||
assertThat(new StringSequence("hamburger").subSequence(4, 8).toString())
|
||||
.isEqualTo("urge");
|
||||
assertThat(new StringSequence("smiles").subSequence(1, 5).toString())
|
||||
.isEqualTo("mile");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subSequenceWhenCalledMultipleTimesShouldReturnSubSequence() {
|
||||
assertThat(new StringSequence("hamburger").subSequence(4, 8).subSequence(1, 3)
|
||||
.toString()).isEqualTo("rg");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subSequenceWhenEndPastExistingEndShouldThrowException() {
|
||||
StringSequence sequence = new StringSequence("abcde").subSequence(1, 4);
|
||||
assertThat(sequence.toString()).isEqualTo("bcd");
|
||||
assertThat(sequence.subSequence(2, 3).toString()).isEqualTo("d");
|
||||
this.thrown.expect(IndexOutOfBoundsException.class);
|
||||
sequence.subSequence(3, 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subSequenceWhenStartPastExistingEndShouldThrowException() {
|
||||
StringSequence sequence = new StringSequence("abcde").subSequence(1, 4);
|
||||
assertThat(sequence.toString()).isEqualTo("bcd");
|
||||
assertThat(sequence.subSequence(2, 3).toString()).isEqualTo("d");
|
||||
this.thrown.expect(IndexOutOfBoundsException.class);
|
||||
sequence.subSequence(4, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isEmptyWhenEmptyShouldReturnTrue() {
|
||||
assertThat(new StringSequence("").isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isEmptyWhenNotEmptyShouldReturnFalse() {
|
||||
assertThat(new StringSequence("x").isEmpty()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lengthShouldReturnLength() {
|
||||
StringSequence sequence = new StringSequence("hamburger");
|
||||
assertThat(sequence.length()).isEqualTo(9);
|
||||
assertThat(sequence.subSequence(4, 8).length()).isEqualTo(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void charAtShouldReturnChar() {
|
||||
StringSequence sequence = new StringSequence("hamburger");
|
||||
assertThat(sequence.charAt(0)).isEqualTo('h');
|
||||
assertThat(sequence.charAt(1)).isEqualTo('a');
|
||||
assertThat(sequence.subSequence(4, 8).charAt(0)).isEqualTo('u');
|
||||
assertThat(sequence.subSequence(4, 8).charAt(1)).isEqualTo('r');
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOfCharShouldReturnIndexOf() {
|
||||
StringSequence sequence = new StringSequence("aabbaacc");
|
||||
assertThat(sequence.indexOf('a')).isEqualTo(0);
|
||||
assertThat(sequence.indexOf('b')).isEqualTo(2);
|
||||
assertThat(sequence.subSequence(2).indexOf('a')).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOfStringShouldReturnIndexOf() {
|
||||
StringSequence sequence = new StringSequence("aabbaacc");
|
||||
assertThat(sequence.indexOf("a")).isEqualTo(0);
|
||||
assertThat(sequence.indexOf("b")).isEqualTo(2);
|
||||
assertThat(sequence.subSequence(2).indexOf("a")).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOfStringFromIndexShouldReturnIndexOf() {
|
||||
StringSequence sequence = new StringSequence("aabbaacc");
|
||||
assertThat(sequence.indexOf("a", 2)).isEqualTo(4);
|
||||
assertThat(sequence.indexOf("b", 3)).isEqualTo(3);
|
||||
assertThat(sequence.subSequence(2).indexOf("a", 3)).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hashCodeShouldBeSameAsString() {
|
||||
assertThat(new StringSequence("hamburger").hashCode())
|
||||
.isEqualTo("hamburger".hashCode());
|
||||
assertThat(new StringSequence("hamburger").subSequence(4, 8).hashCode())
|
||||
.isEqualTo("urge".hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsWhenSameContentShouldMatch() {
|
||||
StringSequence a = new StringSequence("hamburger").subSequence(4, 8);
|
||||
StringSequence b = new StringSequence("urge");
|
||||
StringSequence c = new StringSequence("urgh");
|
||||
assertThat(a).isEqualTo(b).isNotEqualTo(c);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user