jvm architecture

virtual machine

Simulated virtual machine is to perform a certain instruction set architecture (ISA) software, is an abstraction for the operating system and hardware.

 

Such a computer system similar to the abstract object-oriented programming (OOP) is a generic programming interfaces for (or Dependency Inversion Principle), by extracting the bottom layer of abstraction to achieve some of the CCP, the underlying implementation is abstract and complete their personality section. That is to isolate the underlying implementation through a different level of abstraction. Virtual Machine Specification defines the functions (ie interfaces) This virtual machine is completed, the underlying operating system and hardware use functions provided by its own virtual machine needs to be done function (to achieve). By running on a virtual machine, Java only has a good cross-platform features.

Java Virtual Machine

Java Virtual Machine (JVM) is defined by a Java virtual machine specification, which is running on a bytecode instruction set. Such a bytecode instruction set includes byte operation code (opcode), a plurality of operands to zero (oprand), a virtual machine specification defined for each bytecode instruction is completed and how much of what function operand . Java virtual machine running on the class file that contains bytecode instruction stream and the class definition information, the Java virtual machine specification defines a class file format (accurate to each byte). Therefore, the two elements is achieved Java Virtual Machine byte code class file format and the instruction set, the Java virtual machine implemented in the correct manner as long as the file is read each class bytecode instruction, and as required to achieve the bytecode function instruction can be achieved JVM.

Currently used mainly in commercial JVM: Sun HotSpot, BEA JRocket and IBM J9. HotSpot will explain +-time compilation execution code, HotSpot when interpreted bytecode will detect hotspots (Hotspot) code, and then this part of the code is compiled to native code, after which the native code directly, rather than interpretation, and will effectively improve virtual machine performance. JRocket mainly located in the server application, it is not concerned about the start-up speed of the virtual machine, it will be all the code in-time compilation to native code execution, JRocket garbage collector has a high collection efficiency. J9 HotSpot similar positioning and focus on the desktop and server applications, mainly for a variety of IBM's Java products.

 

Java language and the Java Virtual Machine

We know that Java source code that .java files, compiled into .class files by the javac. .class files can be run on a JVM, bytecode instructions in the .class files would be performed by the underlying JVM byte code interpreter or time compiler (JIT Compiler). JVM is running above the operating system, the operating system and the underlying hardware by calling instruction set to perform services on its various software. 

 

Java language is not only run on the JVM, as long as the appropriate compiler implements Java language can run on any platform (such as J ++), it can also be compiled to native code that runs directly on top of the operating system, such as , GCJ (GNU compiler for Java) on Linux you can put Java language compiler to native code directly executed. Similarly, the JVM is not only the implementation of the Java language, as long as appropriate to achieve the compiler will compile into other languages ​​on the JVM bytecode, it can run on the JVM. Therefore, since the .class file screens differences Java, JRuby and other top languages, so Java, Groovy, etc. can call each other.

JVM lifecycle

When starting a Java program, a virtual machine instance was born, when the program is closed exits, the virtual machine instance will die. JVM instance of a Java program to run through the main () method. And the main () method must be common (public), static (static), returns void, and receives as a parameter a string array. Main Java program initial class () method, as a starting point for the reform process initial thread, any other thread is started by the first test of this thread. 
There are two internal JVM thread: thread guard and non-daemon threads. Daemon thread is usually caused by a virtual machine for their own use, such as garbage collection thread. When the program is all non-daemon threads are terminated, JVM instances will automatically exit.

Java Virtual Machine architecture

By the JVM class loader subsystem runtime data area, and a native method execution engine interface.

Class loader subsystem

类加载器子系统主要用于定位类定义的二进制信息,然后将这些信息解析并加载至虚拟机,转化为虚拟机内部的类型信息的数据结构。类加载器子系统还承担着安全性的责任,并且是JVM的动态链接和动态加载的基础。将二进制信息=>类型信息的数据结构,中间需要经过很多步骤。首先类加载器是JVM安全沙箱的第一道防线,能够防止非信任类破坏虚拟机。每一个被加载的class文件需要经过四次校验才能被加载。校验通过后,类加载器的命名空间和运行时包的特性能够防止非信任类伪装成信任类来破坏虚拟机。类加载器在方法区构造具有这个类的信息的数据结构后,会在堆上创建一个Class对象作为访问这个数据结构的接口。同时,类加载还需要初始化类的静态数据,也就是调用类的方法。以上就是一个类的加载、链接及初始化的过程。

运行时数据区

运行时数据区是JVM运行时的内存空间的组织,逻辑上又划分为多个区,这些区的生命周期和它是否线程共享有关,它们分别是:

