java虚拟机的基本结构

来源:互联网 发布:国产007经典视频知乎 编辑:程序博客网 时间:2024/05/16 09:43

一 :java虚拟机的基本结构

1: 类加载子系统(负责从文件系统或者网络中加载class信息,加载的类信息存放于一块成为方法区的内存空间。除了类信息外,方法区中可能还存放运行时常量池信息,包括字符串字面量和数字常量(这部分常量信息是class文件常量池部分的内存映射))

2:java栈(java方法的调用,保存着局部变量,方法参数,同时和java方法的调用、返回密切相关)

3:方法区(所有线程共享的内存区域,保存系统的类信息,比如类的字段、方法、常量池等)

4:java堆(在虚拟机启动的时候建立,它是java程序最主要的内存工作区域。几乎所有的java对象实例都存放于java堆中。堆空间是所有线程共享的,这是一块与java应用密切相关的内存区域)

5:直接内存(java的nio库允许java程序使用直接内存。直接内存是在java堆外的、直接向系统申请的内存区间。通常,访问直接内存的速度会优于java堆。因此,出于对性能的考虑,读写频繁的场合可能会考虑使用直接内存,由于直接内存在java堆外,因此直接内存的大小不会直接受限于xmx指定的最大堆大小,但是系统内存是有限的,java堆和直接内存的总和依然受限于操作系统能给出的最大内存)

6:本地方法栈(本地方法的调用,这是java虚拟机的重要扩展,java虚拟机允许java直接调用本地方法(这个方法通常使用c编写))

7:垃圾回收系统 (对方法区、java堆和直接内存进行回收。其中java堆是垃圾回收的重点。和c/c++ 不同,java中所有的对象空间释放是隐式的,也就是说对象的释放,开发人员不需要人为干预,对于不再使用的对象,垃圾回收系统会在后台自动查找、标识并释放垃圾对象,完成对java堆,方法区和直接内存中的全自动化管理)

8:pc寄存器(每个线程的私有空间,jvm会为每个java线程创建pc寄存器)

9:执行引擎(负责执行虚拟机的字节码)

二 : 初识java堆

根据垃圾回收机制的不同,java堆有可能拥有不同的结构。最为常见的一种构成是将整个java堆分为新生代(存放年龄不大的对象)和老年代(老年对象) 。简单说下java堆、方法区、java栈之间的关系

public class SimpleHeap{

        private int id;

public SimpleHeap{

this.id=id;

}

public void show(){

System.out.println(id);

}

public static void main(String[] args){

SimpleHeap s1= new SimpleHeap(1);

SimpleHeap s2= new SimpleHeap(2);

s1.show();

s2.show();

}

}

请看下面图片解释

三 : java栈

如果说java堆和程序数据密切相关的话,那java栈就是和线程执行密切相关了,线程执行的基本行为就是函数调用每次函数调用的数据都是java栈传递的。

java栈与数据结构上的栈有着类似的含义,它是一个先进后出的数据结构,只支持出栈和入栈。java栈中主要保存的是栈帧,每一个栈帧里面又保存着局部变量表,操作数栈和栈数据区。记住:一个函数(java方法)就是一个栈帧

局部变量表:保存函数(java方法)的参数以及局部变量。局部变量表中的变量只在当前函数调用中有效,当函数调用结束后,随着函数栈帧的销毁,局部变量表也会随之销毁,由于局部变量表在栈帧之中,如果函数的参数和局部变量较多,会使得局部变量表膨胀,从而每一次函数调用就会占用更多的栈空间。在每个栈帧的的变量,大小都是用“”字“”来描述,字指的是计算机内存中占据一个单独的内存单元编号的一组二进制串,一般32位计算机上一个字为4个字节长度,

切记:栈帧中的局部变量表中的槽位是可以重复利用的,如果一个局部变量过了其作用域,那么在其作用域之后申明的心的局部变量就很有可能会复用过期局部变量的槽位,从而达到节省资源的母的。(不懂? 看下面代码)

