Tuesday, May 24, 2016

JUNIT - Method Contract Testing

What is method contract testing:


Form the developer stand point it is very important to write Junit. I my career I had seen many different method/process/ways people write junit. Some write serious test cases, some writes for just sake of writing the test case and some test cases even puzzled me what exactly test case want to accomplish. 

Now as everyone is moving towards continuous integration (CI), automated builds, self-testing and rapid development writing good junit become the most critical part of development cycle.

In any development the most important aspect to test is Object, Method and its behavior with respect to change in its/dependent object state and evaluation path along with non functional aspects.

Any good JUNIT should revolve along the first two, change in state and evaluation path where as non functional aspects should be covered more precisely in integration testing.

While doing JUNIT developer needs to concentrate to method under test and all input / dependencies needs to be mocked. Test exactly how method will react to input, change in object state. Doing so if each and every method has good test case then eventually you will end up in writing good test case which can cover almost all scenarios ( That depends upon how good test case is written).

Coming to the point each method under test has certain method contracts. Following are different types of method contract:

1) Input Contract.
2) Output Contract.
3) Class Contract.
3) Exception Contract.
4) Behavioral Contract.
5) Non functional Contract.

Input Contract:

1) Null check - Method may or may not check null validity for input parameters.
In this case either current method or calling method should have null check unit test case.

2) Input Modification - Method may or may not modified the input objects.
In this case proper assert needs to be there for input object passed to check for modifications.


Output Contract:

1) return value contract - Based on the signature method can return new object or existing one.
Here proper asserts needs to be there to check for new object or existing object modifications.

2) return by reference - Method can return by modifying the input object which is passed by reference.
Proper assert needs to be there to unit test the same.

3) void contract - Method doesn't return any value.
Assert all the input / class object for any modification.


Class Contract:

1) Class contract - Method can change / initialize the accessible class /static variables.
Provide proper assert to track this changes.

Exception Contract:

1) forwarded exception - Method forward the exception instead of catching.
Mock the exception and assert the same.

2) new exception - Method creates and throws new exception.
Assert the new exception whether it is thrown or not.

Behavioral Contract:

Method can have one or many behavioral contract.
For all behavioral contract proper unit test and assert needs to be there.
Example:
If null is passed as input I will return null object which caller needs to handle.
If dependent call throw exception I will not rollback the changed input /class object.

Non functional Contract:

Method can have various non functional aspect like logging , transaction, concurrency etc.

All non functional aspect should be cover as part of integration testing and it should not be included as part of unit testing.

Heap Dump Basics

Taking and using the heap dump information  is very critical for successful career in Java Development. It will be very hard to believe that if you had done successful Java development, you had never taken or analyzed heap dump.  In my career  I had done many production deployments and  solved many critical production and non production issues where I think without heap dump it will be very tough and some time almost next to impossible to solve certain issues. I had be approached by many how to take and analyse heap dump especially who are new or not mature to Java development. So this blog might help them to get some basic to some detail information about the heap dump in step by step order. Lets start with exactly what is heap dump ?.

What is Heap Dump:

Heap dump is a snapshot of the memory of a Java process at a certain point of time. It contains the information about objects loaded in memory, classes,
garbage collection roots, thread stack, local variables.
Using this information any body can get the following details:

1) How many classes loaded.
2) How many objects created.
3) How many heap space available and occupied.
4) What were the JVM parameters/options used for that run.
5) Heap used by each object type.
6) Thread status.
7) Thread call stack - exactly which method was getting evoked during that time.
8) Unreachable objects and many more. But this information is at the point of heap dump and it doesn't reflect the JVM past state.

When to take Heap Dump and why :

1) When your application seems hanging.
2) When there is sudden spike in memory.
3) When application crash due to out of memory issue.
4) When you want introspection into JVM at particular time or event.

Triggering of Heap Dump:

1) Triggering Heap Dump under live running application.
2) Triggering Heap Dump when the application goes out of memory.

Note: When you trigger the heap dump all the active threads are stopped (duration depends on the heap size) . This can lead to performance issue as many critical application cannot tolerate such activity.

Ways to Trigger Heap Dump:

Heap Dump on out of memory: 

java  -XX:+HeapDumpOnOutOfMemoryError  -XX:HeapDumpPath=   ....

or add to JAVA_OPTS if you are using tomcat etc.

Manually taking heap dump of running application:

Find the process id of the application:

