搜索
首页Javajava教程详解Java的堆内存与栈内存的存储机制

堆与内存优化
    今天测了一个项目的数据自动整理功能,对数据库中几万条记录及图片进行整理操作,运行接近到最后,爆出了java.lang.outOfMemoryError,java heap space方面的错误,以前写程序很少遇到这种内存上的错误,因为java有垃圾回收器机制,就一直没太关注。今天上网找了点资料,在此基础上做了个整理。

 一、堆和栈

    堆—用new建立,垃圾回收器负责回收

         1、程序开始运行时,JVM从OS获取一些内存,部分是堆内存。堆内存通常在存储地址的底层,向上排列。              

         2、堆是一个"运行时"数据区,类实例化的对象就是从堆上去分配空间的;       

         3、在堆上分配空间是通过"new"等指令建立的,堆是动态分配的内存大小,生存期也不必事先告诉编译器;

         4、与C++不同的是,Java自动管理堆和栈,垃圾回收器可以自动回收不再使用的堆内存;       

         5、缺点是,由于要在运行时动态分配内存,所以内存的存取速度较慢。

    栈—存放基本类型和引用类型,速度快

         1、先进后出的数据结构,通常用于保存方法中的参数,局部变量;

         2、在java中,所有基本类型(short,int, long, byte, float, double,boolean, char)和引用类型的变量都在栈中存储;

         3、栈中数据的生存空间一般在当前scopes内(由{...}括起来的区域;

         4、栈的存取速度比堆要快,仅次于直接位于CPU中的寄存器;

         5、栈中的数据可以共享,多个引用可以指向同一个地址;

         6、缺点是,栈的数据大小与生存期必须是确定的,缺乏灵活性。

 二、内存设置

        1、查看虚拟机内存情况  

long maxControl = Runtime.getRuntime().maxMemory();//获取虚拟机可以控制的最大内存数量
long currentUse = Runtime.getRuntime().totalMemory();//获取虚拟机当前已使用的内存数量

                默认情况下,java虚拟机的maxControl=66650112B=63.5625M;

                什么都不做的情况,在我的机子上测得的currentUse=5177344B=4.9375M;

          2、设置内存大小的命令

             -Xms971f671fe497569bdb0616a45a44dc0f set initial Java heap size :设置JVM初始化堆内存大小;此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。

             -Xmx971f671fe497569bdb0616a45a44dc0f set maximum Java heap size:设置JVM最大的堆内存大小;

            -Xmn971f671fe497569bdb0616a45a44dc0f:设置年轻代大小,整个堆大小=年轻代大小+ 年老代大小+ 持久代大小。

             -Xss971f671fe497569bdb0616a45a44dc0f set java thread stack size:设置JVM线程栈内存大小;
          3、具体操作
             (1)JVM内存设置:
              打开MyEclipse(Eclipse)  window-preferences-Java -Installed JREs -Edit -Default VM Arguments   
              在VM自变量中输入:-Xmx128m -Xms64m -Xmn32m -Xss16m

             (2)IDE内存设置:

               在MyEclipse根目录下的myeclipse.ini(或Eclipse根目录下的eclipse.ini)中修改-vmargs  下的配置:

              (3)Tomcat内存设置

                   打开Tomcat根目录下的bin文件夹,编辑catalina.bat

                  修改为:set JAVA_OPTS= -Xms256m -Xmx512m

 三、Java堆中的OutOfMemoryError错误分析

       当JVM启动时,使用了-Xms 参数设置的堆内存。当程序继续进行,创建更多对象,JVM开始扩大堆内存以容纳更多对象。JVM也会使用垃圾回收器来回收内存。当快达到-Xmx设置的最大堆内存时,如果没有更多的内存可被分配给新对象的话,JVM就会抛出java.lang.outofmemoryerror,程序就会宕掉。在抛出 OutOfMemoryError之前,JVM会尝试着用垃圾回收器来释放足够的空间,但是发现仍旧没有足够的空间时,就会抛出这个错误。为了解决这个问题,需要清楚程序对象的信息,例如,你创建了哪些对象,哪些对象占用了多少空间等等。可以使用profiler或者堆分析器来处理OutOfMemoryError错误。"java.lang.OutOfMemoryError: Java heap space”表示堆没有足够的空间了,不能继续扩大了。"java.lang.OutOfMemoryError: PermGen space”表示permanent generation已经装满了,你的程序不能再装载类或者再分配一个字符串了。

四、堆和垃圾回收

  我们知道对象创建在堆内存中,垃圾回收这样一个进程,它将已死对象清除出堆空间,并将这些内存再还给堆。为了给垃圾回收器使用,堆主要分成三个区域,分别叫作New Generation,Old Generation或叫Tenured Generation,以及Perm space。New Generation是用来存放新建的对象的空间,在对象新建的时候被使用。如果长时间还使用的话,它们会被垃圾回收器移动到Old Generation(或叫Tenured Generation)。Perm space是JVM存放Meta数据的地方,例如类,方法,字符串池和类级别的详细信息。

 五、总结:

  1、Java堆内存是操作系统分配给JVM的内存的一部分。

  2、当我们创建对象时,它们存储在Java堆内存中。

  3、为了便于垃圾回收,Java堆空间分成三个区域,分别叫作New Generation, Old Generation或叫作Tenured Generation,还有Perm Space。

  4、你可以通过用JVM的命令行选项 -Xms, -Xmx, -Xmn来调整Java堆空间的大小。

  5、可以用JConsole或者Runtime.maxMemory(),Runtime.totalMemory(),Runtime.freeMemory()来查看Java中堆内存的大小。

  6、可以使用命令“jmap”来获得heap dump,用“jhat”来分析heap dump。

  7、Java堆空间不同于栈空间,栈空间是用来储存调用栈和局部变量的。

  8、Java垃圾回收器是用来将死掉的对象(不再使用的对象)所占用的内存回收回来,再释放到Java堆空间中。

  9、当遇到java.lang.outOfMemoryError时,不必紧张,有时候仅仅增加堆空间就可以了,但如果经常出现的话,就要看看Java程序中是不是存在内存泄露了。

  10、使用Profiler和Heap dump分析工具来查看Java堆空间,可以查看给每个对象分配了多少内存。

栈存储详解

Java栈存储具有以下几个特点:

一、存在栈中的数据大小和生命周期必须是确定的。

      如基本类型的存储:int a = 1; 这种变量存的是字面值,a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字面值就消失了),出于追求速度的原因,就存在于栈中。

