类是如何被加载到jvm并且在jvm中执行的?jvm内存各个部分放的是什么,什么时候放进入的?首先分享画的导图,JVM 内存结构和运行机制的关系。
从这个问题出发,首先我们需要知道jvm的内存结构包括什么。
下图为JVM的结构图,主要包括垃圾回收器,类加载子系统,执行引擎,运行时数据区等。
类加载子系统:把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化最终形成可以被JVM直接使用的Java类型。
运行时数据区
程序计数器:
在JVM的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,为了各条线程之间的切换后计数器能恢复到正确的执行位置,所以每条线程都会有一个独立的程序计数器。
Java虚拟机栈:线程私有,生命周期与线程相同,
每个线程都会建立一个操作栈,每个栈又包含了若干个栈帧,每个栈帧对应着每个方法的每次调用,每个栈帧包含了三部分:
局部变量区(方法内基本类型变量、变量对象指针)
操作数栈区(存放方法执行过程中产生的中间结果)
运行环境区(动态连接、正确的方法返回相关信息、异常捕捉)
本地方法栈:与java虚拟机栈差不多,区别就是执行的是native 方法。
java堆:被所有线程所共享的一块内存区域,在虚拟机启动时创建,存放对象实例。
方法区:与java 堆一样,是所有线程共享的内存区域。用于存储已被虚拟机加载的类信息,常量,静态变量,及时编译器编译后的代码等数据。
运行时常量池:运行时常量池时方法区的一部分,用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的常量池中存放。
jvm启动流程
1.java虚拟机启动的命令是通过java +xxx(类名,这个类中要有main方法)或者javaw启动的。
2.执行命令后,系统第一步做的就是装载配置,会在当前路径中寻找jvm的config配置文件。
3.找到jvm的config配置文件之后会去定位jvm.dll这个文件。这个文件就是java虚拟机的主要实现。
4.当找到匹配当前版本的jvm.dll文件后,就会使用这个dll去初始化jvm虚拟机。获得相关的接口。之后找到main方法开始运行。
上面这个过程的描述虽然比较简单,但是jvm的启动流程基本都已经涵盖在里面了。
示例
Java程序开始运行前,类加载器加载类名.class,将类信息保存在运行时数据区的方法区。
1. AppMain.java
2. public class AppMain //运行时, jvm 把appmain的信息都放入方法区
3. {
4. public static void main(String[] args) //main 方法本身放入方法区。
5. {
6. Sample test1 = new Sample( " 测试1 " ); //test1是引用,所以放到栈区里, Sample是自定义对象应该放到堆里面
7. Sample test2 = new Sample( " 测试2 " );
8. test1.printName(); //栈帧
9. test2.printName();
10. }
11. }
这时就会到方法区寻找Sample的类型信息,发现还没有加载,就会加载Sample.class。在堆中分配内存并建立一个Sample对象,它的引用是test1。堆中的对象持有class的引用,即指向方法区中类的类型信息的地址。
1. Sample.java
2. public class Sample //运行时, jvm 把appmain的信息都放入方法区
3. {
4. private name; //new Sample实例后,name引用放入栈区里,name对象放入堆里
5. public Sample(String name)
6. {
7. this .name = name;
8. }
9. public void printName() //print方法本身放入方法区里。
10. {
11. System.out.println(name);
12. }
13.
14. }
栈,堆,方法区交互,一个程序要想执行是需要几个内存区域交互配合执行的。