jps -lv or ps aux | grep java.  -- Recommended is jps 

then take heap dump with jmap
jmap -dump:file=<> <>
jmap -dump:file=heap_dump.hprof 4208

Manually taking heap dump using the UI tool:

Start jvisualvm --> Navigate to the process --> go to monitor tab --> Click Heap Dump Button.

Knowing the object size and no of instances:

jmap -histo:live <>
jmap -histo:live 4208


Note you can also take the heap dump by enabling the JMX.

In next blog I will give the tips to analyze the Heap Dump


Thursday, June 27, 2013

MongoDB Essential Facts

Transactional Support:

1) MongoDB provide ACID in following ways:

• Atomicity - MongoDB provides only a document-wide transaction:  Writes are never partially applied to an inserted or updated document.  The operation is atomic in the sense that it either fails or succeeds, for the document in its entirety.
• Consistency – Consistency is achieved by replica set.
• Isolation – DB level locking from version 2.2 onwards
• Durability – JournalCommitInterval configuration options.

2) You need to model the data correctly to achieve the atomic operation.  Example

3) Even if  in future if there is requirement for having atomicity between multiple collections that can be achieve via two phase commit.

4) Ideally all atomic MongoDB operation should be performed from application layer or by scripting.

JOINs:

1) 1. JOIN’s requirement can be met in following ways in MongoDB:

  •  DBRefs - Referencing one document to other via _ids.
  • Embedding - Putting the data into single document. .
2) When to Embed and when to use DBRefs:
  • If the document is huge and is refereed in many document then it is prefer to use DBRef. Doing so if document needs updating it is done once rather than many times. Also it saves the disk space and performance is better as lesser I/O ( Due to document caching).
  • But if there is one-one relationship and is not refereed to other document then it is always better to embed, irrespective to size of document. If updates are zero and relationship graph is small then also it is advisable to embed the document.
  • Again it depends on the user requirement also whether there is lazy loading required, whether data is write once and read many times, read/write ratio, atomic data requirement, ACID requirement and many other factors which I leave to user to decide.
Database level locking:

 For ACID locking is much more important then any other aspect.  Previous version of MongoDB there was global read/write lock which is slow in performance but now from new release 2.2 things had improved to great extent and hope future release will also add more value.

1) From MongoDB version 2.2 there are the improvements in locking:
  •  Elimination of the global reader/writer lock.
  •  Locking at database level.
  •  PageFaultException architecture – yield lock on page fault.

2) PageFaultException architecture improves concurrency within a single collection,which meets most of normal requirements.

3) DB performance depends on the read/write ratio, so it is recommended to do benchmark before taking any decision.

4) In future MonogoDB is going to have document level locking (latching).

File Sizes :

File size matters a lot from maintenance and from performance perspective. 
Please refer the MongoDB documentation for in depth details.

1) Each BSON document has 16 MB limit in MongoDB, if your document span across 16 MB then you need to use GridFS.

2) Following are the various options to shrink the file if required:
3) Test of file size:
  • No of collection:2
  • No of Documents:
    • Collection 1: 216147
    • Collection 2: 245712
  • Total No of Keys in each collection: 30
  • Data type used: integer, date, string, double.
  • Average length of each key : 10 characters.
  • Total physical file size for that db: 256 MB (268,435,456 bytes) on Windows platform.
Above  figures are done in local isolated environment and it is better to do benchmark testing on your environment and based on your data requirement.

Security:

1) MongoDB needs to be deployed in trusted environment.
2) MongoDB provides basic support for authentication with the auth setting, which is disable by default. 
3) MongoDB can have control access in trusted environment.
4) In production MongoDB will not be installed in an environment which should not be directly accessible via web.

When to use Mongo Db :

1) When your data is not much normalized.
2) When you want to store unstructured data.
3) When you want data flexibility.
4) When you are not much concern about the ACID properties, ideal case most reads operations.
5) If you want to dump data dynamically without even thinking of the schema, ideal case dump as it is form.
6) There is no need of complex transactions.
7) If you want to achieve high scalability, but considering the above aspects.
8) If your requirement is to support high volume traffic.

It is always advisable to do performance/benchmark testing to meet your requirement. 



Friday, September 2, 2011

File Copy Using Java -- Basic and Fastest Way

Recently I had faced with situation to write simple copy command in Java, so I written the same using the plain old style and give it back to developer to implement and unit test the same. To my surprise developer comes back and reported me that my copy code is performing badly. Reason he want to use my copy code to copy at least 20 GB of file data.

