龙空技术网

「每日一面」说一说,Java 内存分配相关的内容

小途马程序猿 183

前言:

目前小伙伴们对“c语言not a function咋解决”都比较注重,姐妹们都需要分析一些“c语言not a function咋解决”的相关资讯。那么小编同时在网上搜集了一些有关“c语言not a function咋解决””的相关内容,希望看官们能喜欢,小伙伴们一起来了解一下吧!

目录

3. Java 内存分配。 1

1、Java中的内存分配 1

2.一维数组的内存 2

(1)一个数组的内存图解 2

(2)两个数组的内存图解 2

(3)三个引用两个数组 3

(4)数组的初始化静态初始化 4

3.二维数组的内存 4

(1)二维数组格式 4

(2)二维数组格式 4

(3)二维数组格式 5

4.Java中的参数传递问题 5

4.1案例1 5

4.2案例2 7

5.对象 7

(1)一个对象 7

(2)二个对象 8

(3)三个引用两个对象 8

(4) 创建对象的步骤 9

一、栈:储存局部变量 10

二、堆:储存 new 出来的东西 11

三、方法区 12

四、本地方法区(和系统相关) 24

五、寄存器(给 CPU 使用) 24

1.基本数据类型 26

2.对象 26

3.包装类 27

4.数组 28

5.静态变量 28

1、Java中的内存分配

分类

作业

特点

栈(掌握)

存储局部变量

局部变量:定义在方法声明上和方法中的变量<br>特点:栈内存的数据用完就释放。

堆(掌握)

存储new出来的数组或对象

方法区

代码

本地方法区

和系统相关

寄存器

给CPU使用

2.一维数组的内存(1)一个数组的内存图解

首先是方法进栈,main方法圧进栈,随后变量进栈,new的对象进入堆,要使用时取堆里的地址值

(2)两个数组的内存图解

会在堆中新建连个地址空间

(3)三个引用两个数组

两个引用指向同一个实体,共用一个地址

(4)数组的初始化静态初始化3.二维数组的内存(1)二维数组格式

int [][] arr = new int[3][2]

在堆中先建立一个一维数组,一维数组的每个索引存的是地址, 默认初始化值是null

(2)二维数组格式

int arr[][] = new int[3][];

(3)二维数组格式

int[][] arr = {{1,2,3},{4,5},{6,7,8,9}}

4.Java中的参数传递问题4.1案例1

程序一:基本数据类型的值传递,不改变原值,因为调用后就会弹栈,局部变量随之消失

public static void main(String[] args) {int a = 10;int b = 20;System.out.println("a:"+a+",b:"+b);change(a,b);System.out.println("a:"+a+",b:"+b);}public static void change(int a,int b) {System.out.println("a:"+a+",b:"+b);a = b;b = a + b;System.out.println("a:"+a+",b:"+b);}

输出结果:

10 20

10 20

20 40

10 20

main方法进栈,随后change方法进栈

change执行完毕后,弹栈

4.2案例2

程序二:引用数据类型的值传递,改变原值,因为即使方法弹栈,但是堆内存数组对象还在,可以通过地址继续访问

public static void main(String[] args) {  int[] arr = {1,2,3,4,5};  change(arr);  System.out.println(arr[1]);}public static void change(int[] arr) {    for(int x=0; x<arr.length; x++) {        if(arr[x]%2==0) {       			 arr[x]*=2;      }	}}

输出 4

同样也是先main进栈,随后change进栈

最后change出栈,但堆内存区的值不会改变

5.对象(1)一个对象

将磁盘上的文件编译成字节码文件,而在运行时字节码文件进入内存的方法区,首先是Demo1_Car ,虚拟机调用main,main进栈,Car先将Car.class加载入内存,随后通过Car创建对象,在堆里创建对象,并将成员变量赋初值,String的赋值null,int的赋值为0,将对象的地址传入,在通过地址找到对象,对成员变量赋值,调用run方法,run方法进栈,运行完之后弹栈

(2)二个对象