用于存放对象或数组实例,也就是运行期间new出来的对象。堆的生命周期与JVM相同,并且在线程之间共享访问。由于多线程并发访问,所以需要考虑线程安全的问题,有两种方法。第一种是,加锁进行互斥访问。第二种是线程本地分配缓冲(Thread Local Allocate Buffer, TLAB),在线程创建时预先给每个线程分配一块区域,这块区域是线程私有的,对其他线程是不可见,也就不会被共享。JVM规范规定在申请不到足够的内存时,堆会抛出OutOfMemoryException。

方法区

存放类型信息和运行时常量池(Runtime Constant Pool)。每个被类加载器加载的类都会在方法区中形成一个与子对应的类型信息的数据结构,包括:这个类的类名、直接超类、实现的接口列表、字段列表、方法列表等。运行时常量池是class文件中的常量池列表(Constant Pool List)在运行时的一种体现,其中存储各种基本数据类型及String类型的常量以及其他类、方法、字段的符号引用。方法区的生命周期与JVM相同,被多个线程共享,所以要考虑并发访问的安全性的问题。JVM规范规定在需要的内存得不到满足的情况下,方法区会抛出OutOfMemoryException。

PC(Program Counter)

线程私有的,生命周期与线程相同,是对CPU中PC的一种模拟。如果线程正在执行的是Java方法,则该线程的PC中存放的下一条字节码指令的地址。在进行Java方法的调用和返回时,需要更新PC以保存当前方法(Current Method)正在执行的字节码指令的地址。PC是JVM规范中唯一没有规定会抛出异常的存储区。

JVM栈

线程私有,生命周期与线程相同,是对传统语言(比如C)中的方法调用栈的一种模拟。JVM栈中存放栈帧(Frame)用于进行方法调用和返回、存储局部变量以及计算的中间结果。JVM规范规定栈可以抛出两种异常:(1)StackOverflowException,在栈的深度大于某个规定值的情况下抛出。(2)OutOfMemoryException,在为新栈帧分配内存或者是为线程分配栈的内存时,申请不到足够的内存的情况下抛出。 
JVM栈中存放的是栈帧,每个栈帧对应着一次方法调用。每一时刻,JVM线程只能执行一个方法(Current Method),该方法的栈帧是JVM栈的栈顶的元素(叫做当前栈帧,Current Frame),当调用一个方法时,会初始化一个栈帧压入JVM栈;当方法调用返回或者抛出异常没有被处理的情况下,JVM栈会弹出该方法对应的栈帧。每一个栈帧中存放局部变量表(Local Variable Table)、操作数栈(Oprand Stack)以及其他栈帧信息。栈帧的大小在编译时就确定了,编译器会把局部变量表和操作数栈的大小记录在class文件中method_info的属性表中。局部变量表类似于数组存放局部变量和方法参数。由于JVM采用的是基于栈的指令集体系结构,而不是基于寄存器,所以JVM上的所有计算都是在操作数栈上进行的(比如,算术运算、方法调用、内存访问等)。

本地方法栈

用于支持本地方法调用,抛出的异常与JVM栈相同。

执行引擎

执行引擎用于执行JVM字节码指令,主要由两种实现方式: 
(1)将输入的字节码指令在加载时或执行时翻译成另外一种虚拟机指令; 
(2)将输入的字节码指令在加载时或执行时翻译成宿主主机本地CPU的指令集。这两种方式对应着字节码的解释执行和即时编译。比如在HotSpot VM中执行引擎的实现是一种解释-编译的层次结构: 
(1)解释执行:解释执行字节码,并以方法为单位收集“热点(HotSpot)代码”的信息,将“热点代码”执行C0编译。 
(2)C0编译:将收集的“热点代码”编译成本地代码,并进行一些简单的优化。继续收集运行时信息,将一些频繁执行的本地代码进行C1编译。 
(3)C1编译:将C0阶段的本地代码,进行一些比较激进的优化。如果某些优化导致本地代码执行失败,此时JVM会退化到解释执行字节码阶段。

自动内存管理

自动内存管理用于管理运行时数据区的分配和释放。和C和C++相比,Java不需要程序员主动的管理内存(在new出对象后,不需要显示的delete),这样JVM就需要承担内存管理这个任务。内存管理的重点主要是在申请内存(new对象、类加载和初始化、启动线程时初始化栈等)得不到满足时,JVM可以自动回收那些不再存活的对象所占用的内存,也就是经常听到的垃圾收集。在回收过程中还要保证处理内存空间的碎片,以提高空间利用率。回收过程主要有两个关键点,标记存活对象和回收内存的算法。

 

标记存活对象主要有引用计算和根搜索法两种。

(1)引用计数,是一种很普遍的方法,在python、lua等一些脚本语言中都是使用这种算法。每个对象持有一个计数器,标记这个对象被引用的次数。进行垃圾收集时,那些引用计数为0的对象就是“死”对象,需要被收集。引用计数的一个缺点就是它没有办法处理循环引用的情况(A->B, B->A)。

