java程序运行时内存分配详解

java程序运行时内存分配详解

 

这篇文章主要介绍了java程序运行时内存分配详解 ,须要的朋友能够参考下
 

1、 基本概念java

   每运行一个java程序会产生一个java进程,每一个java进程可能包含一个或者多个线程,每个Java进程对应惟一一个JVM实例,每个JVM实例惟一对应一个堆,每个线程有一个本身私有的栈。进程所建立的全部类的实例(也就是对象)或数组(指的是数组的自己,不是引用)都放在堆中,并由该进程全部的线程共享。Java中分配堆内存是自动初始化的,即为一个对象分配内存的时候,会初始化这个对象中变量。虽然Java中全部对象的存储空间都是在堆中分配的,可是这个对象的引用倒是在栈中分配,也就是说在创建一个对象时在堆和栈中都分配内存,在堆中分配的内存实际存放这个被建立的对象的自己,而在栈中分配的内存只是存放指向这个堆对象的引用而已。局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间马上被回收,堆空间区域等待GC回收。程序员

具体的概念:JVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method,也叫静态区):数组

堆区: app

1.存储的所有是对象,每一个对象都包含一个与之对应的class的信息(class的目的是获得操做指令) ;
2.jvm只有一个堆区(heap),且被全部线程共享,堆中不存放基本类型和对象引用,只存放对象自己和数组自己;jvm

栈区:
1.每一个线程包含一个栈区,栈中只保存基础数据类型自己和自定义对象的引用;
2.每一个栈中的数据(原始类型和对象引用)都是私有的,其余栈不能访问;
3.栈分为3个部分:基本类型变量区、执行环境上下文、操做指令区(存放操做指令);测试

方法区(静态区):
1.被全部的线程共享,方法区包含全部的class(class是指类的原始代码,要建立一个类的对象,首先要把该类的代码加载到方法区中,而且初始化)和static变量。
2.方法区中包含的都是在整个程序中永远惟一的元素,如class,static变量。
 this

2、实例演示spa

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();  
}  
}  
 
public class Sample  //运行时, jvm 把appmain的信息都放入方法区  
{  
/** 范例名称 */  
private String name; //new Sample实例后, name 引用放入栈区里, name 对应的 String 对象放入堆里  
 
/** 构造方法 */  
public Sample(String name)  
{  
this .name = name;  
}  
 
/** 输出 */  
public void printName() //在没有对象的时候,print方法跟随sample类被放入方法区里。  
{  
System.out.println(name);  
}  
}

运行该程序时,首先启动一个Java虚拟机进程,这个进程首先从classpath中找到AppMain.class文件,读取这个文件中的二进制数据,而后把Appmain类的类信息存放到运行时数据区的方法区中,这就是AppMain类的加载过程。code

接着,Java虚拟机定位到方法区中AppMain类的Main()方法的字节码,开始执行它的指令。这个main()方法的第一条语句就是:

复制代码 代码以下:

  Sample test1=new Sample("测试1");
 

该语句的执行过程:
    一、 Java虚拟机到方法区找到Sample类的类型信息,没有找到,由于Sample类尚未加载到方法区(这里能够看出,java中的内部类是单独存在的,并且刚开始的时候不会跟随包含类一块儿被加载,等到要用的时候才被加载)。Java虚拟机立马加载Sample类,把Sample类的类型信息存放在方法区里。
    二、 Java虚拟机首先在堆区中为一个新的Sample实例分配内存, 并在Sample实例的内存中存放一个方法区中存放Sample类的类型信息的内存地址。
    三、 JVM的进程中,每一个线程都会拥有一个方法调用栈,用来跟踪线程运行中一系列的方法调用过程,栈中的每个元素就被称为栈帧,每当线程调用一个方法的时候就会向方法栈压入一个新帧。这里的帧用来存储方法的参数、局部变量和运算过程当中的临时数据。

    四、位于“=”前的Test1是一个在main()方法中定义的一个变量(一个Sample对象的引用),所以,它被会添加到了执行main()方法的主线程的JAVA方法调用栈中。而“=”将把这个test1变量指向堆区中的Sample实例。
    五、JVM在堆区里继续建立另外一个Sample实例,并在main方法的方法调用栈中添加一个Test2变量,该变量指向堆区中刚才建立的Sample新实例。

    六、JVM依次执行它们的printName()方法。当JAVA虚拟机执行test1.printName()方法时,JAVA虚拟机根据局部变量test1持有的引用,定位到堆区中的Sample实例,再根据Sample实例持有的引用,定位到方法去中Sample类的类型信息,从而得到printName()方法的字节码,接着执行printName()方法包含的指令,开始执行。

3、辨析

  在Java语言里堆(heap)和栈(stack)里的区别 :
    1. 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不一样,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
  2. 栈的优点是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是肯定的,缺少灵活性。另外,栈数据能够共享(详见下面的介绍)。堆的优点是能够动态地分配内存大小,生存期也没必要事先告诉编译器,Java的垃圾收集器会自动收走这些再也不使用的数据。但缺点是,因为要在运行时动态分配内存,存取速度较慢。

Java中的2种数据类型:

  一种是基本类型(primitive types), 共有8类,即int, short, long, byte, float, double, boolean, char(注意,并无string的基本类型)。这种类型的定义是经过诸如int a = 3; long b = 255L;的形式来定义的,称为自动变量。自动变量存的是字面值,不是类的实例,即不是类的引用,这里并无类的存在。如int a = 3; 这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,因为大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的缘由,就存在于栈中。
  

    栈有一个很重要的特性:存在栈中的数据能够共享。假设咱们同时定义:  int a = 3;  int b = 3;  编译器先处理int a = 3;首先它会在栈中建立一个变量为a的引用,而后查找有没有字面值为3的地址,若是没找到,就开辟一个存放3这个字面值的地址,而后将a指向3的地址。接着处理int b = 3;在建立完b的引用变量后,因为在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的状况。

  这种字面值的引用与类对象的引用不一样。假定两个类对象的引用同时指向一个对象,若是一个对象引用变量修改了这个对象的内部状态,那么另外一个对象引用变量也即刻反映出这个变化。相反,经过字面值的引用来修改其值,不会致使另外一个指向此字面值的引用的值也跟着改变的状况。如上例,咱们定义完a与 b的值后,再令a=4;那么,b不会等于4,仍是等于3。在编译器内部,遇到a=4;时,它就会从新搜索栈中是否有4的字面值,若是没有,从新开辟地址存放4的值;若是已经有了,则直接将a指向这个地址。所以a值的改变不会影响到b的值。
  另外一种是包装类数据,如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据所有存在于堆中,Java用new()语句来显示地告诉编译器,在运行时才根据须要动态建立,所以比较灵活,但缺点是要占用更多的时间。

4、总结

      java内存分配条理仍是很清楚的,若是要完全搞懂,能够去查阅JVM相关的书籍。在java中,内存分配最让人头疼的是String对象,因为其特殊性,因此不少程序员容易搞混淆,下一篇文章再详细讲解。

相关文章
相关标签/搜索