JVM运行机制

来源:互联网 发布:电子科技大学 大数据 编辑:程序博客网 时间:2024/05/01 02:33

本文主要讨论以下内容:

1. JVM启动流程

2. JVM基本结构

3. 内存模型

JVM启动流程


1. 我们在启动一个JAVA程序的时候一般是通过java命令启动。
2. 运行java命令后JVM会首先加载jvm.cfg文件
3. 通过cfg找到JVM.DLL
4. jvm.dll初始化jvm并获得JNIEnv接口用来findClass
5. 最后运行class里的main函数。

JVM基本结构

关于JVM内存结构可参考点击打开链接


这里提一下java栈
1. 线程私有
2. 栈由一系列帧组成(因此Java栈也叫做帧栈)
3. 帧保存一个方法的局部变量、操作数栈、常量池指针
4. 每一次方法调用创建一个帧,并压栈

栈的局部变量表包含参数和局部变量

public class StackDemo {


public static int runStatic(int i,long l,float  f,Object o ,byte b){
return 0;
}//对于类方法,参数或局部变量分别存储在栈



public int runInstance(char c,short s,boolean b){
return 0;
}//对于实例方法,栈的第一个指向的是对象本身


}

栈上分配

由于堆上分配会造成多次GC,因此建议在栈上进行分配,因为方法执行完后所有栈内的信息都会回收。但是对于较大的对象,或逃逸对象无法分配在栈上。


栈 堆 方法区的交互




public   class  AppMain      //运行时, jvm 把appmain的信息都放入方法区 { public   static   void  main(String[] args)  //main 方法本身放入方法区。 { Sample test1 = new  Sample( " 测试1 " );   //test1是引用,所以放到栈区里, Sample是自定义对象应该放到堆里面 Sample test2 = new  Sample( " 测试2 " ); test1.printName(); test2.printName(); } 
public   class  Sample        //运行时, jvm 把appmain的信息都放入方法区 { private  name;      //new Sample实例后, name 引用放入栈区里,  name 对象放入堆里 public  Sample(String name) { this .name = name; } //print方法本身放入 方法区里。public   void  printName()    { System.out.println(name); } }


JAVA内存模型

概念:
1. 每一个线程都有一个工作内存和主存独立。工作内存存放变量的值拷贝。
2. 当数据从主内存复制到工作存储时,必须出现两个动作:第一,由主内存执行的读(read)操作;第二,由工作内存执行的相应的load操作;当数据从工作内存拷贝到主内存时,也出现两个操作:第一个,由工作内存执行的存储(store)操作;第二,由主内存执行的相应的写(write)操作

3. 每一个操作都是原子的,不会被中断
4. 对于一个普通变量,一个线程的值的更改不会马上反应在其他线程,如需要马上反应则应对该变量使用volatile关键字

5. 可见性。一个线程变量的修改,其他线程可以立即知道。
6. 保证可见性的方法。volatile, synchronized(unlock前写入主内存),final关键字
7. 有序性。在本线程中,所有的操作都是有序的。但在外部线程看来,操作都是无序的(指令重排)
8. 指令重排。
JVM为提高执行效率会对代码进行重排序。
写后读 a = 1;b = a;写一个变量之后,再读这个位置。
写后写 a = 1;a = 2;写一个变量之后,再写这个变量。
读后写 a = b;b = 1;读一个变量之后,再写这个变量。
以上语句不可重排
编译器不考虑多线程间的语义
可重排: a=1;b=2;//因为对于虚拟机来说a=1和b=2没有任何关联。
例: 指令重排破坏线程有序性
class OrderExample {int a = 0;boolean flag = false;public void writer() {    a = 1;                       flag = true;           }public void reader() {    if (flag) {                        int i =  a +1;              ……    }}}
线程A首先执行writer()方法,线程b随后执行reader()。由于writer()中a=1和flag=true是可以重排序的,因此writer()方法可能是flag=true; a=1. 因此线程B在执行a+1的时候不一定知道a已经被赋值为1.解决上面的问题可以通过对方法加锁synchronized,因为synchronized互斥,所以在b线程看到的a线程是有序的。

9. 重排序原则
程序顺序原则:一个线程内保证语义的串行性
volatile规则:volatile变量的写,先发生于读
锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
传递性:A先于B,B先于C 那么A必然先于C
线程的start方法先于它的每一个动作
线程的所有操作先于线程的终结(Thread.join())
线程的中断(interrupt())先于被中断线程的代码
对象的构造函数执行结束先于finalize()方法


0 0