jvm启动流程
启动java进程-》装载配置(根据当前路径和系统版本寻找jvm.cfg)-》根据配置寻找JVM.dll(JVM.dll为JVM主要实现)-》初始化JVM获得JNIEnv接口(JNIEnv为JVM 接口,findClass等操作通过它实现)-》找到main方法并运行。
jvm基本结构
PC寄存器
每个线程拥有一个PC寄存器(程序计数器)
在线程创建时 创建
指向下一条指令的地址
执行本地方法时,PC的值为undefined
方法区
保存装载的类信息
类型的常量池(JDK6时,String等常量信息置于方法JDK7时,已经移动到了堆)
字段,方法信息
方法字节码
通常和永久区(Perm)关联在一起
Java栈(包括局部变量表和操作数栈)
线程私有
栈由一系列帧组成(因此Java栈也叫做帧栈)
帧保存一个方法的局部变量、操作数栈、常量池指针
每一次方法调用创建一个帧,并压栈
Java栈 – 栈上分配实例(jvm对内存分配做了很多优化,其中一个就是将小对象直接分配到栈,我们知道,栈是非常快的)
如下的程序:
如果jvm配置为:
-server -Xmx10m -Xms10m
-XX:+DoEscapeAnalysis -XX:+PrintGC
添加逃逸分析后,则不会进行GC,因为小对象都直接分配到了栈里。
如果jvm配置为:
-server -Xmx10m -Xms10m
-XX:-DoEscapeAnalysis -XX:+PrintGC
则会出现如下GC日志。
……
[GC 3550K->478K(10240K), 0.0000977 secs]
[GC 3550K->478K(10240K), 0.0001361 secs]
[GC 3550K->478K(10240K), 0.0000963 secs]
564
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);
}
}
Java栈 – 栈上分配
小对象(一般几十个bytes),在没有逃逸的情况下,可以直接分配在栈上
直接分配在栈上,可以自动回收,减轻GC压力
大对象或者逃逸对象无法栈上分配
内存模型
每一个线程有一个工作内存和主存独立
工作内存存放主存中变量的值的拷贝
对于普通变量,一个线程中更新的值,不能马上反应在其他变量中
如果需要在其他线程中立即可见,需要使用 volatile 关键字。
可见性
一个线程修改了变量,其他线程可以立即知道
保证可见性的方法
volatile
synchronized (unlock之前,写变量值回主存)
final(一旦初始化完成,其他线程就可见)
有序性
在本线程内,操作都是有序的
在线程外观察,操作都是无序的。(指令重排 或 主内存同步延时)
volatile规则:volatile变量的写,先发生于读
锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
解释运行
解释执行以解释方式运行字节码
解释执行的意思是:读一句执行一句
编译运行(JIT)
将字节码编译成机器码
直接执行机器码
运行时编译
编译后性能有数量级的提升
java的编译器先将其编译为class文件,也就是字节码;然后将字节码交由jvm(java虚拟机)解释执行;
所以很多地方都说“java是一种半编译、半解释执行”的语言;
近来(其实也不是很”近”)Oracle的(以前是Sun的)HotSpot VM采用了jit compile(just in time compilation)技术,将运行频率很高的字节码直接编译为机器指令执行以提高性能, 所以当字节码被jit编译为机器码的时候,要说它是编译执行的也可以…
不过总体来讲,java的编译结果是被jvm“解释执行”的,所以这么说也能说通,而其实这个“是编译还是解释”这个概念在这里已经有点模糊了,理解它的过程就行了,不必下一个“精确”的定义;
而我自己仍然赞成“java是编译型语言”的说法,因为“编译”其本质就是“把一个相对高级的语言转换为另一个相对低级的语言”,而由java -> class文件的编译已经满足了这个特征; 而后面你要说jvm是“解释执行”的,那其实硬件对于机器码又何尝不是“解释执行”呢?