如果没有任何引用指向该对象,那么该对象就会变成垃圾,java中有完善的垃圾回收机制,会在不定时对其进行回收

(3)三个引用两个对象

同两个对象类似,将从c2的地址赋值给c3

(4) 创建对象的步骤Student s = new Student();1,Student.class加载进内存2,声明一个Student类型引用s3,在堆内存创建对象,4,给对象中属性默认初始化值5,属性进行显示初始化6,构造方法进栈,对对象中的属性赋值,构造方法弹栈7,将对象的地址值赋值给s

同以前,创建对象后,赋初始值

再根据对属性进行显示初始化,随后虚拟机自动调用构造方法

对象创建完毕,构造方法弹栈

Java 中的内存分配

Java 程序运行时,需要在内存中分配空间。为了提高运算效率,就对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。

一、栈:储存局部变量局部变量:在方法的定义中或者在方法声明上的变量称为局部变量。特点:栈内存的数据用完就释放。二、堆:储存 new 出来的东西特点:每一个 new 出来的东西都有地址值;每个变量都有默认值 (byte, short, int, long 的默认值为 0;float, double 的默认值为 0.0;char 的默认值为 “\u0000”;boolean 的默认值为 false;引用类型为 null);使用完毕就变成垃圾,但是并没有立即回收。会有垃圾回收器空闲的时候回收。

三、方法区一个对象的运行过程:程序从 main 方法中进入;运行到 Phone p 时,在栈中开辟了一个空间;new Phone() 时,在队中开了一个内存空间,此时会有一个内存值为 0x0001;此时会找到对应的 Phone 的 class 文件,发现有三个变量和三个方法,于是将三个成员变量放在了堆中,但是此时的值为默认值(具体默认值见上)。注意,在方法区里也有一个地址值,假设为 0x001,可以认为在堆中也有一个位置,在堆中的位置,可以找到方法区中相对应的方法;继续运行,p.brand = "三星";将三星赋值给 p.brand,通过栈中的 p 找到了堆中的 brand,此时的 null 值变为“三星”。剩下的类似;当运行到 p.call("乔布斯") 时,通过栈中的 p 找到堆中存在的方法区的内存地址,从而指引到方法区中的 Phone.class 中的方法。从而将 call 方法加载到栈内存中,注意:当执行完毕后,call 方法就从栈内存中消失!剩余的如上。最后,main 方法消失!

两个对象的运行过程:程序从 main() 方法进入,运行到 Phone p 时,栈内存中开内存空间;new Phone() 时,在队中开了一个内存空间,内存值为 0x0001;此时会找到对应的 Phone 类,发现有三个变量,于是将三个成员变量放在了堆中,但是此时的值为默认值。又发现该类还存在方法,于是将该方法的内存值留在了堆中,在方法区里也有一个地址值,假设为 0x001,这个值与堆中的值相对应;程序继续运行,到 p.brand 时,进行了负值,同上;当程序运行到 Phone p2 时;到 new Phone() 时,在堆内存中开辟了内存空间 0x0002,赋值给 Phone p2;剩下跟一个对象的内存相同。

三个对象的运行过程:基本流程跟前两个无差别;但是当运行到 Phone p3 时,在栈内存中分配了一个空间,然后将 p1 的内存赋值给了 p3,即此时 Phone p3 的内存是指向 0x0001 的;继续给变量赋值,会将原来已经赋值的变量给替换掉。

Java内存分配全面浅析

本文将由浅入深详细介绍Java内存分配的原理,以帮助新手更轻松的学习Java。这类文章网上有很多,但大多比较零碎。本文从认知过程角度出发,将带给读者一个系统的介绍。

进入正题前首先要知道的是Java程序运行在JVM(Java VirtualMachine,Java虚拟机)上,可以把JVM理解成Java程序和操作系统之间的桥梁,JVM实现了Java的平台无关性,由此可见JVM的重要性。所以在学习Java内存分配原理的时候一定要牢记这一切都是在JVM中进行的,JVM是内存分配原理的基础与前提。

(1)内存区域

简单通俗的讲,一个完整的Java程序运行过程会涉及以下内存区域:

