Language background

Golang

Why Google created Go

  • Computer hardware technology is updated frequently and performance improves rapidly. At present, the development of mainstream programming languages ​​obviously lags behind that of hardware, and cannot reasonably utilize the advantages of multi-core and multi-CPU to improve the performance of software systems.
  • The complexity of software systems is getting higher and higher, and the maintenance costs are getting higher and higher. Currently, there is a lack of a programming language that is simple and efficient enough.
    • Existing programming languages ​​have: inconsistent styles, insufficient computing power, and insufficient handling of large concurrency.
  • Enterprises run and maintain many C/C++ projects. Although C/C++ programs run very fast, the compilation speed is indeed very slow. At the same time, there are also a series of problems such as memory leaks that need to be solved.

Go language development history

  • In 2007, Google engineers Rob Pike, Ken Thompson and Robert Griesemer began to design a new language, which was the initial prototype of the Go language.
  • On November 10, 2009, Google released the Go language to the world as open source code.
  • On August 19, 2015, Go version 1.5 was released. The “last remaining c code” was removed in this update.
  • On February 17, 2017, Go language Go1.8 version was released.
  • On August 24, 2017, Go language version Go1.9 was released.
  • On February 16, 2018, Go language Go1.10 version was released.
  • As of now, the latest version of Go language v1.22.1 has been released.

Java

The history of Java can be traced back to the early 1990s. Here are some of the key moments and stages of Java’s development:

  • 1991: Oak project launched: Sun Microsystems engineers such as James Gosling, Mike Sheridan, and Patrick Naughton started a project called Oak to develop a language for embedded systems.
  • 1994: The birth of the Java language: The Oak project evolved into the Java project, and the language was officially released in May 1995 under the leadership of Sun Microsystems’ Java team. Java is designed to be a simple, object-oriented, portable, high-performance programming language.
  • 1995: Public release: Java version 1.0 was officially released, introducing many essential features that are still around today, such as the Java Virtual Machine (JVM) and Applet technology.
  • 1997: Java 1.1 is released: Features such as inner classes, JavaBeans, and RMI (remote method invocation) are introduced to enhance the functionality of the language.
  • 1998: Java 2 is released: Java 2 (J2SE 1.2) is released, introducing important features such as Swing GUI toolkit, collection framework, Java Naming and Directory Interface (JNDI).
  • 2000: Java 2 Enterprise Edition (J2EE) is released: It is launched for enterprise-level application development and introduces technologies such as Servlet and JSP (JavaServer Pages).
  • 2004: Java 5.0 is released: Important language features are introduced, such as generics, enumerations, annotations, autoboxing and unboxing, enhanced for loops, etc.
  • 2006: Java 6 is released: Introducing new features such as updates to the Java virtual machine, Scripting API, and Java Compiler API.
  • 2011: Java 7 is released: Important language features are introduced, such as try-with-resources statement, diamond operator, string support in switch statement, etc.
  • 2014: Java 8 was released: Important features such as Lambda expressions, Stream API, new Date and Time API were introduced, and major upgrades to the language and libraries were made.
  • 2017: Java 9 is released: Introducing new features such as a modular system, improved Javadoc, and collection factory methods.
  • 2018: Java 10 released: Introducing new features such as local variable type inference and an improved garbage collector.
  • 2019: Java 11 is released: New features such as HTTP Client API, upgrades to local variable type inference, and dynamic class file constants are introduced. This release marks Oracle’s commitment to long-term support (LTS) releases.
  • 2021: Java 17 released: As a long-term support (LTS) version, features such as Sealed Classes, Pattern Matching for Switch, and new garbage collector are introduced.

The development of Java has been ongoing and has maintained a trend of advancing with the times. It is widely used in enterprise applications, mobile development, big data, cloud computing and other fields, becoming one of the most popular programming languages ​​in the world.

Language Features

Features of Go language

  • Go is also a static compiled language with syntax similar to C. It inherits many concepts from the C language, including expression syntax, control structures, basic data types, call parameter value transfer, pointers, etc.
  • Garbage collection mechanism, automatic memory recycling
  • Natural concurrency [Important Features]
    • Supports concurrency from the language level and is simple to implement
    • Goroutine, a lightweight thread, can achieve large concurrent processing and efficiently utilize multiple cores.
    • Implemented based on CSP concurrency model (Communicating Sequential Processes, communication sequence process)
  • It absorbs the pipeline communication mechanism and forms a pipeline channel unique to the Go language. Through the pipeline channel, mutual communication between different goroutes can be realized.
  • Function returns multiple values
  • New innovations: such as slicing, delayed execution of defer, etc.

Features of Java language

1.Simple 

Java was originally designed as a language for integrated control of home appliances, so it had to be simple and clear. The simplicity of the Java language is mainly reflected in the following three aspects: 

  • Java’s style is similar to C++, so C++ programmers are very familiar. In a sense, the Java language is a variant of the C and C++ languages. Therefore, C++ programmers can quickly master Java programming technology. 
  • Java abandons areas in C++ that are prone to program errors, such as pointers and memory management. 
  • Java provides a rich class library. 

  2. Object-oriented 

Object-oriented is arguably the most important feature of Java. The design of the Java language is completely object-oriented, and it does not support process-oriented programming technology like the C language. Java supports both static and dynamic styles of code inheritance and reuse. From the perspective of object-oriented features alone, Java is similar to Small Talk, but other features, especially those suitable for distributed computing environments, far surpass Small Talk. 

3.Portable

The original design intention of “compile once, run anywhere” is that after the code is compiled into bytecode, it can be run on the JVM virtual machine. Ignore differences in system environments.

4. Interpretation and execution + just-in-time compilation

At the beginning of the program running, the JVM bytecode generated by javac (front-end compiler) is run through the interpreter in the JVM. The interpreter will interpret our code sentence by sentence into binary code that the machine can recognize for execution. It can be considered Yes, explain one sentence and execute one sentence, which is less efficient.  

During the running of the program, the bytecode is converted into machine code through a just-in-time compiler (JIT). Before JDK 1.7, JIT just-in-time compilation was mainly the C1 and C2 compilers of HotSpot VM. After JDK 1.7, layered compilation was introduced, combining C1 and C2 to better improve the compilation and running efficiency of the program.

5. Dynamic compilation

Dynamic compilation simply means compiling source code when a Java program is running.

Starting from JDK1.6, a rewritten compiler interface for Java code has been introduced, allowing us to compile Java source code at runtime, and then load the compiled classes into the JVM through the class loader. This kind of compilation at runtime The operation of code is called dynamic compilation.

6.Multi-threading

The Java language is multi-threaded, which is also a major feature of the Java language. It must be created by the Thread class and its subclasses. Java supports multiple threads executing simultaneously and provides a synchronization mechanism between multiple threads.

7. Garbage collection

The Java language supports a variety of garbage collection mechanisms, which does not require developers to manually reclaim memory. These include CMS, G1, ZGC

8. Annotations

Java annotation (Annotation), also known as Java annotation, is an annotation mechanism introduced in JDK5.0. Classes, methods, variables, parameters and packages in the Java language can all be annotated. Unlike Javadoc, Java annotations can obtain annotation content through reflection. Annotations can be embedded into the bytecode when the compiler generates the class file

Typical projects

Golang

  • Kubernetes

Kubernetes , also known as K8s, is an open source system for managing containerized applications across multiple hosts. It provides basic mechanisms for application deployment, maintenance and expansion

  • Docker

Docker is used to develop applications, ship applications, and run applications. Docker allows users to separate applications in the infrastructure into smaller particles (containers), thereby increasing the speed of software delivery  

  • Etcd

Etcd is a distributed and reliable key-value storage system for the most critical data in distributed systems.

  • tidb

Tidb is an open source distributed SQL database that supports hybrid transactional and analytical processing (HTAP) workloads. It is compatible with MySQL and has horizontal scalability, strong consistency and high availability.

Java

  • Spring

Spring provides everything you need beyond the Java programming language to create enterprise applications for a variety of scenarios and architectures

  • Spring boot

Spring Boot helps you create Spring-powered production-grade applications and services with minimal hassle. It has its own take on the Spring platform so that new and existing users can quickly get the pieces they need

  • Elasticsearch

Elasticsearch is a distributed RESTful search and analytics engine at the heart of the Elastic Stack

  • Dubbo

Apache Dubbo is a high-performance, java-based open source RPC framework

coding

Keywords

golang has 25 keywords

java has 51 keywords

Statute

Golang is a strict engineering language, mainly reflected in coding style and visible domain rules. In Java, multiple coding styles are allowed to coexist. For example, the following two method declarations are allowed for Java:

public String getString(Integer num) {
    return num.toString();}
public String getString(Integer num) {
    return num.toString();
}

In Golang, the newline style will be strictly checked. If the above situation occurs, a compilation error will be directly prompted.

variable declaration

In Java, variables can be declared but not used, while variables declared in Golang must be used, otherwise you need to use _ to replace the variable name, indicating that the variable will not be used:

func getString(num int) string {
	temp := num 
	_ := num	
	return strconv.Itoa(num)}