So now I was faced with challenge to implement the fastest copy code. The first thing came to my mind was to use the nio API of java.

Copy using the java mainly depends on the following factors which you can control:

1) Buffer Size
2) Reading the data in efficient manner.

Buffer Size:

Buffer size is mainly dependent on operating system block size and CPU Cache. Example windows default block size is 4K (4096 Bytes).

Now if some one configure the block size of say 4500 bytes then two blocks (2* 4096) will be read and put it into the memory. So it means you had read extra bytes which will not be used and you pay the price for disk to ram read. Many times OS cache the blocks you read so next read it will be used but again here you pay the price of RAM and cost of RAM to CPU Cache copy. So ideally the buffer size should be equal to the block size or multiple of it ,so that every time you read it will be full block size read and there is no wastage. Remember cost of RAM to CPU Cache is much lesser than the cost of disk to RAM reads.

Again it depends on the CPU how it moves the data between L3 to L2 cache and complexity increases. So if you run your program under different cache size it gives different result. As per my experience the best buffer size is 8192 bytes even the java default buffer size is 8192. I will suggest to try
different buffer size and select the optimal one which best suites your environment.

Data Reading:

Most inefficient way of reading the text data is

while ((line = in.readLine()) != null) {
...
}

Here you are creating the String object every time you read a line. Lets say you are reading 10000 lines of file that means you are creating 10000 string objects and unnecessary time is spend in object creation, allocation and garbage collection. Instead of String you can use the character array.

For Binary Copy many write the code as below:
BufferedInputStream in = new BufferedInputStream(fis);
BufferedOutputStream out = new BufferedOutputStream(ost);
FileInputStream fis = new FileInputStream(src);
FileOutputStream ost = new FileOutputStream(dst);
byte[] buffer = new byte[8192];

while (true) {
int amountRead = in.read(buffer);
if (amountRead == -1) {
break;
}
out.write(buffer, 0, amountRead);
}

If you observer the out.write method the buffer and amount read are same so every time it will be flushed. So if file is huge it will have significant impact. So here while creating the new BufferedOutputStream(ost, size) use the size option and also use the write method instead of the previous one.

After the nio package is releases there are various option to read out of which one of very interesting is the ByteBuffer class.

ByteBuffer read the data repeatedly it keeps the track of the last byte written so that you can be assured of keep writing and rest it will take care. It doesn't mean that you can read endless data ByteBuffer has the limit. Once the limit is reached you need to flip.

ByteBuffer had following concept:

* Capacity - Size of the internal byte[].
* Position - Last byte index filled.
* Limit - Limit = capacity while reading and one past last filled byte while emptying.
* Mark - Bookmark ( optional )

ByteBuffer data is read from buffer starting position to read and while writing data is written from starting at ByteBuffer position upto limit. If you observer the position will advance till the limit and no further reading will be possible as position == limit. So you needs to do flip in ByteBuffer which will basically sets the position to 0 (start), and limit to the position
(previous position to be exact, or rather the end of useful input).


Different ways of Copy:

1) Conventional Way Of File Copy:


public void copyFile(File src, File dst, int buffLen) throws IOException {

BufferedInputStream in = null;
BufferedOutputStream out = null;
FileInputStream fis = null;
FileOutputStream ost = null;
byte[] buffer = new byte[buffLen];
try {
fis =
ost = new FileOutputStream(dst);
in = new BufferedInputStream(fis);
out = new BufferedOutputStream(ost);
while (true) {
int amountRead = in.read(buffer);
if (amountRead == -1) {
break;
}
out.write(buffer, 0, amountRead);
}
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
if (fis != null) {
fis.close();
}
if (ost != null) {
ost.close();
}

}
}

Here we are using the Buffered Stream to read and write.

2) Using the nio package and lets JVM decide the best way to copy:

public void copyUsingFileChannelIntenalTransfer(File source, File destination) throws IOException {
FileChannel in = null;
FileChannel out = null;
try {
in = new FileInputStream(source).getChannel();
out = new FileOutputStream(destination).getChannel();
// JavaVM does its best to do this as native I/O operations.
in.transferTo(0, in.size(), out);
// Closing file channels will close corresponding stream objects as well.
} finally {
out.close();
in.close();
}
}
Here we are depending on the JVM to decide the best buffer to make it as native I/O operation.

3) Copy using ByteChannel and Bytebuffer :
public void copyUsingByteChannel(File source, File destination, int bufferSize) throws IOException {
final InputStream input = new FileInputStream(source);
final OutputStream output = new FileOutputStream(destination);
final ReadableByteChannel inputChannel = Channels.newChannel(input);
final WritableByteChannel outputChannel = Channels.newChannel(output);
try {
final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bufferSize);
while (inputChannel.read(byteBuffer) != -1) {
// preparing for write by moving the limit to position
// and making position to zero
byteBuffer.flip();
// Writing to channel output stream.
outputChannel.write(byteBuffer);
// If there is any bytes left between postion and limit transfer
// it to front else same result as clear.
byteBuffer.compact();
}
//There are chances that after compact some bytes might left between
// position and limit so that also needs to be written.
byteBuffer.flip();
// Writing the remaining bytes if there is any
while (byteBuffer.hasRemaining()) {
outputChannel.write(byteBuffer);
}
} finally {
inputChannel.close();
outputChannel.close();
}
}
Here we are using the ByteChannel and ByteBuffer to copy please take note of comments for understanding.

4) Copy using MappedByteBuffer :

public void copyUsingMappedByteBuffer(File source, File destination) throws IOException {
FileInputStream fi = new FileInputStream(source);
FileChannel fic = fi.getChannel();
// Here also we are relying on the jvm and os to determine the
// buffer size
MappedByteBuffer mbuf = fic.map(
FileChannel.MapMode.READ_ONLY, 0, source.length());
fic.close();
fi.close();
FileOutputStream fo = new FileOutputStream(destination);
FileChannel foc = fo.getChannel();
foc.write(mbuf);
foc.close();
fo.close();
}

Here we are using the Mapped Byte Buffer and completely relying on jvm to determine the optimal buffer size.

Result:
After rigorous testing with different file size and different buffer size I came to
conclusion that the option no 3 is the fastest and consistent.
We were able to achieve more than 65% of performance improvement by opting to option no 3.
I will update this blog with the throughput data soon.

Friday, February 11, 2011

Class Loader

Class Loader

Class loader is responsible for loading the class. ClassLoader finds and loads the byte codes for the class definitions. Once loaded, they are verified before the ClassLoader can create actual classes. Now one question may arise who loads this classloader class, it is bootstrap also know as Primordial Class Loader.

Primordial Class Loader uses the native library to open and read Java class files from the disk into byte arrays and are platform depended. This class loader will load the core java class whose package starts with java.*. Once the core java classes are loaded it will try to load the extension classes which starts with javax.* and then finally application classes. All these loading works under delegate method one delegating to others.

Lets start writing our own class loader which will load the class from particular directory from the disk.

In order to write our own classloader we need to extend it from ClassLoader object

public class MyClassLoader extends ClassLoader

we had one method to load the class