l 寄存器:JVM内部虚拟寄存器,存取速度非常快,程序不可控制。

l 栈:保存局部变量的值,包括:1.用来保存基本数据类型的值;2.保存类的实例,即堆区对象的引用(指针)。也可以用来保存加载方法时的帧。

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

l 常量池:JVM为每个已加载的类型维护一个常量池,常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)和对其他类型、方法、字段的符号引用(1)。池中的数据和数组一样通过索引访问。由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,所以常量池在Java的动态链接中起了核心作用。常量池存在于堆中。

l 代码段:用来存放从硬盘上读取的源程序代码。

l 数据段:用来存放static定义的静态成员。

下面是内存表示图:

上图中大致描述了Java内存分配,接下来通过实例详细讲解Java程序是如何在内存中运行的(注:以下图片引用自尚学堂马士兵老师的J2SE课件,图右侧是程序代码,左侧是内存分配示意图,我会一一加上注释)。

Java程序是如何在内存中运行

预备知识:

1.一个Java文件,只要有main入口方法,我们就认为这是一个Java程序,可以单独编译运行。

2.无论是普通类型的变量还是引用类型的变量(俗称实例),都可以作为局部变量,他们都可以出现在栈中。只不过普通类型的变量在栈中直接保存它所对应的值,而引用类型的变量保存的是一个指向堆区的指针,通过这个指针,就可以找到这个实例在堆区对应的对象。因此,普通类型变量只在栈区占用一块内存,而引用类型变量要在栈区和堆区各占一块内存。

调用过程演示1:

1.JVM自动寻找main方法,执行第一句代码,创建一个Test类的实例,在栈中分配一块内存,存放一个指向堆区对象的指针110925。

2.创建一个int型的变量date,由于是基本类型,直接在栈中存放date对应的值9。

3.创建两个BirthDate类的实例d1、d2,在栈中分别存放了对应的指针指向各自的对象。他们在实例化时调用了有参数的构造方法,因此对象中有自定义初始值。

调用过程演示2

调用test对象的change1方法,并且以date为参数。JVM读到这段代码时,检测到i是局部变量,因此会把i放在栈中,并且把date的值赋给i。

把1234赋给i。很简单的一步。

调用过程演示3

change1方法执行完毕,立即释放局部变量i所占用的栈空间。

调用过程演示4

调用test对象的change2方法,以实例d1为参数。JVM检测到change2方法中的b参数为局部变量,立即加入到栈中,由于是引用类型的变量,所以b中保存的是d1中的指针,此时b和d1指向同一个堆中的对象。在b和d1之间传递是指针。

调用过程演示5

change2方法中又实例化了一个BirthDate对象,并且赋给b。在内部执行过程是:在堆区new了一个对象,并且把该对象的指针保存在栈中的b对应空间,此时实例b不再指向实例d1所指向的对象,但是实例d1所指向的对象并无变化,这样无法对d1造成任何影响。

调用过程演示6

change2方法执行完毕,立即释放局部引用变量b所占的栈空间,注意只是释放了栈空间,堆空间要等待自动回收。

调用过程演示7

调用test实例的change3方法,以实例d2为参数。同理,JVM会在栈中为局部引用变量b分配空间,并且把d2中的指针存放在b中,此时d2和b指向同一个对象。再调用实例b的setDay方法,其实就是调用d2指向的对象的setDay方法。

调用过程演示8

调用实例b的setDay方法会影响d2,因为二者指向的是同一个对象。

调用过程演示9

change3方法执行完毕,立即释放局部引用变量b。

以上就是Java程序运行时内存分配的大致情况。其实也没什么,掌握了思想就很简单了。无非就是两种类型的变量:基本类型和引用类型。二者作为局部变量,都放在栈中,基本类型直接在栈中保存值,引用类型只保存一个指向堆区的指针,真正的对象在堆里。作为参数时基本类型就直接传值,引用类型传指针。

小结:

1.分清什么是实例什么是对象。Class a= new Class();此时a叫实例,而不能说a是对象。实例在栈中,对象在堆中,操作实例实际上是通过实例的指针间接操作对象。多个实例可以指向同一个对象。

