(1).JVM的启动流程
首先:java XXX(程序)
然后:进行装载配置,这个过程主要是根据当前路径和系统版本寻找jvm.cfg文件,对应的路径如下截图
然后:再根据配置寻找JVM.dll文件,JVM.dll为JVM的主要实现。其中JVM.dll所在目录为:
这时候我们就进行初始化JVM并获得JNIEnv的接口,JNIEnv为JVM的接口,findClass等操作通过它来实现。
最后,找到main方法并运行。
注:整个流程用一张图来表示为
(2).JVM的基本结构
上面的图片为JVM运行时候的数据区,接下来对上面的组成做一个简要的记录:
程序计数器(PC):
- 每一个线程都拥有一个独立的PC寄存器(线程私有);
- 在线程创建时候创建;
- 存放指向下一条指令的地址;
- 执行本地方法(Native)时,这个计数器的值为空(Undefined)。
Java虚拟机栈(帧栈):
- Java虚拟机栈是线程私有的,生命周期与线程相同;
- 栈由一系列帧组成,每一次的方法调用都会创建一个帧,并压栈;
- 帧保存一个方法的局部变量表,操作数栈,动态链接,方法出入口等信息。
- 对局部变量表的补充说明:局部变量表存放了编译器可知的各种基本数据类型(boolean,byte,char,short,int,float,long,double),对象引用(reference类型)和returnAddress类型(指向了一条字节码指令的地址)。我们看以下的代码:
因为静态方法没有本类对象引用(无需this指针,类可以直接调用),所以第一个方法对应的局部变量表为: 第二个方法对应的局部变量表为: 注意:每个局部变量空间(Slot)为32位,所以64位的long和double类型会占用俩个局部变量空间。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; } }
- 栈上分配:指的是一些小对象(一般几十个bytes),在没有逃逸的情况下,可以直接分配在栈上,这些小对象就可以直接回收,从而减轻GC的压力,注意大对象或者逃逸对象是无法分配在栈上的哦!我们看一下如下的效果说明栈上分配的优点:
通过代码我们可以知道申请了很多的2个字节的小空间,这时候我们进行栈上分配,使用public class Test { public static void alloc(){//每次申请2个byte的空间 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);//输出程序运行的时间 } }
来运行程序,得到结果如下: 如果使用堆来分配呢,我们使用一下的代码:-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC
通过上面-Xmx10m -Xms10m 可知,我们给堆分配了最大10M的空间,显然不够用,这时候就要进行很多次的GC,我们得到运行的结果为: 可知程序进行了很多次的GC,最后使用的时间达到了1101,与栈上分配的7的差别有一百多倍。-server -Xmx10m -Xms10m -XX:-DoEscapeAnalysis -XX:+PrintGC
方法区:
- 存储已经被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据;
- 通常和永久区(Perm)关联在一起(因为HotSpot虚拟机设计团队选择把GC分代收集扩展至方法区);
- 注意:在新发布的JDK 1.7的HotSpot中,原本放在永久区的字符串常量池已经被移动到堆区;
- 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError(OOM)异常。
Java堆:
- Java是被所有的线程共享的一块内存区域,在虚拟机启动的时候创建;
- 该内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存;
- Java堆是垃圾收集器管理的主要区域,因此很多时候也会被称为GC堆;
- 对于分代GC来来说,堆也是分代的,可以细分为:新生代(Eden区,From区,To区)和老年代,我们运行一下代码看一下(或许你现在还不懂这短信息是怎样输出的): 从截图中我们可以看出则段程序执行了两次GC,Java堆分为PSYoungGen(新生代)和ParOldGen(老年代),PSYoungGen(新生代)里面包含eden,from和to三个区组成。当然from和to又合称Survivor区。
栈,堆,方法区的交互
- 我们对照如下的代码进行分析:
[AppMain.java] 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(); } }
[Sample.java] class Sample { //运行时,jvm把appmain的信息都放入方法区 private String name; public Sample(String name) { this.name = name; //new Sample实例后, name引用放入栈区里, name对象放入堆里 } public void printName() { //printName方法本身放入方法区里。 System.out.println(name); } }
(3).好了,以上就是我今天学的一些有关JVM的最基本的知识!下一篇会介绍JVM的内存模型以及编译跟解释模型的概念。