二、存在栈中的数据可以共享。

    (1)、基本类型数据存储:

   如:

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的值。

   (2)、包装类数据存储:

   如Integer, Double, String等将相应的基本数据类型包装起来的类。这些类数据全部存在于堆中,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。

   如:以String为例。

    String是一个特殊的包装类数据。即可以用String str = new String("abc");的形式来创建,也可以用String str = "abc";的形式来创建。前者是规范的类的创建过程,即在Java中,一切都是对象,而对象是类的实例,全部通过new()的形式来创建。Java 中的有些类,如DateFormat类,可以通过该类的getInstance()方法来返回一个新创建的类,似乎违反了此原则。其实不然。该类运用了单例模式来返回类的实例,只不过这个实例是在该类内部通过new()来创建的,而getInstance()向外部隐藏了此细节。

    那为什么在String str = "abc";中,并没有通过new()来创建实例,是不是违反了上述原则?其实没有。

    关于String str = "abc"的内部工作。Java内部将此语句转化为以下几个步骤:
  a、先定义一个名为str的对String类的对象引用变量:String str;
  b、在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着创建一个新的String类的对象O,并将O的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象O。如果已经有了值为"abc"的地址,则查找对象O,并返回O的地址。
    c、将str指向对象O的地址。
 值得注意的是,通常String类中字符串值都是直接存值的。但像String str = "abc";这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用(即:String str = "abc";既有栈存储,又有堆存储)。

  为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。

String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true

(只有在两个引用都指向了同一个对象时才返回真值。str1与str2是否都指向了同一个对象)

  结果说明,JVM创建了两个引用str1和str2,但只创建了一个对象,而且两个引用都指向了这个对象。

