JVM原理学习笔记

一、JVM的组成:

JVM 由类加载器子系统、运行时数据区、执行引擎以及本地方法接口组成。

1.类加载器 顾名思义就是用来装载 .class 文件的。JVM包括两种类装载器:启动类装载器和用户自定义装载器,启动类装载器是JVM实现的一部分,用户自定义类装载器则是Java程序的一部分,必须是ClassLoader类的子类。

装载过程负责找到二进制字节码并加载至JVM中,JVM通过类名、类所在的包名通过ClassLoader来完成类的加载,同样,也采用以上三个元素来标识一个被加载了的类:类名+包名+ClassLoader实例ID

启动装载器:只在系统类(JavaAPI的类文件)的安装路径查找要装入的类

用户自定义类装载器:在JVM启动的时候加载 从classPath文件中查找要加载的类目录

其他用户自定义类装载器:这里有必要先说一下ClassLoader类的几个方法,了解它们对于了解自定义类装载器如何装载.class文件至关重要。

defineClass 此方法负责将二进制的字节码转换为Class对象 也就是将二进制class文件(新类型)导入到方法区,也就是这里指的类是用户自定义的类(也就是负责装载类)

findSystemClass通过类型的全限定名,先通过系统类装载器或者启动类装载器来装载,并返回Class对象。

ResolveClass:让类装载器进行连接动作(包括验证,分配内存初始化,将类型中的符号引用解析为直接引用),这里涉及到Java命名空间的问题,JVM保证被一个类装载器装载的类所引用的所有类都被这个类装载器装载,同一个类装载器装载的类之间可以相互访问,但是不同类装载器装载的类看不见对方,从而实现了有效的屏蔽

loadClass此方法负责加载指定名字的类,ClassLoader的实现方法为先从已经加载的类中寻找,如没有则继续从parent ClassLoader中寻找,如仍然没找到,则从System ClassLoader中寻找,最后再调用findClass方法来寻找,如要改变类的加载顺序,则可覆盖此方法

 findClass此方法直接抛出ClassNotFoundException,因此需要通过覆盖loadClass或此方法来以自定义的方式加载相应的类。



2,运行时数据区

主要包括 方法区 堆 Java栈  PC寄存器  方法栈

方法区:方法区主要存放 被虚拟机加载的类信息,常量,运行时常量池,静态变量,即时编译器编译后的代码等。 

对象中的getNameisInterface等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在一定的条件下它也会被GC如果空间不足抛出异常信息:java.lang.OutOfMemoryError: PermGen space

通过调整JVM的参数-XX:PermSize=512m -XX:MaxPermSize=1024m 来解决

堆:存放所有程序在运行时创建的对象 Java中所有通过new创建的对象的内存都在此分配,Heap中的对象的内存需要等待GC进行回收。

如果堆空间不足会抛出异常信息 java.lang.OutOfMemoryError: Java heap space

(1)堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的(2)Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLABThread Local Allocation Buffer),其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配

(3) TLAB仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。


栈:栈是存放主要是栈帧(局部变量表(基本数据类型,对象引用,returnAddress类型),操作数栈,动态链接,方法出口信息)的地方

栈有三个区域:局部变量区、运行环境区、操作数区

JVM栈是线程私有的,每个线程创建的同时都会创建JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:booleancharbyteshortintlongfloatdouble)、部分的返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址


当请求栈的空间大于了虚拟机所允许的时候会抛出StackOverflowError,当不能申请到更多的内存时,会抛出我们熟悉的OutOfMemoryError,

StackOverflowError->VirtualMachineError->Error

catch(Exception ex)catch不到Error 需要用 catch (Throwable ex) 来catch

PC寄存器:是用于存储每个线程下一步将执行的JVM指令,如该方法为native的,则PC寄存器中不存储任何信息。PC寄存器的内容总是指向下一条将被执行指令的地址

