`
hujin1979
  • 浏览: 80126 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

java内存管理概论

 
阅读更多

Java把内存分成两种,一种叫做栈内存,一种叫做堆内存

在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。

堆内存用于存放由new创建的对象和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,在栈中的这个特殊的变量就变成了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号。

引用变量是普通变量,定义时在栈中分配内存,引用变量在程序运行到作用域外释放。而数组&对象本身在堆中分配,即使程序运行到使用new产生数组和对象的语句所在地代码块之外,数组和对象本身占用的堆内存也不会被释放,数组和对象在没有引用变量指向它的时候,才变成垃圾,不能再被使用,但是仍然占着内存,在随后的一个不确定的时间被垃圾回收器释放掉。这个也是java比较占内存的主要原因,实际上,栈中的变量指向堆内存中的变量,这就是 Java 中的指针!

 java中内存分配策略及堆和栈的比较 

1 内存分配策略 
     按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的. 

     静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求. 

    栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存.和我们在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配。 

     静态存储分配要求在编译时能知道所有变量的存储要求,栈式存储分配要求在过程的入口处必须知道所有的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例.堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放. 


 2 堆和栈的比较 

       上面的定义从编译原理的教材中总结而来,除静态存储分配之外,都显得很呆板和难以理解,下面撇开静态存储分配,集中比较堆和栈: 

       从堆和栈的功能和作用来通俗的比较,堆主要用来存放对象的,栈主要是用来执行程序的.而这种不同又主要是由于堆和栈的特点决定的: 

       在编程中,例如C/C++中,所有的方法调用都是通过栈来进行的,所有的局部变量,形式参数都是从栈中分配内存空间的。实际上也不是什么分配,只是从栈顶向上用就行,就好像工厂中的传送带(conveyor belt)一样,Stack Pointer会自动指引你到放东西的位置,你所要做的只是把东西放下来就行.退出函数的时候,修改栈指针就可以把栈中的内容销毁.这样的模式速度最快, 当然要用来运行程序了.需要注意的是,在分配的时候,比如为一个即将要调用的程序模块分配数据区时,应事先知道这个数据区的大小,也就说是虽然分配是在程序运行时进行的,但是分配的大小多少是确定的,不变的,而这个"大小多少"是在编译时确定的,不是在运行时. 

       堆是应用程序在运行的时候请求操作系统分配给自己内存,由于从操作系统管理的内存分配,所以在分配和销毁时都要占用时间,因此用堆的效率非常低.但是堆的优点在于,编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间,因此,用堆保存数据时会得到更大的灵活性。事实上,面向对象的多态性,堆内存分配是必不可少的,因为多态变量所需的存储空间只有在运行时创建了对象之后才能确定.在C++中,要求创建一个对象时,只需用 new命令编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保存.当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!这也正是导致我们刚才所说的效率低的原因,看来列宁同志说的好,人的优点往往也是人的缺点,人的缺点往往也是人的优点(晕~). 


 3 JVM中的堆和栈 

       JVM是基于堆栈的虚拟机.JVM为每个新创建的线程都分配一个堆栈.也就是说,对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。 

       我们知道,某个线程正在执行的方法称为此线程的当前方法.我们可能不知道,当前方法使用的帧称为当前帧。当线程激活一个Java方法,JVM就会在线程的 Java堆栈里新压入一个帧。这个帧自然成为了当前帧.在此方法执行期间,这个帧将用来保存参数,局部变量,中间计算过程和其他数据.这个帧在这里和编译原理中的活动纪录的概念是差不多的. 

       从Java的这种分配机制来看,堆栈又可以这样理解:堆栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。 

       每一个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享.跟C/C++不同,Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。 



 Java 中的堆和栈 

        Java把内存划分成两种:一种是栈内存,一种是堆内存。 

        在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。 

        当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。 

        堆内存用来存放由new创建的对象和数组。 

        在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。 

        在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。 

        引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。 

        具体的说: 
       栈与堆都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。 

       Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。 

       栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量(,int, short, long, byte, float, double, boolean, char)和对象句柄。 

       栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义: 

       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=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。
 
      java虚拟机栈(java virtual machine stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(stack frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。    
      经常有人把java内存区分为堆内存(Heap)和栈内存(Stack),这种分法比较粗糙,java内存区域的划分其实远比这复杂。这种流行方式的划分只能说明大多数程序员最关注的、与对象内存分配关系最密切的内存区域是这两块。其中所指的“栈”就是虚拟机栈,或者说是虚拟机栈中的局部变量表部分。
      局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
      其中64位长度的long和double类型的数据会占用2个局部变量空间,其余的数据类型只占用一个。局部变量表所需的内存空间在编译期间完成分配。当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
      在java虚拟机规范中,对这个区域规定了2种异常情况:如果线程请求的栈深度大与虚拟机所允许的深度,将抛出StackOverFlowError异常;如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。
     对于大多数应用来说,java堆(java Heap)是java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配,但是随着JIT编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也逐渐变得不是那么“绝对”了。
     Java堆是垃圾收集器管理的主要区域,因此很多时候也被称为“GC”堆。如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以java堆中还可以细分为:新生代和老生代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。如果从内存分配的角度看,线程共享的java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。不过,无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好的回收内存,或者更快的分配内存。
      根据java虚拟机规范的规定,java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常
分享到:
评论

相关推荐

    java 知识点整理

    - Java内置了对多线程的支持,通过`Thread`类或实现`Runnable`接口可以创建并管理线程。 9. **IO流**: - Java的I/O流提供了处理输入输出的强大工具,包括字节流和字符流,以及缓冲流、过滤流等高级概念。 10. *...

    java习题(含答案).doc

    Java能检查程序在变异和运行时的错误,通过垃圾回收机制自动管理内存,避免了内存泄露,以及实现了真正的数组,防止数据覆盖。 Java语言的基础部分涵盖变量、数据类型、运算符、流程控制语句等基本概念。面向对象...

    Java企业应用开发核心知识体系概述

    3. **内存分代**:分代理论是Java内存管理的重要概念,它依据对象生命周期的不同阶段将其分配在不同的内存区域,以提高垃圾收集的效率。 4. **内存压缩(压缩整理)**:在老年代,尤其是大型Java应用中,为了防止...

    第2章 Java程序设计语言概论.ppt

    Java虚拟机还负责内存管理、垃圾回收等任务,以提高程序的性能和稳定性。 在编程实践中,变量和数据类型是构建程序的基础,Java提供了多种基本数据类型,如整型、浮点型、字符型和布尔型,以及引用类型如类、接口...

    JAVA程序设计习题库及答案(非常全面)

    **解析:** Java通过内置的垃圾回收(GC)机制自动管理内存。当对象不再被引用时,GC会自动回收这些对象占用的内存空间,从而避免了内存泄漏等问题。 8. **题目:** 在JAVA语言中,将后缀名为_java_____的源代码文件...

    java练习题及答案

    3. **内存管理**:Java自动管理内存,减少了由于手动内存管理不当导致的常见错误,如内存泄漏和野指针问题。 4. **真数组安全**:Java的数组实现了边界检查,避免了数组越界导致的数据覆盖风险。 Java的执行模式是...

    JAVA分章习题库-四川轻化工大学-电子商务系

    - Java的执行过程特点包括多线程、异常处理和内存管理等。 2. Java语言的执行: - Java源代码文件编译后形成字节码文件(.class)。 - Java的执行过程涉及到JDK工具,如javac.exe(Java编译器)、java.exe(Java...

    布鲁克希尔-计算机科学概论(中英文版)

    2. **计算机硬件**:计算机的物理组成部分,如中央处理器(CPU)、内存、硬盘、显示器、键盘等,它们共同构成了计算机的实体。 3. **计算机软件**:运行在计算机上的程序和数据,包括操作系统、应用程序、编程语言...

    JAVA程序设计习题库及答案(下载).pdf

    Java通过垃圾回收机制自动管理内存,减少了内存泄漏的可能性。 - D. Java实现了真正的数组,避免了覆盖数据的问题。 - **答案**:B(这并非是鲁棒性的直接体现,而是Java的一个显著特点)。 2. **执行模式**: ...

    JAVA程序设计习题库及答案(免费下载)

    - **鲁棒性**:Java的鲁棒性体现在它能检查并处理程序运行时的错误,如类型检查、内存管理等,确保程序稳定运行。 - **执行模式**:Java采用半编译和半解释型执行模式,源代码先编译成字节码,然后在Java虚拟机...

    JAVA程序设计习题库及答案(免费下载)

    Java通过垃圾回收机制自动管理内存,减少了内存出错的可能性。 - D. Java提供了真正的数组支持,避免了数组越界的问题。 - B选项提到的“运行虚拟机实现跨平台”实际上是一种平台无关性的特性,并非鲁棒性的一...

    Java编程语言在大数据开发中的应用探究.pdf

    Java语言是基于C和C++语言的进一步发展,保留了它们的许多语法特性,但同时对某些复杂操作进行了简化,例如自动垃圾回收机制,减轻了程序员对内存管理的负担。此外,Java引入了异常处理、类型安全管理和自动拆装等...

    重难点之java复习资料.pdf

    首先,Java的鲁棒性特点包括检查程序在变异和运行时的错误、实现跨平台运行(通过Java虚拟机JVM)、减少内存出错可能性(自动内存管理)以及实现真数组避免数据覆盖。Java的执行模式是半编译和半解释型,意味着源...

    计算机科学概论 内尔戴尔 第五版全书答案.pdf

    3. 英国数学家和发明家巴贝奇(Charles Babbage)设计了第一台包含内存的机械计算机,尽管由于技术限制没有完全实现。 4. 艾达·洛夫莱斯(Ada Lovelace)被认为是世界上第一位程序员,她为巴贝奇的分析机编写了算法,并...

    JAVA里的一些经典习题

    内存管理机制** - **答案解析:**垃圾回收。Java通过自动垃圾回收机制来管理内存。 **8. 字节码文件的后缀名** - **答案解析:**.class。Java源代码文件编译后形成的字节码文件扩展名为`.class`。 **9. 执行模式...

    Java-图书馆管理系统(附全代码)-课程设计报告.docx

    【Java 图书馆管理系统】是一个基于Java编程语言和数据库技术实现的小型图书管理软件,用于高校图书馆的信息管理和自动化操作。该系统旨在提高图书借阅、归还和查询的效率,确保图书流通的有序进行。 系统的主要...

    计算机科学概论.docx

    "计算机科学概论.docx" 计算机科学是研究计算机及其应用的基础学科,它涉及到计算机体系结构、操作系统、数据库、算法设计、程序设计语言等多个方面。计算机科学的重要性及其对现代社会的影响是非常重要的,它已经...

Global site tag (gtag.js) - Google Analytics