初识JVM-JVM运行机制(1)

(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类型(指向了一条字节码指令的地址)。我们看以下的代码:
    	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;
    		}
    	}
    因为静态方法没有本类对象引用(无需this指针,类可以直接调用),所以第一个方法对应的局部变量表为:                                                                                                                                第二个方法对应的局部变量表为:                                                                                                                                                                                                           注意:每个局部变量空间(Slot)为32位,所以64位的long和double类型会占用俩个局部变量空间。
  • 栈上分配:指的是一些小对象(一般几十个bytes),在没有逃逸的情况下,可以直接分配在栈上,这些小对象就可以直接回收,从而减轻GC的压力,注意大对象或者逃逸对象是无法分配在栈上的哦!我们看一下如下的效果说明栈上分配的优点:
    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);//输出程序运行的时间
        }
    }
    通过代码我们可以知道申请了很多的2个字节的小空间,这时候我们进行栈上分配,使用
    -server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC
    来运行程序,得到结果如下:    如果使用堆来分配呢,我们使用一下的代码:
    -server -Xmx10m -Xms10m -XX:-DoEscapeAnalysis -XX:+PrintGC
    通过上面-Xmx10m -Xms10m 可知,我们给堆分配了最大10M的空间,显然不够用,这时候就要进行很多次的GC,我们得到运行的结果为:    可知程序进行了很多次的GC,最后使用的时间达到了1101,与栈上分配的7的差别有一百多倍。
      方法区:
  • 存储已经被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据;
  • 通常和永久区(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的内存模型以及编译跟解释模型的概念。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值