java认知描述_Java技术:JVM的初步认识( 二 )


3. 准备:为类中修饰的变量分配内存空间并设置其初始值为0或null.可能会有人感觉奇怪,在类中定义一个修饰的int,并赋值了123,为什么这里还是赋值0.因为这个int的123是在初始化阶段的时候才赋值的,这里只是先把内存分配好.但如果你的修饰还加上了final,那么就会在准备阶段就会赋值.
4. 解析:解析阶段会将java代码中的符号引用替换为直接引用.比如引用的是一个类,我们在代码中只有全限定名来标识它,在这个阶段会找到这个类加载到内存中的地址.
5. 初始化:如刚才准备阶段所说的,这个阶段就是对变量的赋值的阶段.
4.1.2类与类加载器
每一个类,都需要和它的类加载器一起确定其在JVM中的唯一性.换句话来说,不同类加载器加载的同一个字节码文件,得到的类都不相等.我们可以通过默认加载器去加载一个类,然后new一个对象,再通过自己定义的一个类加载器,去加载同一个字节码文件,拿前面得到的对象去,会得到的结果是false.
4.1.3双亲委派机制
类加载器一般有4种,其中前3种是必然存在的
启动类加载器:加载\lib下的扩展类加载器:加载\lib\ext下的应用程序类加载器:加载下的自定义类加载器而双亲委派机制是如何运作的呢?
我们以应用程序类加载器举例,它在需要加载一个类的时候,不会直接去尝试加载,而是委托上级的扩展类加载器去加载,而扩展类加载器也是委托启动类加载器去加载.
启动类加载器在自己的搜索范围内没有找到这么一个类,表示自己无法加载,就再让扩展类加载器去加载,同样的,扩展类加载器在自己的搜索范围内找一遍,如果还是没有找到,就委托应用程序类加载器去加载.如果最终还是没找到,那就会直接抛出异常了.
而为什么要这么麻烦的从下到上,再从上到下呢?
这是为了安全着想,保证按照优先级加载.如果用户自己编写一个名为java.lang.的类,放到自己的中,没有这种优先级保证,应用程序类加载器就把这个当做加载到了内存中,从而会引发一片混乱.而凭借这种双亲委派机制,先一路向上委托,启动类加载器去找的时候,就把正确的加载到了内存中,后面再加载自行编写的的时候,是不会加载运行的.
4.2运行时数据区
运行时数据区分为虚拟机栈,本地方法栈,堆区,方法区和程序计数器.
4.2.1程序计数器
程序计数器是线程私有的,虽然名字叫计数器,但主要用途还是用来确定指令的执行顺序,比如循环,分支,跳转,异常捕获等.而JVM对于多线程的实现是通过轮流切换线程实现的,所以为了保证每个线程都能按正确顺序执行,将程序计数器作为线程私有.程序计数器是唯一一个JVM没有规定任何OOM的区块.
4.2.2Java虚拟机栈
Java虚拟机栈也是线程私有的,每个方法执行都会创建一个栈帧,局部变量就存放在栈帧中,还有一些其他的动态链接之类的.通常有两个错误会跟这个有关系,一个是,一个是OOM().前者是因为线程请求栈深度超出虚拟机所允许的范围,后者是动态扩展栈的大小的时候,申请不到足够的内存空间.而前者提到的栈深度,也就是刚才说到的每个方法会创建一个栈帧,栈帧从开始执行方法时压入Java虚拟机栈,执行完的时候弹出栈.当压入的栈帧太多了,就会报出这个.
4.2.3本地方法栈
本地方法栈中存放的是JVM实际需要调用到的方法,实际上还是和Java虚拟机栈很相似的.
4.2.4方法区
方法区是所有线程共享的一块内存分区,它的名字其实感觉不太恰当,它主要保存的就是我们前面说的,类加载器加载到JVM中的类信息等.(而方法区在JVM规范中只是规定了它的存在和作用,并没有限制它的实现,所以就在Java7以及之前版本的设计中搞了个永生代来实现方法区,其他的厂商都没有这个永生代.而这个设计经过多个版本的验证,并不是一个好的设计,所以在Java 8的时候就移除掉了永生代,使用一个本地的内存块来替代,命名为)