java中内存主要包含4块,即heap(堆内存)、stack(栈内存)、data segment(静态变量或是常量存放区)、codesegment(方法区)。
>> 堆内存中存放的是new出的对象,new出的对象只包含成员变量。
>> 栈内存中:存放的是局部成员变量。对于基本的数据类型存放的是基本变量的值,而对于对象变量,存放的是堆内存的地址。
>> 静态、常量区:存放的是静态变量(类变量)或是常量。
>> 方法区:存放的是对象的方法。因此即使new出多个对象也只是存在一个方法。
例如:
A a = new A(); A中含有一个work方法,2个成员变量a,b。那么对应的内存分配为 :
① a是分配在栈内存中,里面存放了一个指向堆内存中存放的new A()的地址。
② new A()会导致在堆内存中分配一块空间,该内存中的A对象同时会含有a和b。
③ work()方法会在codesegment区中分配内存。
④ 如果此时 A b = a;则表示把a的值复制给b,即b的值为a中保存的地址。
一、基本概念
每运行一个java程序会产生一个java进程,每个java进程可能包含一个或者多个线程,每一个Java进程对应唯一一个JVM实例,每一个JVM实例唯一对应一个堆,每一个线程有一个自己私有的栈。
进程所创建的所有类的实例(也就是对象)或数组(指的是数组的本身,不是引用)都放在堆中,并由该进程所有的线程共享。
Java中分配堆内存是自动初始化的,即为一个对象分配内存的时候,会初始化这个对象中变量。虽然Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在栈中分配,也就是说在建立一个对象时在堆和栈中都分配内存,在堆中分配的内存实际存放这个被创建的对象的本身,而在栈中分配的内存只是存放指向这个堆对象的引用而已。
局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待GC回收。
具体的概念:JVM的内存可分为3个区: 堆(heap)、栈(stack)和方法区(method,也叫静态区)。
堆区
- 存储的全部是对象,每个对象都包含一个与之对应的class的信息(class的目的是得到操作指令) 。
- jvm只有一个堆区(heap),且被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身和数组本身。
栈区
- 每个线程包含一个栈区,栈中只保存基础数据类型本身和自定义对象的引用。
- 每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
- 栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
方法区(静态区)
- 被所有的线程共享,方法区包含所有的class(class是指类的原始代码,要创建一个类的对象,首先要把该类的代码加载到方法区中,并且初始化)和static变量。
- 方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
二、实例演示
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 把Sample的信息都放入方法区
/**
* 范例名称
*/
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类的加载过程。
接着,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实例。
⑤ 位于“=”前的Test1是一个在main()方法中定义的一个变量(一个Sample对象的引用),因此,它被会添加到了执行main()方法的主线程的JAVA方法调用栈中。而“=”将把这个test1变量指向堆区中的Sample实例。
⑥ JVM依次执行它们的printName()方法。当JAVA虚拟机执行test1.printName()方法时,JAVA虚拟机根据局部变量test1持有的引用,定位到堆区中的Sample实例,再根据Sample实例持有的引用,定位到方法去中Sample类的类型信息,从而获得printName()方法的字节码,接着执行printName()方法包含的指令,开始执行。
三、辨析
在Java语言里堆(heap)和栈(stack)里的区别 :
① 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
② 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享(详见下面的介绍)。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
Java中的2种数据类型
一种是 基本类型 (primitive types), 四类八种:
整型 byte shot int long
浮点型 float double
逻辑型 boolean
字符型 char
这种类型的定义是通过诸如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的情况。
另一种是 包装类数据, 如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据全部 存在于堆中, Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。
四、各种变量存储在哪里
1、在方法中声明的变量,即该变量是局部变量,每当程序调用方法时,系统都会为该方法建立一个方法栈,其所在方法中声明的变量就放在方法栈中,当方法结束系统会释放方法栈,其对应在该方法中声明的变量随着栈的销毁而结束,这就是局部变量只能在方法中有效的原因。
在方法中声明的变量可以是基本类型的变量,也可以是引用类型的变量。
(1) 当声明是基本类型的变量的时,其变量名及值(变量名及值是两个概念)是放在JAVA虚拟机栈中
(2) 当声明的是引用类型变量时,所声明的变量名(该变量实际上是在方法中存储的是内存地址值)是放在JAVA虚拟机的栈中,该变量所指向的对象是放在堆类存中的。
2、在类中声明的变量是成员变量,也叫全局变量,放在堆中的(因为全局变量不会随着某个方法执行结束而销毁)。
同样在类中声明的变量即可是基本类型的变量 也可是引用类型的变量。
(1) 当声明的是基本类型的变量其变量名及其值放在堆内存中的。
(2) 引用类型时,其声明的变量仍然会存储一个内存地址值,该内存地址值指向所引用的对象。引用变量名和对应的对象仍然存储在相应的堆中。
五、参考
https://blog.youkuaiyun.com/qq_30753945/article/details/54974899