2.栈中的数据和堆中的数据销毁并不是同步的。方法一旦结束,栈中的局部变量立即销毁,但是堆中对象不一定销毁。因为可能有其他变量也指向了这个对象,直到栈中没有变量指向堆中的对象时,它才销毁,而且还不是马上销毁,要等垃圾回收扫描时才可以被销毁。

3.以上的栈、堆、代码段、数据段等等都是相对于应用程序而言的。每一个应用程序都对应唯一的一个JVM实例,每一个JVM实例都有自己的内存区域,互不影响。并且这些内存区域是所有线程共享的。这里提到的栈和堆都是整体上的概念,这些堆栈还可以细分。

4.类的成员变量在不同对象中各不相同,都有自己的存储空间(成员变量在堆中的对象中)。而类的方法却是该类的所有对象共享的,只有一套,对象使用方法的时候方法才被压入栈,方法不使用则不占用内存。

常量池

以上分析只涉及了栈和堆,还有一个非常重要的内存区域:常量池,这个地方往往出现一些莫名其妙的问题。常量池是干嘛的上边已经说明了,也没必要理解多么深刻,只要记住它维护了一个已加载类的常量就可以了。接下来结合一些例子说明常量池的特性。

预备知识:

基本类型和基本类型的包装类。基本类型有:byte、short、char、int、long、boolean。基本类型的包装类分别是:Byte、Short、Character、Integer、Long、Boolean。注意区分大小写。二者的区别是:基本类型体现在程序中是普通变量,基本类型的包装类是类,体现在程序中是引用变量。因此二者在内存中的存储位置不同:基本类型存储在栈中,而基本类型包装类存储在堆中。上边提到的这些包装类都实现了常量池技术,另外两种浮点数类型的包装类则没有实现。另外,String类型也实现了常量池技术。

实例:

public class test {public static void main(String[] args) {objPoolTest();}public static void objPoolTest() {int i = 40;int i0 = 40;Integer i1 = 40;Integer i2 = 40;Integer i3 = 0;Integer i4 = new Integer(40);Integer i5 = new Integer(40);Integer i6 = new Integer(0);Double d1=1.0;Double d2=1.0;System.out.println("i=i0\t" + (i == i0));System.out.println("i1=i2\t" + (i1 == i2));System.out.println("i1=i2+i3\t" + (i1 == i2 + i3));System.out.println("i4=i5\t" + (i4 == i5));System.out.println("i4=i5+i6\t" + (i4 == i5 + i6));System.out.println("d1=d2\t" + (d1==d2));System.out.println();}}
结果:
i=i0 truei1=i2 truei1=i2+i3 truei4=i5 falsei4=i5+i6 trued1=d2 false
结果分析

1.i和i0均是普通类型(int)的变量,所以数据直接存储在栈中,而栈有一个很重要的特性:栈中的数据可以共享。当我们定义了int i = 40;,再定义int i0 = 40;这时候会自动检查栈中是否有40这个数据,如果有,i0会直接指向i的40,不会再添加一个新的40。

2.i1和i2均是引用类型,在栈中存储指针,因为Integer是包装类。由于Integer 包装类实现了常量池技术,因此i1、i2的40均是从常量池中获取的,均指向同一个地址,因此i1=12。

3.很明显这是一个加法运算,\Java\的数学运算都是在栈中进行的,**Java**会自动对**i1**、**i2**进行拆箱操作转化成整型**,因此i1在数值上等于i2+i3。

4.i**4和i5 均是引用类型,在栈中存储指针,因为Integer是包装类。但是由于他们各自都是new出来的,因此不再从常量池寻找数据,而是从堆中各自new一个对象,然后各自保存指向对象的指针,所以i4和i5不相等,因为他们所存指针不同,所指向对象不同。

5.这也是一个加法运算,和3同理。