protected synchronized Class loadClass(String dirName, String className, boolean resolve)
throws ClassNotFoundException {

Step 1: We need to find out whether that class is already loaded or not if loaded then just return that class.

Class cls = findLoadedClass(className);
if (cls != null) {
return cls;
}
Step 2: Getting the full fledge class name
String clsFile = className.replace('.', '/') + ".class";
clsFile = dirName+clsFile;

Step 3 : Security Checks.
Now here is the important step by default java runs with security manager disabled so if you want to run by enabling the security Manger you need to pass the following commands as arguments to java -Djava.security.manager -Djava.security.policy==/home/milind/.java.policy

Classloader should call the security manager to check whether particular classes are to be loaded or defined. So we will first call checkPackageDefinition() method of the security manager so that the security manager can prevent an untrusted class from defining classes in a particular package. and then we will call checkPackageAccess() method of the security manager so that the security manager can prevent certain classes from being loaded. By this way you can ensure that untrusted classes do not directly call classes in that package by placing the appropriate logic into the checkPackageAccess() method of your SecurityManager and also prevent an untrusted class from loading a new class into the java.lang package.

Classic example are Applet browser runs with own security manger and grants the correct access.

SecurityManager securityManager = System.getSecurityManager();
if( securityManager !=null) {
securityManager.checkPackageDefinition(packageName);
securityManager.checkPackageAccess(packageName);
}

Note securtiyManager can be null if you run the code without security manage enabled.


Step4: Getting the ByteStream of the .class file.

Step5: Converting the bytestream to java class and resolving it by calling the Classloader Methods.
cls = defineClass(className, classBytes, 0, classBytes.length);
if (resolve) {
resolveClass(cls);
}


Complete Code:
public class MyClassLoader extends ClassLoader {
private static final int BUFFER_SIZE = 8192;
protected synchronized Class loadClass(String className, boolean resolve)
throws ClassNotFoundException {
return loadClass(null, className, resolve);
}
protected synchronized Class loadClass(String dirName, String className, boolean resolve)
throws ClassNotFoundException {
// 1. is this class already loaded?
Class cls = findLoadedClass(className);
if (cls != null) {
return cls;
}
// get class file name from class name
String clsFile = className.replace('.', '/') + ".class";
if(dirName !=null) {
clsFile = dirName+clsFile;
}
String packageName = className.substring(0,className.lastIndexOf("."));
// Checking for the security
SecurityManager securityManager = System.getSecurityManager();
if( securityManager !=null) {
securityManager.checkPackageDefinition(packageName);
securityManager.checkPackageAccess(packageName);
}
// get bytes for class
byte[] classBytes = null;
try {
InputStream in=null;
if(dirName == null){
in = getResourceAsStream(clsFile);
}else {
in = new BufferedInputStream(new FileInputStream(clsFile));
}
byte[] buffer = new byte[BUFFER_SIZE];
ByteArrayOutputStream out = new ByteArrayOutputStream();
int n = -1;
while ((n = in.read(buffer, 0, BUFFER_SIZE)) != -1) {
out.write(buffer, 0, n);
}
classBytes = out.toByteArray();
}
catch (IOException e) {
}
if (classBytes == null) {
throw new ClassNotFoundException("Cannot load class: " + className);
}
// turn the byte array into a Class
try {
cls = defineClass(className, classBytes, 0, classBytes.length);
if (resolve) {
resolveClass(cls);
}
}
catch (SecurityException e) {
// loading core java classes such as java.lang.String
// is prohibited, throws java.lang.SecurityException.
// delegate to parent if not allowed to load class
cls = super.loadClass(className, resolve);
}
return cls;
}
}

Calling:

public static void main(String[] args) throws Exception {
MyClassLoader my = new MyClassLoader();
Class cls = my.loadClass("/media/disk/milind/MyCode/classloadingtest/", "com.milind.ClassTest", false);
Object obj = cls.newInstance();
Method method = cls.getMethod("getDesc");
Object returnObj = method.invoke(obj,null);
System.out.println(returnObj);
}









Thursday, February 10, 2011

String Vs StringBuilder

String Vs StringBuilder

Many time question comes into mind when to use String and when to use StringBuilder. Rule of thumb is that when you want to append the string it is best practice to use StringBuilder rather than appending the String object which is immutable.

Lets look at some example below lets to find out the performance difference between String and StringBuilder.

Testing String:

import java.util.Calendar;

public class StringTest {
public static void main(String[] args) {
long startTime = Calendar.getInstance().getTimeInMillis();
String test = "Testing";
test += "String";
test +="For Permormance";
test +="during append";
long endTime = Calendar.getInstance().getTimeInMillis();
System.out.println("Time Taken in Millisec ="+(endTime -startTime) + " to display String :" +test);
}
}

When you run that it the output will be :
Time Taken in Millisec =0 to display String :TestingStringFor Permormanceduring append


Testing StringBuilder:
import java.util.Calendar;

public class StringBuilderTest {

public static void main(String[] args) {

long startTime = Calendar.getInstance().getTimeInMillis();
StringBuilder testBuilder = new StringBuilder("Testing");
testBuilder.append("String");
testBuilder.append("For Permormance");
testBuilder.append("during append");
long endTime = Calendar.getInstance().getTimeInMillis();
System.out.println("Time Taken in Millisec ="+(endTime -startTime) + " to display String :" +testBuilder.toString());
}
}

When you run it the output will be:
Time Taken in Millisec =0 to display String :TestingStringFor Permormanceduring append

If you see the output in terms of performance is same for String and StringBuilder so question arise why not to use the String straightly rather then using the StringBuilder ?

Here is catch even when you are appending the String straightly compiler is smart enough to use the StringBuilder internally.

There is one tool javap - The Java Class File Disassembler which comes with the JDK lets use that try to find out JVM bytecode.

javap -c StringTest and the outcome will be:

public static void main(java.lang.String[]);
Code:
0: invokestatic #2; //Method java/util/Calendar.getInstance:()Ljava/util/Calendar;
3: invokevirtual #3; //Method java/util/Calendar.getTimeInMillis:()J
6: lstore_1
7: ldc #4; //String Testing
9: astore_3
10: new #5; //class java/lang/StringBuilder
13: dup
14: invokespecial #6; //Method java/lang/StringBuilder."":()V
17: aload_3
18: invokevirtual #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: ldc #8; //String String
23: invokevirtual #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
26: invokevirtual #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
29: astore_3
30: new #5; //class java/lang/StringBuilder
33: dup
34: invokespecial #6; //Method java/lang/StringBuilder."":()V
37: aload_3
38: invokevirtual #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
41: ldc #10; //String For Permormance
43: invokevirtual #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
46: invokevirtual #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
49: astore_3
50: new #5; //class java/lang/StringBuilder
53: dup
54: invokespecial #6; //Method java/lang/StringBuilder."":()V
57: aload_3
58: invokevirtual #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
61: ldc #11; //String during append
63: invokevirtual #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
66: invokevirtual #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
69: astore_3
70: invokestatic #2; //Method java/util/Calendar.getInstance:()Ljava/util/Calendar;
73: invokevirtual #3; //Method java/util/Calendar.getTimeInMillis:()J
76: lstore 4
78: getstatic #12; //Field java/lang/System.out:Ljava/io/PrintStream;
81: new #5; //class java/lang/StringBuilder
84: dup
85: invokespecial #6; //Method java/lang/StringBuilder."":()V
88: ldc #13; //String Time Taken in Millisec =
90: invokevirtual #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
93: lload 4
95: lload_1
96: lsub
97: invokevirtual #14; //Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
100: ldc #15; //String to display String :
102: invokevirtual #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
105: aload_3
106: invokevirtual #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
109: invokevirtual #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
112: invokevirtual #16; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
115: return

}


If you are familiar with assembly language you can dig it further, but if you are not it is simple to interpret if you see for each String operation += the JVM had created a separate StringBuilder object and append the given String and again assign back to the String Object using the toString(). Please refer line no 14, 34 and 54.


Now lets do the same for StringBuilder
javap -c StringBuilderTest and the outcome will be:

public class StringBuilderTest extends java.lang.Object{
public StringBuilderTest();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."":()V
4: return

public static void main(java.lang.String[]);
Code:
0: invokestatic #2; //Method java/util/Calendar.getInstance:()Ljava/util/Calendar;
3: invokevirtual #3; //Method java/util/Calendar.getTimeInMillis:()J
6: lstore_1
7: new #4; //class java/lang/StringBuilder
10: dup
11: ldc #5; //String Testing
13: invokespecial #6; //Method java/lang/StringBuilder."":(Ljava/lang/String;)V
16: astore_3
17: aload_3
18: ldc #7; //String String
20: invokevirtual #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: pop
24: aload_3
25: ldc #9; //String For Permormance
27: invokevirtual #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
30: pop
31: aload_3
32: ldc #10; //String during append
34: invokevirtual #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
37: pop
38: invokestatic #2; //Method java/util/Calendar.getInstance:()Ljava/util/Calendar;
41: invokevirtual #3; //Method java/util/Calendar.getTimeInMillis:()J
44: lstore 4
46: getstatic #11; //Field java/lang/System.out:Ljava/io/PrintStream;
49: new #4; //class java/lang/StringBuilder
52: dup
53: invokespecial #12; //Method java/lang/StringBuilder."":()V
56: ldc #13; //String Time Taken in Millisec =
58: invokevirtual #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
61: lload 4
63: lload_1
64: lsub
65: invokevirtual #14; //Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
68: ldc #15; //String to display String :
70: invokevirtual #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
73: aload_3
74: invokevirtual #16; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
77: invokevirtual #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
80: invokevirtual #16; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
83: invokevirtual #17; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
86: return

}

You can clearly see that StringBuilder Object was created only Once at line no 13 and rest of the time append was called to append it to StringBuilder.

This is a very simple code and you can see the difference when you use String instead of StringBuilder the compiler had created StringBuilder object implicitly for 3 times and use toString call also for 3 times. And in case of StringBuilder it was only once. Object creation is expensive and if the code is bigger than there can be huge difference in case of performance and memory utilization.


Conclusion:

Use StringBuilder whenever you want to append the String.