第二课:jvm运行机制

1.启动流程

 java + 类-->装载配置(config文件)-->根据配置找到java.dll(为java虚拟机的主要实现)-->初始化java虚拟机(比如JNIEnv接口,findclass等操作可以通过这个接口来实现)
     -->找到main方法

2.如图1所示:jvm的内部结构

几个重要的组成部分:
pc寄存器:1.每个线层又有一个pc寄存器
         2.在线程创建时 创建
         3.指向下一条指令的地址
         4.执行本地方法时,pc的值为undefined
方法区:保存类的原信息 (类型的常量池/字段、方法信息/方法字节码)在jdk6和7中,String等常量信息置于方法,jdk7时,已经移动到了堆
       通常和永久区(perm)关联在一起
java堆:
        所有通多new操作出来的对象,都是通过堆来操作的
        --和程序开发密切相关
        --应用系统对象都保存在java堆中
        --所有线程共享java堆
        --一般是对分代GC来说,堆也是分代的(不同的gc方法需要不同的堆)
        --GC的主要工作区间
        eden--s0--s1--tenured
        (复制算法)
java栈:-线程私有
        -栈由一系列的帧组成(因此Java栈也叫做帧栈)
	    -帧保存一个方法的局部变量、操作数栈、常量池指针
	    -每一次方法调用创建一个帧,并压栈

在这里插入图片描述
3.JVM栈很重要的一个局部变量表 基本结构 (包含参数和局部变量)

  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}
  } 

想想成每一个都是32位的一个小格子一个槽位 long是64位占俩

静态方法如下:

0 int int i
1 long long l
2 *1
3 float float f
4 reference Object o
5 int byte b

实例方法如下:

0 reference this
1 int char c
2 int short s
3 int boolean b

4. java栈 操作数栈
-java没有寄存器 所有参数传递使用操作数栈

 public static int add(int a,int b){
   int c = 0;
   c = a+b;
   return c;
 }

当执行上面程序帧栈的的操作工程

0 iconst_0 // 0压栈
1 istore_2 // 弹出int,存放于局部变量2
2 iload_0 // 把局部变量0压栈
3 iload_1 // 局部变量1压栈
4 iadd // 弹出两个变量,求和,结果压栈
5 istore_2 // 弹出结果,存放局部变量2
6 iload_2 // 局部变量2压栈
7 ireturn //返回 

5. Java栈的分配(优化手段)

-小对象(几十个bytes),在没有逃逸的情况下,可以直接分配在栈上
-直接分配在栈上,可以自动回收,减少GC压力(是指,这个栈没有了,那我就没有必要再去进行垃圾回收了)
-大对象或者逃逸对象无法栈上分配  

解释:一般来说一个栈最大几百kb,1M顶天了,所以大对象,是没有办法在栈上进行分配的。当有小对象时,当一个方法分配在栈上,执行完成之后,这些小对象随着方法的消亡而消亡,所以可以直接分派到栈上。
这里还有一个逃逸对象的概念,是指,我并不止一个线程中要用到这个对象,我在其他的线程中,也用到这个对象,所以这个对象是在栈尚无法进行分配的。
6.图2:堆、栈、方法去之间的交互

public class AddMain{
  // AddMain的所有信息都在方法区,包括main方法
  public static void main(String ){
  //new出来的sample对象在堆里面,test1和test2 都是sample的对象的引用,在栈里面
    Sample test1 = new Sample("测试1");
	Sample test2 = new Sample("测试2");
    test1.printName();
	test2.printName();
	}
	
  public class Sample{
  //Sample的所有信息都是在i方法区内
    private name;
	//name 的对象在堆里面,name的引用在栈里面
	public Sample(name){
	  this.name = name;
	}
	//println方法也在方法区内部
    public class printName{
	  System.Out.println(name);
    }
}

在这里插入图片描述
7.图三 :关于线程
-每一个线程都有一个工作内存和主存,主存是一个共享的内存区间(可以理解成堆空间,所有线程都共享,所有线程都可以在里面存储数据)
-工作内存存放主存中变量的值的拷贝

	 图三解释:当数据从主存复制到工作内存时,必须出现两个动作:第一由主存执行的read操作,第二,由工作内存执行的相应的load操作;当数据从工作内存
	 拷贝到主存时,也出现两个操作,一个是由工作内存存储到主存的store操作,第二,由主存执行的相应的write操作
	 
	 每一个操作都是原子的,即执行期间不会被中断 但是read和load之间是可以被中断的

     对于普通变量,一个线程中更新的值,可能无法立即加载到主存中,也就无法被其他反应在其他变量中

     如果需要其他线程立即可见,需要使用volatile关键字

在这里插入图片描述
8.volatile关键字

public class VolatileStopThread extends Thread{
   private volatile boolean stop = false;
   public void stopMe(){
     stop = true ;
   }
   public void run(){
   int i = 0 ;
   while(!stop)
     i++
   }
   System.out..println("stop Thread");
   }
   
   public static void main(String args[]) throws InterruptException{
     VolatileStopThread t = new VolatileStopThread();
     t.start();
     Thread.sleep(1000);
     t.stopMe();
     Thread.sleep(1000);
   }
 } 

volatile不能代替锁(线程不安全的)
一般认为volatile比锁的性能要好(不绝对)

选择使用volatile的条件是:
语义是否满足应用

如果这个里面没有volatile这个关键字的话,这个VolatileStopThread只在自己的工作内存当中查找这个stop的值,因此永远也不会停止

9.内存模型可见的概念
1.可见性

  可见性是指在我修改了线程的值的时候,其他的线程能够立即知道(这里说的不是能够知道,是立即知道)
  能够实现可见性的几种方法
  1.volatile 
  2.synchronized(unlock之前,写变量值回主存)线程间的同步,这个词就是同步
  3.final(一旦初始化完成,其他线程就可见,常量)

2.有序性。(在一个线程当中,所有的指令,所有的操作都是有序执行的)
但是在另一个线程当中,去观察本线程,也许就是无序的(两种可能,第一种就是指令重排,第二种就是与主存存在同步延时)

 指令重排:在编译器中,会自动重排一些语句,比如a=1,b=2。在编译器的理解下,它不考虑语句的语义,如果他认为这是可以重排的,
 那么就会发生指令重排。
 但是如果a=1,b=a 这个就不可以发生指令重排

指令重排的一个例子:

public 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首先执行write()方法
   线程B接着执行reader()方法
   线程B在 int i = a+1是不一定能看到a已经被赋值给1的,因为在write中,两句话的顺序可能会被打乱 (这个时候就体现了操作重排了)
 这个时候,两个方法都是同一个操作级别的,当你的flag = true时,这个时候reader就进来了,把flag取走了
   那么在执行的时候,最后的结果是多少呢 
     线程A:                   线程B:
     flag = true          flag = true(此时a = 0) 
     a = 1 
   但是我想保证他的有序性,因为有时候是必要的,那我应该怎么做呢:
   我就可以在两个方法都加上synchronized关键字,加上suynchronized关键字之后,即使A线程出现了指令重排,但是线程B是进不来的,
 所以是要把一个线程运行完成之后,直到把线程锁释放,才能够运行下一个。不再是平行运行,而是属于一个串行了。

指令重排基本原则:

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

10.解释执行和编译执行的区别
解释执行:

  -解释执行以解释的方式运行字节码
  -解释执行的意思是:读一句,执行一句	

编译执行:

-将字节码编译成机器码
-直接执行机器码
-运行时编译
-编译后性能有数量级的提升(10倍以上)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值