本地方法堆栈(Native Method StacksJVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每个native方法调用的状态。

GC 的基本原理: 将内存中不再被使用的对象进行回收, GC 中用于回收的方法称为收集器,由于 GC 需要消耗一些资源和时间, Java 在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短 GC 对应用造成的暂停


二、JVM的运行原理:

JVM是java的核心和基础,在java编译器和OS(Operating System)平台之间的虚拟处理器。它是一种基于下层的操作系统和硬件平台并利用软件方法来实现的抽象的计算机,可以在上面执行java的字节码程序。java编译器只需面向JVM,生成JVM能理解的代码或字节码文件。Java源文件经编译器,编译成字节码程序,通过JVM将每一条指令翻译成不同平台机器码,通过特定平台运行。


三、JVM的生命周期

JVM实例的诞生:当启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点,既然如此,那么JVM如何知道是运行classA的main而不是运行classB的main呢?这就需要显式的告诉JVM类名,也就是我们平时运行Java程序命令的由来,如Java classA helloworld,这里Java是告诉os运行SunJava2SDK的Java虚拟机,而classA则指出了运行JVM所需要的类名。

JVM实例的运行:main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,Java程序也可以标明自己创建的线程是守护线程。

 每一个Java虚拟机都由一个类加载器子系统(class loader subsystem),负责加载程序中的类型(类和接口),并赋予唯一的名字。每一个Java虚拟机都有一个执行引擎(execution engine)负责执行被加载类中包含的指令。
     程序的执行需要一定的内存空间,如字节码、被加载类的其他额外信息、程序中的对象、方法的参数、返回值、本地变量、处理的中间变量等等。Java虚拟机将 这些信息统统保存在数据区(data areas)中。虽然每个Java虚拟机的实现中都包含数据区,但是Java虚拟机规范对数据区的规定却非常的抽象。许多结构上的细节部分都留给了 Java虚拟机实现者自己发挥。不同Java虚拟机实现上的内存结构千差万别。一部分实现可能占用很多内存,而其他以下可能只占用很少的内存;一些实现可 能会使用虚拟内存,而其他的则不使用。这种比较精炼的Java虚拟机内存规约,可以使得Java虚拟机可以在广泛的平台上被实现。
     数据区中的一部分是整个程序共有,其他部分被单独的线程控制。每一个Java虚拟机都包含方法区(method area)和堆(heap),他们都被整个程序共享。Java虚拟机加载并解析一个类以后,将从类文件中解析出来的信息保存与方法区中。程序执行时创建的 对象都保存在堆中。 
     当一个线程被创建时,会被分配只属于他自己的PC寄存器“pc register”(程序计数器)和Java堆栈(Java stack)。当线程不掉用本地方法时,PC寄存器中保存线程执行的下一条指令。Java堆栈保存了一个线程调用方法时的状态,包括本地变量、调用方法的 参数、返回值、处理的中间变量。调用本地方法时的状态保存在本地方法堆栈中(native method stacks),可能再寄存器或者其他非平台独立的内存中。
     Java堆栈有堆栈块(stack frames (or frames))组成。堆栈块包含Java方法调用的状态。当一个线程调用一个方法时,Java虚拟机会将一个新的块压到Java堆栈中,当这个方法运行结束时,Java虚拟机会将对应的块弹出并抛弃。
     Java虚拟机不使用寄存器保存计算的中间结果,而是用Java堆栈在存放中间结果。这是的Java虚拟机的指令更紧凑,也更容易在一个没有寄存器的设备上实现Java虚拟机。 

JVM实例的消亡:当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用Runtime类或者System.exit()来退出。Runtime类封装了运行时的环境。每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。

Runtime:    一般不能实例化一个Runtime对象,应用程序也不能创建自己的 Runtime 类实例,但可以通过 getRuntime 方法获取当前Runtime运行时对象的引用。
      一旦得到了一个当前的Runtime对象的引用,就可以调用Runtime对象的方法去控制Java虚拟机的状态和行为。

    exit(int status) 通过启动虚拟机的关闭序列,终止当前正在运行的 Java 虚拟机。 
    freeMemory() 返回 Java 虚拟机中的空闲内存量。 

    maxMemory() 返回 Java 虚拟机试图使用的最大内存量。 

    gc()   运行垃圾回收器。

    halt(int status)  强行终止目前正在运行的 Java 虚拟机。 

    load(String filename)  加载作为动态库的指定文件名。 
    loadLibrary(String libname)  加载具有指定库名的动态库。 



猜你喜欢

转载自blog.csdn.net/simon47/article/details/53535539