JVM 相关知识
Posted Lee_Sung
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM 相关知识相关的知识,希望对你有一定的参考价值。
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)
- 将字节码编译成机器码
- 直接执行机器码
- 运行时编译
- 编译后性能有数量级的提升
以上是关于JVM 相关知识的主要内容,如果未能解决你的问题,请参考以下文章