Golang supports two methods of variable declaration. Method 2 will have the compiler automatically deduce the type, which is more concise.

 1: var a string
 2:b := 1

In Golang, for basic types, declaration is initialization; for reference types, declaration is initialized to nil. In Java, if a variable is declared inside a method but not initialized, a compilation error will occur when using it:

public void solve() {
    int num;
    Object object;
    System.out.println(num); 
    System.out.println(object); 
 }

Visible domain rules

Java’s visible domain rules for methods, variables and classes are controlled through the private, protected, and public keywords, while there is only one way to control the visible domain in Golang. When the first letter of a field starts with a capital letter, it means that it is visible to the outside world. When lowercase, it is only visible to members within the package.

Parameter passing

There are two types of parameter passing in Java, one is passing by reference and the other is passing by value. In Golang, everything is passed by value.

For example, arrays are passed by reference in Java. When an array is passed as a parameter to a method, the contents of the array can be modified inside the method. In Golang, a complete copy of the array is carried out in the method.

However, there is the concept of pointers in Golang, and the original content of the parameters can be modified by passing the pointer of the parameter. However, this is also a value transfer, which essentially makes a deep copy of the address of the pointer. It is equivalent to two pointers pointing to the real memory address of the parameter at the same time.

Structure declaration and usage

The most significant difference between Golang and Java is that Golang does not have the concept of “class”. The structure that organizes data entities is called a structure in Golang. Functions can exist without “classes”, and functions can be called depending on the structure or the package name.

Structures in Golang give up polymorphic concepts such as inheritance and implementation. Structures can be combined to achieve the effect of reusing methods or fields.

To declare a structure just use the type + struct keyword:

type Person struct {
	Name string
	Age  int
	id   string}

It is also very simple to use a structure. Generally, there are the following ways to create a structure:

  personPoint := new(entity.Person) 
	person1 := entity.Person{} 
	person2 := entity.Person{ 
		Name: "ARong",
		Age:  21,
	}

Exception handling

In Java, exceptions are handled through try catch. There is no concept of try catch in the go language, because try catch consumes more resources, and no matter where it jumps out from the try, it is a violation of the normal structure of the code. kind of destruction.

Therefore, the design philosophy of the go language advocates that if an exception may occur in a function, the exception should be used as the return value. If there is no exception, nil will be returned. Every time a function that may be abnormal is called, it should be proactively checked and reacted.

type Student struct {
   name string
}
func main() {
   name, err := GetName(&Student{})
   if err != nil {
      fmt.Println("error", err)
      return
   }
   fmt.Println(name)
}


func GetName(s *Student) (string, error) {
   if s.name == "" {
      return "", errors.New("not found name")
   }
   return "Evan", nil
}

Rewriting and reloading

There is no concept of inheritance in Golang, but the effect of “inheritance” can be achieved through structure combination.

type Parent struct {
   name string
}
type Child struct {
   Parent
   name string
}
func (p *Parent) Name() string {
   return p.name
}
func (c *Child) Name() string {
   return c.Parent.Name()
}

Overloading is not as flexible as Java. The requirement for overloading in Java is: in a class, the method names are the same but the parameters are different. The return types can be the same or different.

Similar effects can be achieved through interfaces in Golang, but the method parameters and return values ​​must be the same

type Info interface {
   Name() string
}
type A struct {
   name string
}
type B struct {
   firstName string
   lastName  string
}
func (a *A) Name() string {
   return a.name
}
func (b *B) Name() string {
   return b.firstName + b.lastName
}

Multithreading

When using multi-threading in Java, if you do not consider the thread pool, you also need to inherit the Thread class or implement the Runnable/Callable interface, which is relatively cumbersome.

public class Task extends Thread {
    @Override
    public void run() {
    }
    public static void main(String[] args) {
        new Task().start();
    }
}

If you consider resource issues (for example, starting too many threads may cause frequent CPU context switching, which in turn reduces performance. Creating too many threads will also consume a lot of memory), you also need to consider creating threads through a thread pool, which is more cumbersome.

In Golang, through golang’s own GMP scheduling mechanism, users only need to use the Go keyword to create goroutine coroutines. The coroutines are very lightweight, and the initial size of each coroutine is about 2kb.  

Coroutines (user-level threads) are transparent to the kernel, that is, the system does not know the existence of coroutines and is completely scheduled by the user’s own program. In golang, the coroutine is enabled through the go keyword. The coroutine belongs to user mode and reduces CPU switching in multi-thread situations. It works better in high-concurrency scenarios.

func main() {
   for i := 0; i < 100; i++ {
      go func() {
         fmt.Println("hello")
      }()
   }
}  

Garbage collection

There are garbage collection mechanisms in java and golang. Common garbage collection algorithms are as follows:

  1. Mark-clear: Traverse all referenced objects starting from the root variable, mark the referenced objects, and recycle those that are not marked.
    • Advantages: The algorithm is simple.
    • Disadvantages: requires STW, unstable efficiency, fragmented memory space.
  2. Mark-copy: In order to solve the problem of low execution efficiency of the mark-sweep algorithm when faced with a large number of recyclable objects, Fenichel proposed a garbage collection algorithm called “Semispace Copying” in 1969, which combines the available memory with Divide it into two pieces of equal size according to capacity, and only use one piece at a time. When this block of memory runs out, copy the surviving objects to another block, and then clean up the used memory space at once.
    • Advantages: simple implementation, efficient operation, no fragmentation
    • Disadvantages: serious waste of space, available memory reduced to half of the original
  3. Mark-organize: The marking process is still the same as the “mark-clear” algorithm, but the subsequent steps are not to directly clean up the recyclable objects, but to move all surviving objects to one end of the memory space, and then directly clean up outside the boundaries. of memory.
    • Advantages: No memory fragmentation
    • Disadvantages: Not suitable for some scenarios. If there are a large number of surviving objects when recycling, then the objects need to be moved and all references updated, which is inefficient because this operation requires STW.

Java

There are multiple garbage collectors in Java such as Serial, ParNew, CMS, G1, and ZGC. In fact, CMS, G1, and ZGC use the idea of ​​generational garbage collection. Distinguish “live and die” objects and “enduring” objects according to different memory areas.

  The CMS collector is a collector implemented based on the mark-and-sweep algorithm (with fragmentation problems), and the old generation is implemented using the mark-and-compact algorithm.

In the G1 collector (introduced in 1.7, enabled by default in 1.9), it follows the generational collection theory design, but downplays the concept of heap memory divided by generations. G1 no longer adheres to a fixed size and a fixed number of generational area divisions, but Divide the continuous Java heap into multiple independent regions (Regions) of equal size. Each Region can act as the Eden space, Survivor space, or old generation space of the new generation as needed. With the concept of Region, after recycling During the process, you only need to copy the surviving objects in the Region that need to be recycled to the empty Region, and then clean up the entire old Region space. Equivalent to the G1 collector, which uses the mark-copy+clear algorithm.

The ZGC collector is based on the Region memory layout (the same as G1), without generation. It uses technologies such as read barriers, dyed pointers, and memory multiple mapping to implement concurrent marking algorithms, with low latency as the primary goal. a garbage collector

Golang

In Golang, the garbage collection algorithm chooses mark-and-clear, marking through the “three-color marking” algorithm, and solving the problem of memory fragmentation as much as possible through a multi-level memory allocation algorithm. Use write barrier + auxiliary GC to shorten STW

memory allocation

Memory allocators in programming languages ​​generally include two allocation methods, one is linear allocator (Sequential Allocator, Bump Allocator), and the other is free-list allocator (Free-List Allocator)

Linear distributor

Linear allocation (Bump Allocator) is an efficient memory allocation method, but it has major limitations. When we use a linear allocator, we only need to maintain a pointer to a specific memory location in the memory. If the user program applies for memory from the allocator, the allocator only needs to check the remaining free memory, return the allocated memory area and modify the pointer in The location in memory, i.e. moving the pointer in the image below:

Although the linear allocator implementation brings it faster execution speed and lower implementation complexity, the linear allocator cannot reuse memory when it is released. As shown in the figure below, if the allocated memory is reclaimed, the linear allocator cannot reuse the memory in red:

Because linear allocators have the above characteristics, they need to be used in conjunction with appropriate garbage collection algorithms, such as Mark-Compact, Copying GC, and Generational GC. They can be copied through Use this method to defragment the surviving objects and merge the free memory regularly, so that the efficiency of the linear allocator can be used to improve the performance of the memory allocator.

free list allocator

The Free-List Allocator can reuse memory that has been released. It maintains a data structure similar to a linked list internally. When a user program applies for memory, the free linked list allocator will traverse the free memory blocks in order to find a memory large enough, then apply for new resources and modify the linked list:

Because different memory blocks form a linked list through pointers, an allocator using this method can reuse recycled resources. However, because it needs to traverse the linked list when allocating memory, its time complexity is 𝑂(𝑛)

