您的位置:首页 > 编程语言 > Java开发

Java内存分析一

2015-10-26 02:25 344 查看
如果了解java内存的使用情况,对于程序的执行情况会更加清晰。关于java内存深度解析,请读者自行参考JVM有关书籍文档,会得到更多更完善的信息。下面通过一段简单的代码来分析。

首先简单介绍下JVM运行时内存数据区:

第一块:PC寄存器

PC寄存器是用于存储每个线程下一步将执行的JVM指令,如该方法为native的,则PC寄存器中不存储任何信息。

第二块:JVM栈

JVM栈是线程私有的,每个线程创建的同时都会创建JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址。保存局部变量的值,包括:1.用来保存基本数据类型的值;2.保存类的实例,即堆区对象的引用(指针)。也可以用来保存加载方法时的帧。

第三块:堆(Heap)

1.它是JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配,Heap中的对象的内存需要等待GC进行回收。堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的。

2.Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLAB(Thread Local AllocationBuffer),其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,在这种情况下JVM中分配对象内存的性能和C基本是一样高效。的,但如果对象过大的话则仍然是直接使用堆空间分配。

3.TLAB仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。

4.用来存放动态产生的数据,比如new出来的对象。注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法。因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。

第四块:方法区域(Method Area)

(1)在Sun JDK中这块区域对应的为PermanetGeneration,又称为持久代。

(2)方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当开发人员在程序中通过Class。对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。

第五块:运行时常量池(Runtime Constant Pool)

存放的为类中的固定的常量信息、方法和Field的引用信息等,其空间从方法区域中分配。

第六块:本地方法堆栈(Native Method Stacks)

JVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每个native方法调用的状态。

下面通过一段简单的程序来分析:

点击(此处)折叠或打开

public class Student {

    

    private int age;

    private int height;

    private String name;

    

    Student(){

        

        

    }

    

    Student(int a,int h,String n){

        

        age = a;

        height = h;

        name = n;

    }

    public int getage(){

        

        return age;

    }

    

    public int setage(int age1){

        

        age =0;

        return age;

    }

    public int getheight(){

        

        return height;

    }

    

    public String getname(){

        

        return name;

    }

    

    public void setStudent1(Student h){

        

        h.setage(1);

    }

    

    public void setStudent2(Student s){

        

        s = new Student(1,1,"www");

    }

    

    public String toString(){

        

        return age + " " + height + "
" + name;

    }

    public static void main(String[] args){

        

        Student one = new Student();
   //1

        int  s = 5;                                   //2

        Student a = new Student(20, 177, "diy");  
//3

        Student b = new Student(30, 180, "os");
   //4

        System.out.println(one);        //p

        one.setage(s);
      //5

        System.out.println(one.getage());
  //m

        one.setStudent1(a);
                //6

        System.out.println(a);
             //q

        one.setStudent2(b);
                //7

        System.out.println(b);
             //r

    

    }

    

}

程序的执行从main函数开始:

1:在栈中存入了一类型为Student的局部变量one,其它的值我们未知,然后在堆中创建了一块对象内存区域,系统会调用构造函数来初始化对象成员变量,由于new Student(),无参,所以系统会调用无参数构造器来初始化成员变量:

上述无参构造函数可以写成:

Student(){

  

  age = 0;

  height = 0;

  name = null;

}

当执行p步骤时,系统会自动调用toString()方法,打印成员变量,得到的是:0 0 null

此时栈中的局部变量one指向了堆中被初始化的Student对象内存区域(其实one中存放的就是被初始化的Student对象在堆中的物理地址,作用和C/C++指针一样)

2.在栈中压入一局部变量s,并且赋值是5

由此可以发现,基础类型的变量就占用一块内存,引用类型的变量占用两块内存

3.在栈中申请一块内存存放类型为Student的局部变量a,并在堆中new出了一块Student对象内存,然后调用构造函数 Student(int a,int h,String n),在栈中依次分配三块内存区域存放局部变量a,h,n,这三个变量分别被赋值为20, 177,”diy",在构造函数内部,该实例的成员变量分别被局部变量赋值:
        age = a;

      height = h;

      name = n;
构造函数执行完毕后,局部变量的内存空间被依次收回n,h,a,其内存空间消失(注意栈中存储的特性,先入后出)

此时栈中Student 的引用a指向堆中的该Student 实例内存区域

4.分析同3

5.Student的引用one,调用函数setage(int age1),会在栈中分配局部变量age1的内存空间,并赋值为s的值为5,但是此时age被赋上了0,并返回了age的值,返回的值也会在栈中申请一块我们不知道的内存空间,用来放返回的age值,该函数调用完成,局部变量age1内存空间被收回,我们不知道名字的内存空间也被收回。

6.Student引用one调用函数setStudent1(),参数传入了3中的a 
setStudent1(Student h),该步在函数调用时,在栈中分配了一块内存用来存放类型为Student的局部变量h,h被赋值上a,此时a和h同时指向堆中的同一实例对象,h调用setage()函数来修改了age,此步与5有交叉,不做分析。该函数调用完成后,引用h的内存空间被收回,内存空间消失。但是此步骤的修改是永久性的。

7.Student的引用one调用函数setStudent2(),并传入参数4中的b

setStudent2(Student s),该步在函数调用时,在栈中分配了一块内存用来存放类型为Student的局部变量s,s被赋值上b,此时b和s同时指向堆中的同一实例对象,但是在函数内部,又在堆中new Student(1,1,"www"),new出了一块实例内存空间,此时把该实例所在堆中的地址赋值给s,所以s现在不再和b指向同一堆中实例内存空间,而是指向新的实例内存空间。当该函数调用完成,s的内存空间被收回,空间消失,而s之前指向的堆内存实例空间没有引用指向,JAVA的垃圾收集器会在一定的时机回收该堆内存空间。

关于p,q,r,系统会自动调用toString()函数,打印数据成员

而main()中,该函数执行完后,会根据入栈顺序相反的顺序而依次回收栈内存空间,在堆中new出的对象,也会在没有引用指向的同时,被Java垃圾收集器在一定的时机回收堆内存空间。

上述分析如有错误或不妥之处,请读者指正!

参考参考:点击打开链接

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: