JAVA内存管理

被问到有关Java内存管理的知识,因此要搜集整理一下了。开始以前,咱们要明白一点,咱们所使用的变量就是一块一块的内存空间!!java

1、内存管理原理:

在java中,有java程序、虚拟机、操做系统三个层次,其中java程序与虚拟机交互,而虚拟机与操做系统间交互!这就保证了java程序的平台无关性!下面咱们从程序运行前,程序运行中、程序运行内存溢出三个阶段来讲一下内存管理原理!
一、程序运行前:JVM向操做系统请求必定的内存空间,称为初始内存空间!程序执行过程当中所需的内存都是由java虚拟机从这片内存空间中划分的。
二、程序运行中:java程序一直向java虚拟机申请内存,当程序所须要的内存空间超出初始内存空间时,java虚拟机会再次向操做系统申请更多的内存供程序使用!
三、内存溢出:程序接着运行,当java虚拟机已申请的内存达到了规定的最大内存空间,但程序还须要更多的内存,这时会出现内存溢出的错误!
至此能够看出,Java 程序所使用的内存是由 Java 虚拟机进行管理、分配的。Java 虚拟机规定了 Java 程序的初始内存空间和最大内存空间,开发者只须要关心 Java 虚拟机是如何管理内存空间的,而不用关心某一种操做系统是如何管理内存的。  
 
2、 RUNTIME 类的使用:
 
Java 给咱们提供了Runtime 类获得JVM 内存的信息
 方法名称  参数 做用  返回值 
 getRuntime   无  获取 Runtime 对象   Runtime 对象 
 totalMemory   无  获取 JVM 分配给程序的内存数量   long:内存数量 
 freeMemory  无  获取 当前可用的内存数量   long:内存数量 
 maxMemory   无  获取 JVM 能够申请到的最大内存数量  long:内存数量 
   
 
3、内存空间逻辑划分:
 
JVM 会把申请的内存从逻辑上划分为三个区域,即:方法区、堆与栈。 
方法区方法区默认最大容量为64M,Java虚拟机会将加载的java类存入方法区,保存类的结构(属性与方法),类静态成员等内容。
堆:默认最大容量为64M,堆存放对象持有的数据,同时保持对原类的引用。能够简单的理解为对象属性的值保存在堆中,对象调用的方法保存在方法区。
:栈默认最大容量为1M,在程序运行时,每当遇到方法调用时,Java虚拟机就会在栈中划分一块内存称为栈帧(Stack frame),栈帧中的内存供局部变量(包括基本类型与引用类型)使用,当方法调用结束后,Java虚拟机会收回此栈帧占用的内存。 
 
4、java数据类型
 
 
一、基本数据类型:没封装指针的变量。
声明此类型变量,只会在栈中分配一块内存空间。
 
二、引用类型:就是底层封装指针的数据类型。
他们在内存中分配两块空间,第一块内存分配在栈中,只存放别的内存地址,不存放具体数值,咱们也把它叫指针类型的变量,第二块内存分配在堆中,存放的是具体数值,如对象属性值等。
 
三、下面咱们从一个例子来看一看:
public class Student { 

  String stuId; 

  String stuName; 

  int stuAge; 

} 

 


public class TestStudent { 

  public static void main(String[] args) { 

    Student s = new Student(); 

    String name = new String("云鹤");  

    int a = 10; 

    char b = 'm'; 

    s.stuId = "6363"; 

    s.stuName = "刘德华"; 

    s.stuAge = 25; 

  } 

}

(1)类固然是存放在方法区里面的。数组

(2) Student s = new Student(); 
这行代码就建立了两块内存空间,第一个在栈中,名字叫s,它就至关于指针类型的变量,咱们看到它并不存放学生的姓名、年龄等具体的数值,而是存放堆中第二块内存的地址,第二块才存放具体的数值,如学生的编号、姓名、年龄等信息。
 
(3) int a = 10; 
这是 基本数据类型 变量,具体的值就存放在栈中,并无只指针的概念!
 
下图就是本例的内存布置图:
 
此外咱们还要知道 Student s = new Student(); 包括了声明和建立,即:
声明: Student s;
建立:s = new Student();
其中声明只是在栈中声明一个空间,但尚未具体的值,声明后的状况以下图所示:
建立后的状况以下图所示:
 
 
(4) 引用类型中的数组也封装了指针,即使是基本数据类型的数组也封装了指针,数组也是引用类型。好比代码int[] arr = new int[]{3, 6, 12, 9, 66, 31};以下图所示:
 
 5、java值传参与引用传参
 
(1)参数根据调用后的效果不一样,便是否改变参数的原始数值,又能够分为两种: 按值传递的参数按引用传递的参数
按值传递的参数原始数值不改变,按引用传递的参数原始数值改变!这是为何呢?其实至关简单:
咱们知道 基本数据类型的变量存放在栈里面,变量名处存放的就是变量的值,那么当基本数据类型的变量做为参数时,传递的就是这个值,只是把变量的值传递了过去, 无论对这个值如何操做,都不会改变变量的原始值。而 对引用数据类型的变量来讲,变量名处存放的地址,因此引用数据类型的变量做为传参时, 传递的其实是地址,对地址处的内容进行操做,固然会改变变量的值了!
正常状况下,咱们用数组测试TestArray类以下:
public class TestArray { 

      void change(int[] arr) { 

            for(int i=0;i<arr.length;i++)
               if(i%2==0)
                   arr[i]=1000;
            System.out.println("方法体内修改值后:" ); 
            for(int i=0;i<arr.length;i++)
                System.out.println(arr[i]);
      }

public static void main(String[] args) { 


    int[] a = {1,2,3,4}; 

    TestArray testString = new TestArray(); 

    System.out.println("方法调用前:"); 
    for(int i=0;i<a.length;i++)
        System.out.println(a[i]);
    testString.change(a); 
    System.out.println("方法调用后:"); 
    for(int i=0;i<a.length;i++)
        System.out.println(a[i]);
  } 

    }

输出结果以下:多线程

方法调用前:
1
2
3
4
方法体内修改值后:
1000
2
1000
4
方法调用后:
1000
2
1000
4

数组实际上也是引用类型,在调用函数的过程当中改变了其值。函数

 (2)特例:String性能

 
public class TestString { 

      void change(String str) { 

            str = "吴奇隆"; 

            System.out.println("方法体内修改值后:" + str); 
    
      }

public static void main(String[] args) { 


    String name = "歌星"; 

    TestString testString = new TestString(); 

    System.out.println("方法调用前:" + name); 
    testString.change(name); 
    System.out.println("方法调用后:" + name); 

  } 

    

结果:测试

方法调用前:歌星
方法体内修改值后:吴奇隆
方法调用后:歌星

分析:ui

上例中,虽然参数String 是引用数据类型,但其值没有发生改变,这是由于String 类是final 的,它是定长,不容许对其进行改变,而StringBuffer(多线程下使用性能优)和StringBuilder(单线程下面使用性能优)是能够改变的。若是这里用StringBuffer和SringBuiler替代,结果和Array的使用同样,中间结果会被改变。
咱们看初始状况,即String name = "歌星";这行代码运行
完,以下图:
 
 
 当调用方法时testString.change(name),内存变化为:
 
 
 
在方法体内,参数str赋予一个新值,str = "吴奇隆"。由于"吴奇隆"这个String是定长,系统就会在堆中分配一块新的内存空间37DF,这样str指向了新的内存空间37DF,而name仍是指向36DF, 37DF的改变对它已没影响:
 
 
最后,方法调用结束,str与37DF的内存空间消亡。Name的值依然为歌星,并无改变。
因此String虽然是引用类型参数,但值依然不变:
 
 
 (3)没法交换的例子:
 
public class TestChange { 

  void change(Student stu1, Student stu2) { 

    stu1.stuAge ++; 

    stu2.stuAge ++; 

    Student stu = stu1; 

    stu1 = stu2; 

    stu2 = stu; 

  } 

   

  public static void main(String[] args) { 

     

    Student z = new Student(); 

    z.stuName = "张信哲"; 

    z.stuAge = 40; 

     

    Student r = new Student(); 

    r.stuName = "任贤齐"; 

    r.stuAge = 30; 

    System.out.println("交换前z:\t"+z.stuName+"\t"+z.stuAge); 
    System.out.println("交换前r:\t"+r.stuName+"\t"+r.stuAge); 

    TestChange testChange = new TestChange(); 

    testChange.change(z, r);      

    System.out.println("交换后z:\t"+z.stuName+"\t"+z.stuAge); 
    System.out.println("交换后r:\t"+r.stuName+"\t"+r.stuAge); 

  } 
  
} 

运行结果:spa

交换前z:    张信哲    40
交换前r:    任贤齐    30
交换后z:    张信哲    41
交换后r:    任贤齐    31

 

相关文章
相关标签/搜索