6.d1和d2均是引用类型,在栈中存储指针,因为Double是包装类。但Double包装类没有实现常量池技术,因此Doubled1=1.0;相当于Double d1=new Double(1.0);,是从堆new一个对象,d2同理。因此d1和d2存放的指针不同,指向的对象不同,所以不相等。

小结:

1.以上提到的几种基本类型包装类均实现了常量池技术,但他们维护的常量仅仅是【-128至127】这个范围内的常量,如果常量值超过这个范围,就会从堆中创建对象,不再从常量池中取。比如,把上边例子改成Integer i1 = 400; Integer i2 = 400;,很明显超过了127,无法从常量池获取常量,就要从堆中new新的Integer对象,这时i1和i2就不相等了。

2.String类型也实现了常量池技术,但是稍微有点不同。String型是先检测常量池中有没有对应字符串,如果有,则取出来;如果没有,则把当前的添加进去。

凡是涉及内存原理,一般都是博大精深的领域,切勿听信一家之言,多读些文章。我在这只是浅析,里边还有很多猫腻,就留给读者探索思考了。希望本文能对大家有所帮助!

脚注:

(1) 符号引用,顾名思义,就是一个符号,符号引用被使用的时候,才会解析这个符号。如果熟悉linux或unix系统的,可以把这个符号引用看作一个文件的软链接,当使用这个软连接的时候,才会真正解析它,展开它找到实际的文件

对于符号引用,在类加载层面上讨论比较多,源码级别只是一个形式上的讨论。

当一个类被加载时,该类所用到的别的类的符号引用都会保存在常量池,实际代码执行的时候,首次遇到某个别的类时,JVM会对常量池的该类的符号引用展开,转为直接引用,这样下次再遇到同样的类型时,JVM就不再解析,而直接使用这个已经被解析过的直接引用。

除了上述的类加载过程的符号引用说法,对于源码级别来说,就是依照引用的解析过程来区别代码中某些数据属于符号引用还是直接引用,如,System.out.println("test" +"abc");//这里发生的效果相当于直接引用,而假设某个Strings = "abc"; System.out.println("test" + s);//这里的发生的效果相当于符号引用,即把s展开解析,也就相当于s是"abc"的一个符号链接,也就是说在编译的时候,class文件并没有直接展看s,而把这个s看作一个符号,在实际的代码执行时,才会展开这个。

参考文章:

java内存分配研究:

Java常量池详解之一道比较蛋疼的面试题:

jvm常量池:

深入Java核心 Java内存分配原理精讲:

四、本地方法区(和系统相关)五、寄存器(给 CPU 使用)Java内存分配总结Java 的内存管理就是对象的分配和释放的处理

1.分配:通过关键字new创建对象分配内存空间,对象存在堆中。

2.释放 :对象的释放是由垃圾回收机制决定和执行的,开发人员可以将经历集中在业务的开发上

Java内存泄漏:

当对象存在内存的引用,却不会再继续使用,对象会占用内存无法被GC回收,这些对象就会判定为内存泄漏。

Java内存区域划分:

1.栈:

在函数中定义的基本类型变量和对象的引用变量都在函数的栈内存中分配。栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

2.堆:

通过new生成的对象都存放在堆中,对于堆中的对象生命周期的管理由Java虚拟机的垃圾回收机制GC进行回收和统一管理。优点是可以动态分配内存大小,缺点是由于动态分配内存导致存取速度慢。

3.方法区:

是各个线程共享的内存区域,它用于存储class二进制文件,包含了虚拟机加载的类信息、常量(常量池)、静态变量(静态域)、即时编译后的代码等数据。

1.常量池:

常量池在编译期间就将一部分数据存放于该区域,包含以final修饰的基本数据类型的常量值、String字符串。

2.静态域:

存放类中以static声明的静态成员变量。

3.程序计数器

当前线程所执行的行号指示器。通过改变计数器的值来确定下一条指令,比如循环,分支,跳转,异常处理,线程恢复等都是依赖计数器来完成。

堆和栈在内存中的区别是什么各种数据在内存中的存储方式:1.基本数据类型

众所周知,基本数据类型有8种,它们存储于栈中。