(2)根搜索,HotSpot虚拟机采用这种算法标记存活对象。把方法区、JVM栈中的所有的引用组成的集合作为搜索的根,从这个集合开始遍历直到结束。其中被遍历到的对象是存活对象;那些没有被遍历到的对象需要被垃圾收集。这样可以有效的避免循环引用的情况。

回收内存的算法主要有: 
(1)复制算法,将内存分成两个部分,每一时刻只是用其中的一个。进行回收时,将所有存活的对象依次复制到另一个部分(依次复制避免了内存碎片的产生),接下来只用这一个部分。复制算法需要在两个内存区域来回复制,有一定的复制开销和空间开销(每一时刻只使用一个区域),但是可以很好的解决内存碎片的问题,适用于对象频繁创建并且生命周期短的情况。 
(2)标记清扫,先进行存活对象标记,回收时将“死”对象占用的内存直接释放掉,会产生大量的内存碎片。 
(3)标记整理,标记阶段与标记清扫算法一样,回收阶段释放“死”对象的内存后,还需要进行对象的移动使得所有对象依次在内存中排列,避免了内存碎片的产生。标记整理与复制算法相反,适用于对象创建不频繁,生命周期长得情况。 
(4)按代收集,将内存按照对象生命周期的不同划分为多个部分,每个部分采用不同的收集算法。目前,大部分商业虚拟机都是采用这种算法。比如,在HotSpot中,内存被划分为:新生代(New)、老年代(Old)和永久代(Perm)。新生代采用复制算法,老年代和永久代采用标记整理算法。内存分配、回收的策略是,对象首先在新生代分配,如果新生代内存不满足要求,则触发一次新生代内存的垃圾收集(Young GC,或者是Minor GC)。Young GC会导致部分新生代的对象被移动至老年代,一部分是因为新生代内存不足以放下所有的对象;另一部分是因为这些对象的年龄(每个对象都保存着这个对象被垃圾收集的次数,表示它的年龄。存储在对象头的age属性中)大到足以晋升到老年代。当新生代的对象进入老年代,而老年代的内存不满足要求时,则会触发一次整个新生代和老年代的垃圾收集(Full GC, 或者是Major GC)。 
在JVM中有多个后台线程用于完成自动内存管理,对于CPU来说这些后台线程和用户线程是一样的,都需要占用系统的资源。在GC线程进行垃圾收集时必须执行“Stop the World”这一操作,也就是暂停所有的用户线程。这就导致对于实时性要求比较高的系统,JVM的垃圾收集可能是一个短板。但是在JDK1.5,Sun提供了CMS(Concurrent Mark and Sweep)垃圾收集器,通过GC线程和用户线程并发执行减少GC时间,提高了JVM的实时性。在JVM的各种应用中,gc调优是一个关键的部分,主要目标是减少GC的次数并且降低每次GC的时间。

 

JVM执行程序的流程分析

在命令行执行”java Main”就会开启一个JVM实例,我们可以通过jps,jstat等JVM工具观察JVM的运行状态,下面以运行com.ntes.money.Main这个类为例来描述一下JVM执行一个程序的流程。 
当在命令行执行”java -Xmx=12m -Xms=12m -Dname=value com.ntes.money.Main”这个命令时,JVM的执行流程是: 
1)加载JVM,主要是加载动态链接库,windows下是jvm.dll,Linux下是libjvm.so; 
2)设置JVM启动参数,比如命令中的-Xmx=12m -Xms=12m用于设置堆大小。 
3)初始化JVM。 
4)调用类加载器子系统,加载com.ntes.money.Main。这里给出的是自定义类,根据类加载器双亲委派链,最后是由系统默认类加载器(Classpath类加载器)进行加载。首先,根据全路径类型转化为文件路径com/ntes/money/Main.class,然后读取Main.class中的二进制信息、解析、加载,在方法区中形成Main类对应的数据结构。这里可能抛出ClassNotFoundException,有两种原因。一是文件路径com/ntes/money/Main.class不存在;二是com/ntes/money/Main.class文件路径存在,但是Main.class文件中存储的不是Main类的信息,比如是Main1,Main2等其他类的信息。这种情况下,会抛出NoClassDefFoundError,然后导致ClassNotFoundException。 
5)在方法区com.ntes.money.Main类对应的数据结构中,根据方法描述符及访问标志,查找main方法。这里的描述符,包括了方法的方法名、参数、返回值,也就是public static void main(String[])。如果找不到对应的main方法,会抛出NoSuchMethodError: main异常。 
6)通过本地方法(JNI)执行main方法。

 

转自:

 

Guess you like

Origin www.cnblogs.com/niwa/p/11206222.html