JVM:Java Virtual Machine,Java虚拟机。
JVM:Java Virtual Machine,Java虚拟机。
作用:由类装载器装载class字节码文件,通过执行引擎执行被装载类中方法中的指令。生命周期:启动一个Java程序,就会创建一个虚拟机实例,该程序关闭,虚拟机实例随之消亡。
虚拟机实例通过调用某个初始类的main方法来运行一个Java程序.
JVM模块介绍:
1:类装载子系统:根据给定的类名或者接口名装载class字节码文件。2:执行引擎:负责执行被装载类中的方法的指令。
3:方法区跟堆:每个虚拟机实例都有一个方法区和一个堆。【他们由该虚拟机实例中所有线程共享。】
方法区:当虚拟机装载一个class文件时,他会从这个class文件包含的二进制信息中解析类型数据,然后他把这些类型数据放到方法区中,方法区包含所有的class和static变量。
解析类型信息,都有哪些类型信息呢?:
1:基本信息
这个类型的全限定名
这个类型的直接超类的全限定名(除非这个类型是java.lang.Object,它没有超类)
这个类型是类类型还是接口类型
这个类型的访问修饰符(public、abstract或final的某个子集)
任何直接超接口的全限定名的有序列表
2:其它信息
该类型的常量池
字段信息
方法信息
除了常量以外的所有类(静态)变量
一个到类ClassLoader的引用
一个到Class类的引用
堆:当程序运行时,虚拟机会把所有该程序在运行时创建的对象都放到堆中。
4:栈:每当启动一个新线程时,Java虚拟机都会为它分配一个Java栈。Java栈以帧为单位保存线程的运行状态。
虚拟机只会直接对Java栈执行两种操作:以帧为单位的压栈和出栈。
Java栈则总是存储该线程中Java方法调用的状态——包括它的局部变量,被调用时传进来的参数、返回值,
以及运算的中间结果等等。
Java栈是由许多栈帧(stack frame)组成的,一个栈帧包含一个Java方法调用的状态。当线程调用一个Java方法时,虚拟机
压入一个新的栈帧到该线程的Java栈中,当该方法返回时,这个栈帧被从Java栈中弹出并抛弃。
每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
Java虚拟机没有寄存器,其指令集使用Java栈来存储中间数据。这样设计的原因是为了保持Java虚拟机的指令集尽量紧凑。
同时也使于Java虚拟机在那些只有很少通用寄存器的平台上实现。另外,Java虚拟机的这种基于栈的体系结构,也有助于运行时某些虚拟机实现的动态编译器和即时编译器的代码优化。
栈空间的生命周期:
开辟:线程被创建时,分配栈空间。
回收:栈空间随着线程的终止而释放。
操作模式:栈中的数据占内存大小在编译时是确定的,有两个基本操作:入栈和出栈,操作规则是:LIFO(Last In Fast Out),后进先出。
StackOverflowError:如果在线程执行的过程中,栈空间不够用,那么JVM就会抛出此异常,这种情况一般是死递归造成的
堆空间的生命周期:
开辟:程序运行时(进程创建时)创建堆空间。
回收:当停止了对一个对象的引用,过一段时间后,GC会自动回收这个对象所占的内存。
操作模式:动态内存空间,栈中的数据占内存大小和初始值在运行时确定。
堆跟栈也可以理解成JVM在建立一个进程或线程时为它们分配的存储区域。
Java类加载的全过程,是加载、连接(验证、准备、解析)和初始化这三个阶段的过程。
(1)加载阶段是类加载过程的一个阶段。在加载阶段,虚拟机需要完成以下三件事情:1:通过一个类的全限定名来获取定义此类的二进制字节流(Class文件)
2:将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3:在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。
在这三件事情中,通过一个类的全限定名来获取定义此类的二进制字节流这个动作是在Java虚拟机外部来实现的,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块被称为“类加载器”。
(2)连接——指向验证、准备、以及解析(可选)。
1:验证 确保被导入类型的正确性。
2:准备 为类变量分配内存,并将其初始化为默认值。
3:解析 把类型中的符号引用转换为直接引用。
(3)初始化——把类变量初始化为正确初始值。
实例分析:
class Person { private String name; public Person(String name) { this.name = name; } public void say() { System.out.println("My Name is:" + this.name); } } public class Demo { public static void main(String[] args) { Person p1 = new Person("小强"); p1.say(); } }
/*
1:执行java Demo,创建一个Java虚拟机实例(进程),该进程首先根据classpath找到Demo.class文件。
读入该文件中的二进制信息,然后把Demo类的类信息存放到运行时数据区的方法区中。类加载完成。
2:Java虚拟机定位到方法区中Demo类的main方法的字节码,开始执行他的指令。
那么在main方法中,第一条语句是:Person p1 = new Person("小强");
意思是让虚拟机创建一个Person类的实例,然后让引用变量p1引用这个实例。
1)首先虚拟机会到方法区中寻找Person类的类信息,结果没找到。于是会立即加载Person类,并把Person类的类信息存放到方法区中。
2)成功加载了Person类,紧接着为Person类的实例对象在堆中分配内存,实例对象持有着指向方法区的Person类的类信息的引用。
这里所说的引用,实际上指的是Person类的类信息在方法区中的内存地址。
3)p1是main方法中的局部变量,所以会被添加到执行main方法的主线程的栈中。赋值运算符"="会把p1引用变量指向堆区中的Person实例对象。也就是说,p1持有指向Persons实例的引用。
4)当虚拟机执行p1.say()方法时,虚拟机会根据局部变量p1持有的引用,定位到堆区中的Person实例对象,
再根据Person类实例对象持有的引用,定位到方法区中Person类的类型信息,从而获得say()方法的字节码,
接着执行say()方法中包含的指令。
*/
附:
Java内存保护的方式
1:不使用指针,使用引用。
2:自动垃圾收集器(GC)。
3:严格的数组边界检查,数组越界会抛出数组脚标越界异常。
4:对象引用检查,使用引用的时候确保这些引用不为空值,否则抛出空指针异常。
Java体系结构的代价
1:程序执行速度相对于本地平台的程序来说,相对较慢。
2:内存管理,垃圾收集器使程序更加健壮,但你无法GC什么时候开始回收垃圾,无法确认GC是否开始收集垃圾,也无法确认收集垃圾要持续多长时间。
3:因为Java程序是动态连接的,从一个类到另一个类的引用是符号化的。在静态连接的可执行程序中,类之间的引用只是直接的指针或者偏移量。
相反,在Java class文件中,指向另一个类的引用通过字符串清楚地表明了所指向的这个类的名字,一个class文件的符号信息,以及字节码指令集和Java语言之间的密切关系,
使得把class文件逆向编译为Java源码相当容易。可以使用混淆器混淆class文件来解决这个问题。