The free list allocator can choose different strategies to select among memory blocks in the linked list, the most common ones are the following four:

  • First-Fit – Traverse from the head of the linked list and select the first memory block whose size is larger than the requested memory;
  • Loop first adaptation (Next-Fit) – Traverse from the end of the last traversal and select the first memory block whose size is larger than the requested memory;
  • Best-Fit – traverse the entire linked list from the head of the linked list and select the most appropriate memory block;
  • Segregated-Fit – Divide the memory into multiple linked lists. The memory blocks in each linked list are of the same size. When applying for memory, first find a linked list that meets the conditions, and then select the appropriate memory block from the linked list;

The first three of the above four strategies will not be introduced in detail. The memory allocation strategy used by the Go language is somewhat similar to the fourth strategy. We understand the principle of this strategy through the following figure:

As shown in the figure above, this strategy will divide the memory into a linked list consisting of 4, 8, 16, and 32-byte memory blocks. When we apply for 8-byte memory from the memory allocator, it will be found in the figure above Free memory blocks that meet the conditions are returned. The isolation adaptive allocation strategy reduces the number of memory blocks that need to be traversed and improves the efficiency of memory allocation.

**java uses a linear allocator, and golang uses a free linked list allocator, which solves the problem of memory fragmentation to a certain extent**  

Golang memory allocation related articles: draveness.me/golang/docs…

Threading model

Implemented using user threads (many-to-one model M:1)

Broadly speaking, as long as a thread is not a kernel thread, it can be a type of user thread (User Threa, UT). Multiple user threads are mapped to one kernel thread. User threads are built on the thread library of user space. The establishment, synchronization, destruction and scheduling of user threads are completely completed in user mode and are transparent to the kernel. If the program is implemented properly, there is no need to switch the kernel state, so the operation can be very fast and low-cost, and can also support a larger number of threads. Multi-threading in some high-performance databases is implemented by user threads.

advantage:

  1. Thread context switching occurs in user space, avoiding mode switching and reducing performance overhead.
  2. The creation of user threads is not limited by kernel resources and can support a larger number of threads.

shortcoming:

  1. All threads are based on one kernel scheduling entity, which is the kernel thread. This means that only one processor can be utilized, which wastes other processor resources. Parallelism is not supported. This is unacceptable in a multi-processor environment. If threads Because the I/O operation falls into the kernel state and the kernel state threads are blocked waiting for I/O data, all threads will be blocked.
  2. Adding to the complexity, all thread operations need to be handled by the user program itself, and it is extremely difficult to implement “mapping threads to other processors when blocked” in user space.

Implemented using kernel threads (one-to-one model 1:1)

Implementation using kernel threads is called 1:1 implementation. Kernel threads (Kernel Levvel Thread, KLT ) are threads directly supported by the operating system kernel (Kernel, hereinafter referred to as the kernel). The kernel schedules threads by manipulating the scheduler ( Scheduler ) and is responsible for mapping the thread’s tasks to each processor. superior. In fact, programs generally do not use kernel threads directly. What programs use is a high-level interface of kernel threads – lightweight process (Light Weight Process, LWP). A lightweight process is what we usually call a thread. The magnitude process also belongs to the user thread.

Each user thread is mapped to a kernel thread, and each thread becomes an independent scheduling unit and is independently scheduled by the kernel scheduler. The blocking of one thread will not affect other threads, thus ensuring that the entire process continues to work.

advantage:

  1. Each thread becomes an independent scheduling unit. Using the thread scheduling function and processor mapping provided by the kernel, thread switching can be completed, and thread tasks can be mapped to other processors, making full use of the advantages of multi-core processors to achieve True parallelism.

shortcoming:

  1. Every time a user-level thread is created, a corresponding kernel-level thread needs to be created, which consumes a certain amount of kernel resources. Kernel resources are limited, so the number of threads that can be created is also limited.
  2. Mode switching is frequent. Various thread operations, such as creation, destruction and synchronization, require system calls. Frequent switching between user mode and kernel mode is required, which is expensive.

Use user threads and lightweight process hybrid implementation (many-to-many model M:N)

The number ratio of kernel threads to user threads is M:N. This model requires the kernel thread scheduler and the user space thread scheduler to interoperate. In essence, multiple threads are mapped to multiple kernel threads.

It combines the advantages of the previous two models:

  1. The creation, switching, destruction and synchronization of user threads still occur in user space, which can create a larger number of threads and support larger-scale concurrency.
  2. Most thread context switching occurs in user space, reducing the overhead caused by mode switching.
  3. You can use the thread scheduling function and processor mapping provided by the kernel to make full use of the advantages of multi-core processors to achieve true parallelism and reduce the risk of the entire process being completely blocked.

Java uses the 1:1 model, while golang uses the M:N model.

Leave a Reply

Your email address will not be published. Required fields are marked *