String str1 = "abc";
String str2 = "abc";
str1 = "bcd";
System.out.println(str1 + "," + str2); //bcd, abc
System.out.println(str1==str2); //false

   这就是说,赋值的变化导致了类对象引用的变化,str1指向了另外一个新对象,而str2仍旧指向原来的对象。上例中,当我们将str1的值改为"bcd"时,JVM发现在栈中没有存放该值的地址,便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。

  事实上,String类被设计成为不可改变(immutable)的类。如果你要改变其值,可以,但JVM在运行时根据新值悄悄创建了一个新对象(没法在原来内存的基础上改变其值),然后将这个对象的地址返回给原来类的引用。这个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的环境中,会带有一定的不良影响。

String str1 = "abc";
String str2 = "abc";
str1 = "bcd";
String str3 = str1;
System.out.println(str3); //bcd
String str4 = "bcd";
System.out.println(str1 == str4); //true

   str3这个对象的引用直接指向str1所指向的对象(注意,str3并没有创建新对象)。当str1改完其值后,再创建一个String的引用str4,并指向因str1修改值而创建的新的对象。可以发现,这回str4也没有创建新的对象,从而再次实现栈中数据的共享。

String str1 = new String("abc");
String str2 = "abc";
System.out.println(str1==str2); //false

 创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。

String str1 = "abc";
 String str2 = new String("abc");
 System.out.println(str1==str2); //false

 创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。

  以上两段代码说明,只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。

 总结:

  (1)我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,我们创建了String类的对象str。担心陷阱!对象可能并没有被创建!唯一可以肯定的是,指向 String类的引用被创建了。至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,除非你通过new()方法来显要地创建一个新的对象。因此,更为准确的说法是,我们创建了一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为"abc"的String类。清醒地认识到这一点对排除程序中难以发现的bug是很有帮助的。

  (2)使用String str = "abc";的方式,可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。


  (3)由于String类的immutable性质(因为包装类的值不可修改),当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。

更多详解Java的堆内存与栈内存的存储机制相关文章请关注PHP中文网!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
如何将Maven或Gradle用于高级Java项目管理,构建自动化和依赖性解决方案?如何将Maven或Gradle用于高级Java项目管理,构建自动化和依赖性解决方案?Mar 17, 2025 pm 05:46 PM

本文讨论了使用Maven和Gradle进行Java项目管理,构建自动化和依赖性解决方案,以比较其方法和优化策略。

如何使用适当的版本控制和依赖项管理创建和使用自定义Java库(JAR文件)?如何使用适当的版本控制和依赖项管理创建和使用自定义Java库(JAR文件)?Mar 17, 2025 pm 05:45 PM

本文使用Maven和Gradle之类的工具讨论了具有适当的版本控制和依赖关系管理的自定义Java库(JAR文件)的创建和使用。

如何使用咖啡因或Guava Cache等库在Java应用程序中实现多层缓存?如何使用咖啡因或Guava Cache等库在Java应用程序中实现多层缓存?Mar 17, 2025 pm 05:44 PM

本文讨论了使用咖啡因和Guava缓存在Java中实施多层缓存以提高应用程序性能。它涵盖设置,集成和绩效优势,以及配置和驱逐政策管理最佳PRA

如何将JPA(Java持久性API)用于具有高级功能(例如缓存和懒惰加载)的对象相关映射?如何将JPA(Java持久性API)用于具有高级功能(例如缓存和懒惰加载)的对象相关映射?Mar 17, 2025 pm 05:43 PM

本文讨论了使用JPA进行对象相关映射,并具有高级功能,例如缓存和懒惰加载。它涵盖了设置,实体映射和优化性能的最佳实践,同时突出潜在的陷阱。[159个字符]

Java的类负载机制如何起作用,包括不同的类载荷及其委托模型?Java的类负载机制如何起作用,包括不同的类载荷及其委托模型?Mar 17, 2025 pm 05:35 PM

Java的类上载涉及使用带有引导,扩展程序和应用程序类负载器的分层系统加载,链接和初始化类。父代授权模型确保首先加载核心类别,从而影响自定义类LOA

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
4 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
4 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
4 周前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
1 个月前By尊渡假赌尊渡假赌尊渡假赌

热工具

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中