Sign in
Log inSign up

Go vs Java in terms of memory efficiency. Beauty of go routines and their variable stack size limit.

PRASHANTH H N's photo
PRASHANTH H N
·Mar 12, 2019

Concurrency can be achieved in different ways. Different languages/technologies take different approaches to achieve concurrency.

For example, C/C++, Java,etc. will create OS threads to achieve Concurrency. But they have fixed stack size. This limits the number of threads that our application could create.

Java achieves concurrency by creating OS threads. Thread stack size is configurable in Java. To understand this better look for Source1 and Source2 .

The main take away from these sources is that we can configure the stack size limit but it will be applicable to all the threads. We cannot have each threads to have its own stack size limit.

Coming to Go, it achieves concurrency in a different way. It does not create OS threads. Instead, it creates a much lighter go routines. Go has its own scheduler to have multiple routines run time efficiently. Go routines can have stack size of as small as 4 kB which can be extended till 1000000000 bytes.That’s cool.

I have done a small experiment to understand this better. Here is the link to the same — github.com/PraGitHub/Prapository/tree/mast…

Go Program :

package main

import(
    "os"
    "strconv"
)

var count int64 = 0

func stackOverflow(){
    count = count + 1
    if count%1000 == 0{
        file,_ := os.Create("./results/go/stackOverflow.go.result"+os.Args[1])
        defer file.Close()
        file.Write([]byte(strconv.FormatInt(count,10)))
        file.Sync()
    }
    stackOverflow()
}

func main(){
    stackOverflow()
}

Java Program :

import java.io.*;
public class main {

    static String fileName;
    static int count = -1;
    static FileOutputStream result = null;

    public static void stackOverflow()throws IOException{
        count++;
        if(count%1000 == 0){
            try{
                result = new FileOutputStream("./results/java/stackOverflow.java.result."+fileName);
            }
            catch(FileNotFoundException ex){
            }
        result.write(new Integer(count).toString().getBytes());
        result.close();
        stackOverflow();
    }

   public static void main(String args[]) {  
      fileName = args[0];
      File file = new File("./results/java/stackOverflow.java.result."+fileName);
      try{
        file.createNewFile();
        stackOverflow();
      }
      catch(IOException ex){
      }
   }
}

There is a function stackOverFlow(), which is called infinitely recursively.

  • In java, 7000+ to 10000+ recursive calls before the stack got overflown. ( this is with the default stack size limit which is 1024 kB).

  • In go, it is able to do 6710000+ iterations before the stack got overflown. This is the beauty of go language.

  • Results are here .

So what ?

To answer the above question running in your mind, let us consider that we have 2 web servers,

  1. Go web server : Written in go. This is going to handle concurrency by creating go routines which can have variable stack size ( ranging from 4 kB to 1 GB ).

  2. Java web server : Written in java. This is going to achieve concurrency by creating OS thread which will have a fixed stack size. Let us consider that we have configured the stack size to be 256 kB.

We are going to host them in two different machines that are identical in terms of hardware.

Let us consider that our application is going to get an amount of 256 MB of stack size.

Whenever a client is connected to java web server, it spawns an OS thread and whenever a client is connected to go web server, it will create a go routine.

Practically, in order to serve a client, the thread need not have to have 256 kB. However, java server will spawn a thread with fixed stack size of 256 kB. As the stack size is limited to 256 MB, it can have only around 1000 threads (theoretically it is 1024) running concurrently. In case if we get into a scenario where in we have more than 1000 users then the java web server is going to hang/crash. Go web server will create go routines which will have variable stack size starting from as small as 4 kB. On an average, let us say that every go routine is going to consume 16 kB of stack size. Considering these figures, our go web server is not going to hang/crash if we have 1000 clients. It is going to take only 16 MB.

This is the primary reason why any application written in go can spawn millions(literally) of go routines while applications written in Java, C++,etc… fail.

Considering the following facts :

  • OS has a fixed stack size for threads

  • Go routine can have stack size upto 1 GB

  • Ultimately go program has to run on OS threads

The amount of memory required for go routine has to be created in Heap. I am not sure about whether memory allocation for go routines is done in heap or not, please let me know if it is right.