int num = 5,这里的 num 是一个指向 int 类型的引用,指向5这个字面值。这些字面值的数据,由于大小可知,生存期可知,出于追求速度的原因,就存在于栈中。

栈中的数据共享:已存在的值不会再次创建

int a = 3;

int b = 3;

编译器先处理 int a = 3;首先它会在栈中创建一个变量为 a 的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将 a 指向3的地址。接着处理 int b = 3;在创建完 b 这个引用变量后,由于在栈中已经有3这个字面值,便将 b 直接指向3的地址。这样,就出现了 a 与 b 同时均指向3的情况。

2.对象

举一个万年不变的Person类:

public class Person {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}}

一个类通过使用new运算符可以创建多个不同的对象实例,这些对象实例将在堆中被分配不同的内存空间,改变其中一个对象的状态不会影响其他对象的状态。

Person one = new Person("小明",15);Person two = new Person("小王",17);

在堆内存中只创建了一个对象实例,在栈内存中创建了两个对象引用,两个对象引用同时指向一个对象实例。

Person one = new Person("小明",15);Person two = one;
3.包装类

基本类型的定义都是直接在栈中,如果用包装类来创建对象,就和普通对象一样了。

比如int a = 5,a存储在栈中。而Integer i = new Integer(5),i 对象数据存储在堆中,i 的引用存储在栈中。

4.数组

数组是一种引用类型,数组用来存储同一种数据类型的数据,一旦初始化完成,即所占的空间就已固定下来,即使某个元素被清空,但其所在空间仍然保留,因此数组长度将不能被改变。

举例:执行int[] array = new int[5]时,首先会在栈中创建引用变量,在堆中开辟5个int型数据的空间,该引用变量存放数组首地址,即实现数组名来引用数组。

5.静态变量

static 的修饰的变量和方法会存在静态域,类变量会共同使用同一块变量地址。即改变该静态变量,该所有类对象所使用的变量值都会随之改变。

关联面试题1、对象==和基本数据==

"=="符号判断的内存地址是否是同一块。

int a=5;int b=5;System.out.println(a==b);//trueString str1 = "hello";String str2 = "he" + new String("llo");System.err.println(str1 == str2); //false

因为a在栈中创建了存放值为5的地址,创建b的时候,由于数据共享,已经有5的值就不会再创建,将b指向5,所有是同样的地址。但是String拼接总是会创建新的对象,是在常量池中创建的是不同的地址。

2、内存分配综合情况分析:

public class Dog {Collar c;String name;//1. main()方法位于栈上public static void main(String[] args) {//2. 在栈上创建引用变量d,但Dog对象尚未存在Dog d;//3. 创建新的Dog对象,并将其赋予d引用变量d = new Dog();//4. 将引用变量的一个副本传递给go()方法d.go(d);}//5. 将go()方法置于栈上,并将dog参数作为局部变量void go(Dog dog){//6. 在堆上创建新的Collar对象,并将其赋予Dog的实例变量c =new Collar();}//7.将setName()添加到栈上,并将dogName参数作为其局部变量void setName(String dogName){//8. name的实例对象也引用String对象name=dogName;}//9. 程序执行完成后,setName()将会完成并从栈中清除,此时,局部变量dogName也会消失,尽管它所引用的String仍在堆上}
3、Java中==和equals的区别,equals和hashCode的区别

Java中==和equals的区别,equals和hashCode的区别

==用于基本数据类型用比较,比较的是值是否相等

==用于对象,比较的是在内存中的地址是否相等

Equals表示引用所指内容是否相等。

4、Android为每个应用程序分配的默认内存大小是多少

16M,可以申请使用更大内存

5、String s1=”ab”,String s2=”a”+”b”,String s3=”a”,String s4=”b”,s5=s3+s4请问s5==s2返回什么?

返回false.在编译过程中,编译器会将s2直接优化为”ab”,会将其放置在常量池当中,s5则是被创建在堆区,相当于s5=new String(“ab”);内存地址不一样

7、引用

1、

2、

3、Java内存分配全面浅析

4、

标签: #c语言not a function咋解决