JVM结构
Posted ritualyang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM结构相关的知识,希望对你有一定的参考价值。
JVM结构
- JVM是 Java Virtual Machine的简称,意为Java虚拟机 。是Java可以在不同平台保证编译运行程序的保证。主要分为五大模块: 类装载器子系统、运行时数据区、执行引擎(堆操作)、本地方法接口(对接本地方法栈)和垃圾收集模块(GC操作)。
类装载器子系统
类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成
java.lang.Class
类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的newInstance()
方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。但第二次实例化一个类时,就从对应Class类newInstance(),不用每次都读取.class文件。过程
加载
装载过程负责找到二进制字节码并加载至JVM中,JVM通过类名、类所在的包名通过ClassLoader来完成类的加载,同样,也采用以上三个元素来标识一个被加载了的类:类名+包名+ClassLoader实例ID,因此不同类加载器加载相同的类是不同的。有继承,将先在加载父类。
链接
链接过程负责对二进制字节码的格式进行:校验、解析类中调用的接口、类。校验是防止不合法的.class文件,然后 对类中的所有属性、调用方法进行解析,以确保其需要调用的属性、方法存在,以及具备应的权限(例如public、private域权限等),会造成NoSuchMethodError、NoSuchFieldError等错误信息。
初始化
初始化过程即为执行类中的静态初始化代码、构造器代码以及静态属性的初始化。
在四种情况下初始化过程会被触发执行:
调用了new;
反射调用了类中的方法;
子类调用了初始化(先执行父类静态代码和静态成员,再执行子类静态代码和静态变量,然后调用父类构造器,最后调用自身构造器。);
JVM启动过程中指定的初始化类。
运行时数据区
运行时数据区具有五块区域:栈(线程),本地方法栈,程序计数器,堆,方法区。
前三者是非线程共享的( 每遇到一个线程,就为其分配一个 程序计数器 , 栈和本地方法栈 ,当线程终止时,栈,本地方法栈和程序计数器所占用的内存空间也会被释放掉。 )
后两者是线程共享的(JVM在初始运行时就分配好方法区和堆)。
栈(线程)
创建线程时被创建,由多个帧组成,称为“栈帧”。(栈帧随着方法的调用而创建,随着方法的结束而消亡)由于栈的结构机制, 每一个方法调用,就是一个压栈的操作(将该栈帧置于栈顶部),每个方法的结束就是一个弹栈的操作(执行结束将该栈帧弹出,同时释放该栈帧的内存)。 每一个栈帧有如下4个部分组成。
局部变量表:是一组变量值存储空间,用于存放方法参数和局部变量。
- 操作树栈:常称为操作数栈,是一个后入先出栈。方法执行中进行算术运算或者是调用其他的方法进行参数传递的时候是通过操作数栈进行的。在概念模型中,两个栈帧是相互独立的。但是大多数虚拟机的实现都会进行优化,令两个栈帧出现一部分重叠。令下面的部分操作数栈与上面的局部变量表重叠在一块,这样在方法调用的时候可以共用一部分数据,无需进行额外的参数复制传递。
- 动态连接:首先在虚拟机运行的时候,运行时常量池会保存大量的符号引用,这些符号引用可以看成是每个方法的间接引用,如果代表栈帧A的方法想调用代表栈帧B的方法,那么这个虚拟机的方法调用指令就会以B方法的符号引用作为参数,但是因为符号引用并不是直接指向代表B方法的内存位置,所以在调用之前还必须要将符号引用转换为直接引用,然后通过直接引用才可以访问到真正的方法。如果符号引用是在类加载阶段或者第一次使用的时候转化为直接应用,那么这种转换成为静态解析,如果是在运行期间转换为直接引用,那么这种转换就成为动态连接。
方法出口:方法返回分为两种情况,一种是正常退出退出后会根据方法的定义来决定是否要传返回值给上层的调用者,一种是异常导致的方法结束,这种情况是不会传返回值给上层的调用方法.不过无论是那种方式的方法结束,在退出当前方法时都会跳转到当前方法被调用的位置,如果方法是正常退出的,则调用者的程序计数器的值就可以作为返回地址,如果是因为异常退出的,则是需要通过异常处理表来确定。进行的操作:恢复上层方法的局部变量表以及操作数栈,如果有返回值的话,就把返回值压入到调用者栈帧的操作数栈中,还会把程序计数器的值调整为方法调用入口的下一条指令。
本地方法栈
- 虚拟机的Native方法执行的内存区。
程序计数器
- 是一个记录着当前线程所执行的字节码的行号指示器。程序计数器是线程隔离的,每一个线程在工作的时候都有一个独立的计数器。
- 编译后的字节码在没有经过JIT(实时编译器)编译前,是通过字节码解释器j进行解释执行。其执行原理为:字节码解释器读取内存中的字节码,按照顺序读取字节码指令,读取一个指令就将其翻译成固定的操作,根据这些操作进行分支,循环,跳转等动作。
- JVM的多线程是通过CPU时间片轮转来实现的,某个线程在执行的过程中可能会因为时间片耗尽而挂起。当它再次获取时间片时,需要从挂起的地方继续执行。在JVM中,通过程序计数器来记录程序的字节码执行位置。
堆(GC优化主要位置)
- 堆主要用来存储被创建的对象,一个类new出一个对象,会在堆中开辟内存空间,并在栈中存储一个引用,存储着对象在堆中的地址。堆内存中的对象存储着自己的成员变量,并不保存对象的方法,方法被保存在帧栈中,堆内存也称为gc堆,是主要用来进行垃圾回收的内存。
- 堆分为年轻代(YoungGen),老年代(OldGen)
永久代(PermGen)。 年轻代分为Eden(生成区)和Survivor(幸存区)。Survivor由FromSpace(0区)和ToSpace(1区)组成。Eden占大容量,Survivor两个区占小容量,默认比例是8:1:1。 - 新生成的对象首先放到年轻代的eden区,当年轻代eden区空间满了,触发Minor GC,存活下来的对象移动到Survivor0区,Survivor0区满后触发执行Minor GC,Survivor0区存活对象移动到Suvivor1区,这样保证了一段时间内总有一个survivor区为空。经过多次Minor GC仍然存活的对象移动到老年代。老年代存储长期存活的对象,占满时会触发Major GC=Full GC,GC期间会停止所有线程等待GC完成,所以对响应要求高的应用尽量减少发生Major GC,避免响应超时。
方法区
用于存储已被虚拟机加载的类信息、常量、静态变量、实时编译器编译后的代码等数据。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。
类型信息:
类的完整名称
类的直接父类的完整名称
类的直接实现接口的有序列表
类型标志(类类型还是接口类型)
类的修饰符(public,private,defautl,abstract,final,static)
类型的常量池
存放该类型所用到的常量的有序集合,包括直接常量(字符串、整数、浮点数)和对其他类型、字段、方法的符号引用。
字段信息(该类声明的所有字段)
字段修饰符(public、peotect、private、default)
字段的类型
字段名称
方法信息
方法信息中包含类的所有方法。
方法修饰符
方法返回类型
方法名
方法参数个数、类型、顺序等
方法字节码
操作数栈和该方法在栈帧中的局部变量区大小
异常表
类变量(静态变量)
指向类加载器的引用
指向Class实例的引用
方法表
运行时常量池(Runtime Constant Pool)
以上是关于JVM结构的主要内容,如果未能解决你的问题,请参考以下文章