JVM 相关知识
来源:互联网 发布:java 对话框 编辑:程序博客网 时间:2024/06/07 07:23
- JVM运行机制
- 1 JVM启动流程
- 2 JVM基本结构
- 21 PC寄存器
- 22 方法区
- 23 Java堆
- 24 Java栈
- 25 栈堆方法区交互
- 3 内存模型
- 31 volatile关键字
- 32 几个概念
- 4 字节码执行的两种方式编译运行与解释运行
JVM运行机制
1.1 JVM启动流程
java *(启动类,含有main方法)命令或javaw命令来启动。启动过程如下:
1、装载配置:在当前路径中寻找配置文件(根据当前路径和系统版本寻找jvm.cfg);
2、根据配置寻找JVM.dll文件(JVM的主要实现);
3、初始化JVM,获得JNIEnv接口(JNIEnv接口为JVM接口,findClass等操作通过它实现);
4、找到main方法,开始运行。
1.2 JVM基本结构
1.2.1 PC寄存器
- 每个线程拥有一个PC寄存器
- 在线程创建时创建
- 指向下一条指令的地址
- 执行本地方法时,PC的值为undefined
1.2.2 方法区
- 保存装载的类信息(保存类的元信息,对类进行描述)
- 类型的常量池(JDK6时,String等常量信息置于方法区,JDK7时,已经移动到了堆区)
- 字段,方法信息
- 方法字节码
- 通常和永久区(Perm)关联在一起
1.2.3 Java堆
- 和程序开发密切相关
- 应用系统对象都保存在Java堆中
- 所有线程共享Java堆
- 对分代GC来说,堆也是分代的
- GC的主要工作区间
1.2.4 Java栈
- 1、线程私有
- 2、栈由一系列帧组成(因此Java栈也叫作帧栈)
- 3、帧保存一个方法的局部变量、操作数栈、常量池指针
- 4、每一次方法调用都会创建一个帧,并压栈
- 5、局部变量表——包含函数的参数和局部变量
public class StackDemo{ //每个槽位最大能容纳32位的数据类型,long是64位,引用类型相当于是指针,占32位 public static int runStatic(int i, long l, float f,Object o,byte b){ return 0; } //实例方法与静态方法有一个区别是:第一个槽位是当前对象的一个引用(this) public int runInstance(char c,short s,boolean b){ return 0; } }
- 6、函数调用组成帧栈
public static int runStatic(int i, long l, float,Object o,byte b){ return runStatic(i,l,f,o,b);}
每一块是一个帧,方法调用结束,帧就移调(这里省略了操作数栈、返回地址等)。
- 7、操作数栈
- 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(c变量)//2:iload_0 //把局部变量0(a变量)压栈//3:iload_1 //把局部变量1(b变量)压栈//4:iadd //弹出2个变量,求和,结果压栈//5:istore_2 //弹出结果,存放于局部变量2//6:iload_2 //局部变量2压栈//7:ireturn //返回
- 栈上分配
- 堆上分配,用完之后需要手动删除(否则易导致内存泄漏);Java中局部变量在栈上分配,函数调用完成自动清理
- 小对象(一般几十个bytes),在没有逃逸(对象分配出来除了在该线程上用,还要在别的线程上用)的情况下,可以直接分配在栈上
- 直接分配在栈上,可以自动回收,减轻GC压力
- 大对象或者逃逸对象无法栈上分配(因为栈是线程私有的,有逃逸的时候,对象会被别的线程共享,所以该对象不能分配在栈上)
public class OnStackTest{ public static void alloc(){ byte[] b = new byte[2]; b[0] = 1; } public static void main(String[] args){ long b = System.currentTimeMillis(); for(int i = 0;i<100000000;i++){ alloc(); } long e = System.currentTimeMillis(); System.out.println(e-b); }}//以下参数运行不会有GC//-server -Xmx10m -Xms10m//-XX:+DoescapeAnalysis -XX:+PrintGC//以下参数运行会发生GC,且是在堆上分配//-server -Xmx10m -Xms10m//-XX:-DoescapeAnalysis -XX:+PrintGC
1.2.5 栈、堆、方法区交互
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); } }
1.3 内存模型
- 每一个线程有一个工作内存,和主存独立
- 工作内存存放主存中变量的值的拷贝
1、当数据从主内存复制到工作存储时,必须出现两个动作:第一,由主内存执行的读(read)操作;第二,由工作内存执行的相应的load操作;当数据从工作内存拷贝到主内存时,也出现两个操作:第一个,由工作内存执行的存储(store)操作;第二,由主内存执行的相应的写(write)操作。
2、每一个操作都是原子的,即执行期间不会被中断。
3、对于普通变量,一个线程中更新的值,不能马上反应在其他线程中。(线程直接读取和存储的是线程的工作内存,工作内存到主存是有时差的)
4、如果需要在其他线程中立即可见,需要使用 volatile 关键字。(直接到主存中拿数据)
1.3.1 volatile关键字
volatile不能代替锁;
一般认为volatile比锁性能好(不绝对);
选择使用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 InterruptedException{ VolatileStopThread t=new VolatileStopThread(); t.start(); Thread.sleep(1000); t.stopMe(); Thread.sleep(1000); }}
1.3.2 几个概念
- 可见性:一个线程修改了变量,其他线程可以立即知道
- 保证可见性的方法
- volatile
- synchronized (unlock之前,写变量值回主存)
- final(一旦初始化完成,其他线程就可见)
- 有序性
- 在本线程内,操作都是有序的
- 在线程外观察,操作都是无序的。(指令重排 或 主内存同步延时)
- 指令重排
- 线程内串行语义
- 写后读 a = 1;b = a; 写一个变量之后,再读这个位置。
- 写后写 a = 1;a = 2; 写一个变量之后,再写这个变量。
- 读后写 a = b;b = 1; 读一个变量之后,再写这个变量。
- 以上语句不可重排
- 编译器不考虑多线程间的语义
- 可重排: a=1;b=2;
Eg.
1、指令重排——破坏线程间的有序性
/* 线程A首先执行writer()方法 线程B线程接着执行reader()方法 线程B在int i=a+1 时不一定能看到a已经被赋值为1,因为在writer中,两句话顺序可能打乱 线程A flag=true a=1 线程B flag=true(此时a=0)*/class OrderExample { int a = 0; boolean flag = false; public void writer() { a = 1; flag = true; } public void reader() { if (flag) { int i = a +1; //…… } }}
2、指令重排——保证有序性的方法
/* 同步后,即使做了writer重排,因为互斥的缘故,reader 线程看writer线程也是顺序执行的。 线程A flag=true a=1 线程B flag=true(此时a=1)*/class OrderExample { int a = 0; boolean flag = false; public synchronized void writer() { a = 1; flag = true; } public synchronized void reader() { if (flag) { int i = a +1; //…… } }}
- 指令重排的基本原则
- 程序顺序原则:一个线程内保证语义的串行性
- volatile规则:volatile变量的写,先发生于读
- 锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
- 传递性:A先于B,B先于C 那么A必然先于C
- 线程的start方法先于它的每一个动作
- 线程的所有操作先于线程的终结(Thread.join())
- 线程的中断(interrupt())先于被中断线程的代码
- 对象的构造函数执行结束先于finalize()方法
1.4 字节码执行的两种方式——编译运行与解释运行
- 解释执行
- 解释执行以解释方式运行字节码
- 解释执行的意思是:读一句执行一句
- 编译运行(JIT)
- 将字节码编译成机器码
- 直接执行机器码
- 运行时编译
- 编译后性能有数量级的提升
阅读全文
0 0
- JVM相关知识小结
- JVM相关知识小结
- jvm相关知识
- jvm相关知识整理
- JVM的相关知识
- JVM 相关知识整理
- JVM相关知识
- jvm 相关知识
- JVM相关知识总结
- JVM相关知识整理
- JVM相关知识
- JVM相关知识整理
- JVM 相关知识
- JVM相关知识总结
- JVM相关知识梳理
- JVM的一些相关知识
- java中JVM相关知识
- 【JVM】虚拟机相关知识整理
- 学习资源大全 干货!
- Hibernate dialect设置
- A
- springboot跨域
- Windows中的自动启动项(自动启动的多种方式)
- JVM 相关知识
- Android动态设置主题样式
- FCC学习笔记-(一) HTML5 and CSS
- 【自考】-数据结构第一遍导图
- 02-STL
- Git常用命令简介
- SonarQube的安装、配置与使用
- 文章标题
- C++ 5种迭代器