public void localvarGc1(){

byte a = new byte[6*1024*1024];

System.gc();

}


        public void localvarGc2(){

byte a = new byte[6*1024*1024];

a=null;

System.gc();

}


       public void localvarGc3(){

{

byte a = new byte[6*1024*1024];

}

System.gc();

}

         public void localvarGc4(){

{

byte a = new byte[6*1024*1024];

}

int c =10;

System.gc();

}



        public void localvarGc5(){

localvarGc1();

System.gc();

}

这些代码,每一个方法都分配一块6MB的堆空间,并使用局部变量引用这块空间
localvarGc1()中,在申请空间后,立即进行垃圾回收,很明显,由于byte数组被变量a引用,因此无法回收这块空间。
localvarGc2()中,在垃圾回收之前,先将变量a设置为null,使byte数组失去强引用,故垃圾回收可以顺利回收byte数组。
localvarGc3()中,在进行垃圾回收之前,先使局部变量a失效,虽然变量a离开了其作用域,但是变量a依然存在于局部变量表中,并且也指向这块byte数组,所以byte数组依然不会被回收
localvarGc4()中,不仅使变量a失效,更是申明了变量c,使变量c使用了变量a的字,由于变量a此时被销毁,所以垃圾回收器可以顺利回收byte数组。
localvarGc5()中,首先调用Gc1()方法,在Gc1()方法中byte数组没有被释放,但是Gc1()方法执行完之后,它的栈帧被销毁(包含了栈帧中的局部变量),所以byte数组会被回收掉
操作数栈:也是一个先进后出的数据结构,只支持出栈和入栈。主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。
帧数据区:除了局部变量表和操作数栈之外,java栈帧还需要一些数据来支持常量解析,正常方法返回和异常处理等。大部分java字节码指令需要进行常量池访问,在帧数据区中保存着访问常量池的指针,方便程序访问常量池。此外,当函数返回或者出现异常时,虚拟机必须回复调用者函数的栈帧,并让调用者函数执行下去,对于异常处理,虚拟机必须有一个异常处理表,方便在发生异常的时候找到处理异常的代码,因此,异常处理表也是帧数据区中重要的一部分
栈上分配:java虚拟机提供的一项优化技术,基本思想是:对于那些线程私有的对象,可以将他们打散分配在栈上,而不是分配在堆上,分配在栈上的好处是可以在函数调用结束后自行销毁,而不需要垃圾回收器的介入,从而提高系统的性能,
栈上分配的一个技术基础是 进行逃逸分析,逃逸分析的目的是判断对象的作用域是否有能逃逸出函数体(类似于线程不安全的操作)。如下代码展示了一个逃逸的对象
private static User u;
public static void alloc(){
u=new User();
u.id=1;
u.name="zhaoxiao";
}

对象User u 是类的成员变量,该字段有可能被任何线程访问,因此属于逃逸对象

下面展示一个非逃逸对象

public static void alloc(){
User u=new User();
u.id=1;
u.name="zhaoxiao";
}
对于非逃逸的对象,虚拟机就有可能将其对象分配在栈中,而不是在堆中


总结:栈上分配依赖逃逸分析和标量替换的实现,关闭其中之一,栈上分配技术就达不到目的了。

对于大量的零散小对象,栈上分配提供了一种很好的对象分配优化策略,栈上分配速度快,并且可以有效避免垃圾回收带来的负面影响,但由于和堆空间相比,栈空间较小,因此对于大对象无法也不适合在栈上分配,

四:java虚拟机之  识别方法区

和java堆一样,方法区是一块所有线程都共享的内存区域。方法区用于保存系统的类信息,比如累的字段,方法,常量池等。方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内幕才能溢出错误。

在JDK1.6、JDK1.7中,方法区可以理解为永久区,默认情况下永久区的大小为64M。一个大的永久区可以保存更多的类信息。在JDK1.8中,永久区已经被彻底移除了。取而代之的事元数据区,这是一块堆外的直接内存。与永久区不同,如果不指定大小,默认情况下,虚拟机会耗尽所有的可用系统内存。


文章内容如有错误 欢迎加 QQ :82479297  探讨


0 0