精华内容
下载资源
问答
  • Java堆内存

    万次阅读 多人点赞 2016-02-29 12:05:10
    Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。   在 Java 中,堆被划分成两个不同的...  这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。

    欢迎支持笔者新作:《深入理解Kafka:核心设计与实践原理》和《RabbitMQ实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客。


    欢迎跳转到本文的原文链接:https://honeypps.com/java/java-heap-memory/

      Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。
      在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。
      这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。
      堆的内存模型大致为:
    这里写图片描述

      新生代:Young Generation,主要用来存放新生的对象。
      老年代:Old Generation或者称作Tenured Generation,主要存放应用程序声明周期长的内存对象。
    >永久代:(方法区,不属于java堆,另一个别名为“非堆Non-Heap”但是一般查看PrintGCDetails都会带上PermGen区)是指内存的永久保存区域,主要存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域. 它和和存放Instance的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的应用会加载很多Class的话,就很可能出现PermGen space错误。

      堆大小 = 新生代 + 老年代。其中,堆的大小可以通过参数 –Xms、-Xmx 来指定。
      默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分。
      默认的,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
      JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。
      因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。


    ##回收方法区(附加补充)
      很多人认为方法区(或者HotSpot虚拟机中的永久代[PermGen])是没有垃圾收集的,java虚拟机规范中确实说过可以不要求虚拟机在方法区实现垃圾收集,而且在方法去中进行垃圾收集的“性价比”一般比较低:在堆中,尤其是在新生代中,常规应用进行一次垃圾收集一般可以回收70%-95%的空间,而永久代的垃圾收集效率远低于此。
      永久代的垃圾收集主要回收两部分内容:废弃的常量和无用的类。
      废弃的常量:回收废弃常量与回收java堆中的对象非常类似。以常量池字面量的回收为例,加入一个字符串“abc"已经进入了常量池中,但是当前系统没有任何一个String对象是叫做"abc"的,换句话说,就是有任何String对象应用常量池中的"abc"常量,也没有其他地方引用了这个字面量,如果这时发生内存回收,而且必要的话,这个“abc”常量就会被系统清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。(注:jdk1.7及其之后的版本已经将字符串常量池从永久代中移出)
      无用的类:类需要同时满足下面3个条件才能算是“无用的类”:

    • 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
    • 加载该类的ClassLoader已经被回收
    • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

      虚拟机可以对满足上述3个条件的无用类进行回收,这里说的仅仅是”可以“,而并不和对象一样,不使用了就必然会回收。是否对类进行回收,HotSpot虚拟机提供了-Xnoclassgc(关闭CLASS的垃圾回收功能,就是虚拟机加载的类,即便是不使用,没有实例也不会回收。)参数进行控制。
    >在大量使用反射、动态代理、CGlib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。


    ##GC

      Java 中的堆也是 GC 收集垃圾的主要区域。GC 分为两种:Minor GC、Full GC ( 或称为 Major GC )。
      Minor GC 是发生在新生代中的垃圾收集动作,所采用的是复制算法。
      新生代几乎是所有 Java 对象出生的地方,即 Java 对象申请的内存以及存放都是在这个地方。Java 中的大部分对象通常不需长久存活,具有朝生夕灭的性质。
      当一个对象被判定为 “死亡” 的时候,GC 就有责任来回收掉这部分对象的内存空间。新生代是 GC 收集垃圾的频繁区域。
      当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,在经过一次 Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳( 上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域 ( 即 to 区域 ) 中,然后清理所使用过的 Eden 以及 Survivor 区域 ( 即 from 区域 ),并且将这些对象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,可以通过参数 -XX:MaxTenuringThreshold 来设定 ),这些对象就会成为老年代。
      但这也不是一定的,对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代。虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配。这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存复制(新生代采用复制算法收集内存)。
      为了能够更好的适应不同的程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。
      Full GC 是发生在老年代的垃圾收集动作,所采用的是“标记-清除”或者“标记-整理”算法。
      现实的生活中,老年代的人通常会比新生代的人 “早死”。堆内存中的老年代(Old)不同于这个,老年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 “死掉” 了的。因此,Full GC 发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长。
      在发生MinorGC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么MinorGC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试这进行一次MinorGC,尽管这次MinorGC是有风险的;如果小于,或者HandlePromptionFailure设置不允许冒险,那这是也要改为进行一次FullGC.
      另外,标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作。

    MinorGC, MajorGC以及FullGC

    1. Full GC == Major GC指的是对老年代/永久代的stop the world的GC
    2. Full GC的次数 = 老年代GC时 stop the world的次数
    3. Full GC的时间 = 老年代GC时 stop the world的总时间
    4. CMS 不等于Full GC,我们可以看到CMS分为多个阶段,只有stop the world的阶段被计算到了Full GC的次数和时间,而和业务线程并发的GC的次数和时间则不被认为是Full GC
    5. Full GC本身不会先进行Minor GC,我们可以配置,让Full GC之前先进行一次Minor GC,因为老年代很多对象都会引用到新生代的对象,先进行一次Minor GC可以提高老年代GC的速度。比如老年代使用CMS时,设置CMSScavengeBeforeRemark优化,让CMS remark之前先进行一次Minor GC。

    ##GC日志
    首先看一下如下代码:

    package jvm;
    
    public class PrintGCDetails
    {
        public static void main(String[] args)
        {
            Object obj = new Object();
            System.gc();
            System.out.println();
            obj = new Object();
            obj = new Object();
            System.gc();
            System.out.println();
        }
    }
    

      设置JVM参数为-XX:+PrintGCDetails,执行结果如下:

    [GC [PSYoungGen: 1019K->568K(28672K)] 1019K->568K(92672K), 0.0529244 secs] [Times: user=0.00 sys=0.00, real=0.06 secs] 
    {博主自定义注解:[GC [新生代: MinorGC前新生代内存使用->MinorGC后新生代内存使用(新生代总的内存大小)] MinorGC前JVM堆内存使用的大小->MinorGC后JVM堆内存使用的大小(堆的可用内存大小), MinorGC总耗时] [Times: 用户耗时=0.00 系统耗时=0.00, 实际耗时=0.06 secs] }
    [Full GC [PSYoungGen: 568K->0K(28672K)] [ParOldGen: 0K->478K(64000K)] 568K->478K(92672K) [PSPermGen: 2484K->2483K(21504K)], 0.0178331 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] 
    {博主自定义注解:[Full GC [PSYoungGen: 568K->0K(28672K)] [老年代: FullGC前老年代内存使用->FullGC后老年代内存使用(老年代总的内存大小)] FullGC前JVM堆内存使用的大小->FullGC后JVM堆内存使用的大小(堆的可用内存大小) [永久代: 2484K->2483K(21504K)], 0.0178331 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]}
    
    [GC [PSYoungGen: 501K->64K(28672K)] 980K->542K(92672K), 0.0005080 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [Full GC [PSYoungGen: 64K->0K(28672K)] [ParOldGen: 478K->479K(64000K)] 542K->479K(92672K) [PSPermGen: 2483K->2483K(21504K)], 0.0133836 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
    
    Heap
     PSYoungGen      total 28672K, used 1505K [0x00000000e0a00000, 0x00000000e2980000, 0x0000000100000000)
      eden space 25088K, 6% used [0x00000000e0a00000,0x00000000e0b78690,0x00000000e2280000)
      from space 3584K, 0% used [0x00000000e2600000,0x00000000e2600000,0x00000000e2980000)
      to   space 3584K, 0% used [0x00000000e2280000,0x00000000e2280000,0x00000000e2600000)
     ParOldGen       total 64000K, used 479K [0x00000000a1e00000, 0x00000000a5c80000, 0x00000000e0a00000)
      object space 64000K, 0% used [0x00000000a1e00000,0x00000000a1e77d18,0x00000000a5c80000)
     PSPermGen       total 21504K, used 2492K [0x000000009cc00000, 0x000000009e100000, 0x00000000a1e00000)
      object space 21504K, 11% used [0x000000009cc00000,0x000000009ce6f2d0,0x000000009e100000)
    

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

      再看一个例子:

    package jvm;
    public class PrintGCDetails2
    {
        /**
         * -Xms60m -Xmx60m -Xmn20m -XX:NewRatio=2 ( 若 Xms = Xmx, 并且设定了 Xmn,
         * 那么该项配置就不需要配置了 ) -XX:SurvivorRatio=8 -XX:PermSize=30m -XX:MaxPermSize=30m
         * -XX:+PrintGCDetails
         */
        public static void main(String[] args)
        {
            new PrintGCDetails2().doTest();
        }
    
        public void doTest()
        {
            Integer M = new Integer(1024 * 1024 * 1); // 单位, 兆(M)
            byte[] bytes = new byte[1 * M]; // 申请 1M 大小的内存空间
            bytes = null; // 断开引用链
            System.gc(); // 通知 GC 收集垃圾
            System.out.println();
            bytes = new byte[1 * M]; // 重新申请 1M 大小的内存空间
            bytes = new byte[1 * M]; // 再次申请 1M 大小的内存空间
            System.gc();
            System.out.println();
        }
    }
    

      运行结果:

    [GC [PSYoungGen: 2007K->568K(18432K)] 2007K->568K(59392K), 0.0059377 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
    [Full GC [PSYoungGen: 568K->0K(18432K)] [ParOldGen: 0K->479K(40960K)] 568K->479K(59392K) [PSPermGen: 2484K->2483K(30720K)], 0.0223249 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] 
    
    [GC [PSYoungGen: 3031K->1056K(18432K)] 3510K->1535K(59392K), 0.0140169 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
    [Full GC [PSYoungGen: 1056K->0K(18432K)] [ParOldGen: 479K->1503K(40960K)] 1535K->1503K(59392K) [PSPermGen: 2486K->2486K(30720K)], 0.0119497 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
    
    Heap
     PSYoungGen      total 18432K, used 163K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
      eden space 16384K, 1% used [0x00000000fec00000,0x00000000fec28ff0,0x00000000ffc00000)
      from space 2048K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x0000000100000000)
      to   space 2048K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x00000000ffe00000)
     ParOldGen       total 40960K, used 1503K [0x00000000fc400000, 0x00000000fec00000, 0x00000000fec00000)
      object space 40960K, 3% used [0x00000000fc400000,0x00000000fc577e10,0x00000000fec00000)
     PSPermGen       total 30720K, used 2493K [0x00000000fa600000, 0x00000000fc400000, 0x00000000fc400000)
      object space 30720K, 8% used [0x00000000fa600000,0x00000000fa86f4f0,0x00000000fc400000)
    

      从打印结果可以看出,堆中新生代的内存空间为 18432K ( 约 18M ),eden 的内存空间为 16384K ( 约 16M),from / to survivor 的内存空间为 2048K ( 约 2M)。
      这里所配置的 Xmn 为 20M,也就是指定了新生代的内存空间为 20M,可是从打印的堆信息来看,新生代怎么就只有 18M 呢? 另外的 2M 哪里去了?
      别急,是这样的。新生代 = eden + from + to = 16 + 2 + 2 = 20M,可见新生代的内存空间确实是按 Xmn 参数分配得到的。
      而且这里指定了 SurvivorRatio = 8,因此,eden = 8/10 的新生代空间 = 8/10 * 20 = 16M。from = to = 1/10 的新生代空间 = 1/10 * 20 = 2M。
      堆信息中新生代的 total 18432K 是这样来的: eden + 1 个 survivor = 16384K + 2048K = 18432K,即约为 18M。
      因为 jvm 每次只是用新生代中的 eden 和 一个 survivor,因此新生代实际的可用内存空间大小为所指定的 90%。
      因此可以知道,这里新生代的内存空间指的是新生代可用的总的内存空间,而不是指整个新生代的空间大小。
      另外,可以看出老年代的内存空间为 40960K ( 约 40M ),堆大小 = 新生代 + 老年代。因此在这里,老年代 = 堆大小 - 新生代 = 60 - 20 = 40M。
      最后,这里还指定了 PermSize = 30m,PermGen 即永久代 ( 方法区 ),它还有一个名字,叫非堆,主要用来存储由 jvm 加载的类文件信息、常量、静态变量等。


    ##附:JVM常用参数
    -XX:+<option> 启用选项
    -XX:-<option>不启用选项
    -XX:<option>=<number>
    -XX:<option>=<string>

    堆设置
    -Xms :初始堆大小
    -Xmx :最大堆大小
    -Xmn:新生代大小。通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor,即 90%
    -XX:NewSize=n :设置年轻代大小
    -XX:NewRatio=n: 设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
    -XX:SurvivorRatio=n :年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
    -XX:PermSize=n 永久代(方法区)的初始大小
    -XX:MaxPermSize=n :设置永久代大小
    -Xss 设定栈容量;对于HotSpot来说,虽然-Xoss参数(设置本地方法栈大小)存在,但实际上是无效的,因为在HotSpot中并不区分虚拟机和本地方法栈。
    -XX:PretenureSizeThreshold (该设置只对Serial和ParNew收集器生效) 可以设置进入老生代的大小限制
    -XX:MaxTenuringThreshold=1(默认15)垃圾最大年龄 如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代. 对于年老代比较多的应用,可以提高效率.如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活 时间,增加在年轻代即被回收的概率
    该参数只有在串行GC时才有效.
    收集器设置
    -XX:+UseSerialGC :设置串行收集器
    -XX:+UseParallelGC :设置并行收集器
    -XX:+UseParallelOldGC :设置并行年老代收集器
    -XX:+UseConcMarkSweepGC :设置并发收集器
    垃圾回收统计信息
    -XX:+PrintHeapAtGC GC的heap详情
    -XX:+PrintGCDetails GC详情
    -XX:+PrintGCTimeStamps 打印GC时间信息
    -XX:+PrintTenuringDistribution 打印年龄信息等
    -XX:+HandlePromotionFailure 老年代分配担保(true or false)
    -Xloggc:gc.log 指定日志的位置
    并行收集器设置
    -XX:ParallelGCThreads=n :设置并行收集器收集时使用的CPU数。并行收集线程数。
    -XX:MaxGCPauseMillis=n :设置并行收集最大暂停时间
    -XX:GCTimeRatio=n :设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
    并发收集器设置
    -XX:+CMSIncrementalMode :设置为增量模式。适用于单CPU情况。
    -XX:ParallelGCThreads=n :设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
    其他
    -XX:PermSize=10M和-XX:MaxPermSize=10M限制方法区大小。
    -XX:MaxDirectMemorySize=10M指定DirectMemory(直接内存)容量,如果不指定,则默认与JAVA堆最大值(-Xmx指定)一样。
    -XX:+HeapDumpOnOutOfMemoryError 可以让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照(.hprof文件)以便时候进行分析(比如Eclipse Memory Analysis)。


    参考资料:

    1. 《java 虚拟机–新生代与老年代GC》
    2. 《Java 堆内存》
    3. 《深入理解Java虚拟机》周志明著

    欢迎跳转到本文的原文链接:https://honeypps.com/java/java-heap-memory/

    欢迎支持笔者新作:《深入理解Kafka:核心设计与实践原理》和《RabbitMQ实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客。


    展开全文
  • java堆内存详解操作

    万次阅读 2020-09-02 09:57:21
    java堆内存划分为新生代,老年代,永久代,在jdk1.8中永久代被元空间取代。新生代又分为三个空间分别为eden,s0,s1区。 新生代:使用复制清除算法,新生代每次gc都会回收大部分对象,新生代里面分成一份较大的eden...

    堆的内存划分

    java堆内存划分为新生代,老年代,永久代,在jdk1.8中永久代被元空间取代。新生代又分为三个空间分别为eden,s0,s1区。

    1. 新生代:使用复制清除算法,新生代每次gc都会回收大部分对象,新生代里面分成一份较大的eden空间和两份较小Survivor空间,每次只使用eden和其中一块survivor空间,每次垃圾回收的时候把存活对象复制到未使用的survivor空间中,然后清空eden空间和刚刚使用的survivor空间。eden空间和survivor空间比例为8:1:1,内存不足时发生Minor GC.
    2. 老年代(Old Memory):采用标记整理算法,老年代每次gc都只会回收少量对象。利用可达性遍历内存,把存活对象和垃圾对象进行标记,然后把存活对象全部堆在一起,清空边界以外内存。
    3. 永久代(Perm):用来存储类的元数据,也就是方法区。在jdk1.8中Perm被替换成元空间(MetaSpace),元空间存放在本地内存中。

    GC垃圾回收

    java垃圾回收机制主要是回收堆和方法区的内存,这块区域的数据是被线程所共享的,而pc寄存器,java虚拟机栈,本地方法栈都是线程独有的,随着线程而产生,随着线程销毁被灭亡。

    1. 可达性分析:通过GC Roots对象作为起点进行搜索,如果在GC Roots和对象之间没有可达路径,则称为对象是不可达的,不可达对象不一定会成为可回收对象。进入DEAD状态的线程还可以恢复,GC不会回收它的内存。把一些对象当做Root对象,JVM认为是不可回收的,并且Root对象引用的对象也是不可回收的。
    2. 什么是root对象:虚拟机栈中引用的对象,方法区中静态属性引用的对象,方法区中常量引用的对象,本地方法栈中Native方法引用的对象。
    3. 对象被回收要经历的两个阶段:
              第一阶段可达性分析,分析该对象是否可达。
              第二阶段当对象没有重写finalize()方法或者finalize()方法已经被调用过,虚拟机认为该对象不可以被救活,因此回收该对象。

    虚拟机频繁full GC

    full gc清理整个堆空间,包括年轻代和元空间

    • 首先我们用命令查看触发gc的原因是什么jstat -gccause 进程编号
    • 查看代码是否有System.gc()
    • 内存检查heap inspection ,执行jmap  命令
    • 如果是gc locker, 可能是程序依赖的JNI库的原因

    垃圾回收算法

    1. Mark-Sweep(标记-清除算法):标记清除算法分为两个阶段,标记阶段和清除阶段。标记阶段是标记出所有需要回收的对象,回收阶段是回收所有被标记的对象的空间。容易产生内存碎片。
    2. Copying(复制清除算法):将可用的内存划分为大小相等的两块,每次只使用其中一块,当前进行垃圾回收的时候,把存活的对象全部复制到另一块中,然后把已使用的内存空间一次清空掉。不容易产生内存碎片,可用内存空间少,存活对象多的话,效率比较低。
    3. Mark-Compact(标记-整理算法):先标记存活对象,然后把存活对象向一边移动,然后清理掉边界以外的内存。不容易产生碎片,内存利用率高,存活对象多并且分散的时候,移动次数多,效率低。
    4. 分代收集算法:新生代每次垃圾回收都要回收大部分对象,所有新生代采用复制算法,新生代里面分成一份较大的eden空间和两份较小的survivor空间,每次只使用Eden和其中一块Survivor空间,然后垃圾回收的时候,把存活对象放到未使用的survivor空间,然后清空eden空间和刚使用的survivor空间。

    垃圾回收类型

    1. Minor GC:从年轻代回收内存。
    2. Major GC: 清理整个老年代,当eden区内存不足时触发。
    3. Full GC: 清理整个堆空间,包括年轻代和老年代,当老年代内存不足时触发。

    jvm优化

    1. 一般来说当Survior区不够大或者占用量达到50%,就会把一些对象放到老年区。通过设置合理的eden区,survior区及使用率,可以将年轻对象保存在年轻代,从而避免full GC, 使用-Xmn设置年轻代的大小。
    2. 对于占用内存比较大的对象,一般会选择在老年代分配内存。如果在年轻代给对象分配内存,内存不够了,就要在eden区移动大量对象到老年代,然后这些移动的对象可能很快消亡,英雌导致full GC。XX:PetenureSizeThreshold=1000000B,标明对象大小超过1M,将在老年代分配内存。
    3. 一般情况下年轻对象存放在eden区,当一次gc后,对象还存活的话,放在survior区。此后每次gc年龄加一,当对象的年龄到达指定的阈值,就会被存放到老年区中,这个阈值可以通过-XX:MaxTenuringThreshold设置。
    4. 设置最小堆和最大堆,-Xmx和-Xms稳定的堆大小对垃圾回收是有利的。设置-Xmx和-Xms的值一样,可以减少gc次数。虽然减少了gc次数但是增加了gc的时间。                                                                                                                                    -XX:MinHeapFreeRatio参数用于设置堆空间的最小空闲比率,默认40,当堆空间空闲内存比率少于40,jvm会扩展堆。        -XX:MaxHeadFreeRatio参数用于设置堆空间的最大空闲比率,默认70,当堆空间空闲内存比率大于70,jvm会压缩堆。
    5. 、通过增加吞吐量提高系统性能,可以通过设置并行垃圾回收器。                                                                                              -XX:+UseParallelGC: 年轻代使用并行垃圾回收收集器。                                                                                                          -XX:+UseParallelOldGC: 设置老年代使用并行垃圾回收收集器。
    6. 使用大的内存分页。-XX:+LargePageSizeInBytes设置内存页的大小。
    7. 使用非占用的垃圾收集器。-XX:+UseConcMarkSweepGC老年代使用CMS收集器降低停顿。
    8. -XXSurvivorRatio=3,表示年轻代中的分配比率survivor:eden= 2:3
    9. jvm性能调优工具
        jps:输出jvm中运行的进程状态信息。jconsole
        jstack: 查看java进程内存线程的堆栈信息。
        jmap: 用于生成堆转存快照。
        jhat: 用于分析jmap生成的快照。Ecplise Memory Analyzer替代
        jstat: jvm统计监测工具。
        VisualVM: 故障处理工具。

     

    展开全文
  • java堆内存和栈内存的区别

    千次阅读 2015-11-03 14:05:48
    一段时间之前,我写了两篇文章文章分别是Java的垃圾回收和Java的值传递,从那之后我收到了很多要求解释Java堆内存和栈内存的邮件,并且要求解释他们的异同点。在Java中你会看到很多堆和栈内存的引用,JavaEE书和文章...

    一段时间之前,我写了两篇文章文章分别是Java的垃圾回收和Java的值传递,从那之后我收到了很多要求解释Java堆内存和栈内存的邮件,并且要求解释他们的异同点。

    在Java中你会看到很多堆和栈内存的引用,JavaEE书和文章很难在程序的角度完全解释什么是堆什么是栈。

    Java堆内存

    堆内存在Java运行时被使用来为对象和JRE类分配内存。不论什么时候我们创建了对象,它将一直会在堆内存上创建。垃圾回收运行在堆内存上来释放没有任何引用的对象所占的内存,任何在堆上被创建的对象都有一个全局的访问,并且可以在应用的任何位置被引用。

    Java栈内存

    Java的栈内存被用来线程的执行,他们包含生命周期很短的具体值的方法和在堆中使用这个方法对象的引用。栈内存是LIFO(后进先出)序列。当方法被调用的时候,堆内存中一个新的块被创建,保存了本地原始值和在方法中对其他对象的引用。这个方法结束之后,这个块对其他方法就变成可用的了。栈内存与堆内存相比是非常小的。
    我们用下边的例子理解堆内存和栈内存
    package com.journaldev.test;
     
    public class Memory {
     
        public static void main(String[] args) { // Line 1
            int i=1; // Line 2
            Object obj = new Object(); // Line 3
            Memory mem = new Memory(); // Line 4
            mem.foo(obj); // Line 5
        } // Line 9
     
        private void foo(Object param) { // Line 6
            String str = param.toString();  Line 7
            System.out.println(str);
        } // Line 8
     
    }

    下边的图片展示了上边程序堆和栈内存的引用,并且是怎么用来存储原始值、对象和变量的引用。
     
    我们来看看程序执行的过程:
    1、只要我们一运行这个程序,它会加载所有的运行类到堆内存中去,当在第一行找到main()方法的时候,Java创建可以被main()方法线程使用的栈内存。
    2、当在第一行,我们创建了本地原始变量,它在main()的栈中创建和保存。
    3、因为我们在第三行创建了对象,它在堆内存中被创建,在栈内存中保存了它的引用,同样的过程也发生在第四行我们创建Memory对象的时候。
    4、当在第五行我们调用foo()方法的时候,在堆的顶部创建了一个块来被foo()方法使用,因为Java是值传递的,在第六行一个新的对象的引用在foo()方法中的栈中被创建
    5、在第七行一个String被创建,它在堆空间中的 String池中运行,并且它的引用也在foo()方法的栈空间中被创建
    6、foo()方法在第八行结束,此时在堆中为foo()方法分配的内存块可以被释放
    7、在第九行,main()方法结束,栈为main()方法创建的内存空间可以被销毁。同样程序也在行结束,Java释放了所有的内存,结束了程序的运行

    堆内存和栈内存的区别

    基于上边的解释我们可以很简单的总结出堆和栈的区别:
    1、应用程序所有的部分都使用堆内存,然后栈内存通过一个线程运行来使用。
    2、不论对象什么时候创建,他都会存储在堆内存中,栈内存包含它的引用。栈内存只包含本地原始变量和堆中对象变量的引用。
    3、存储在堆中的对象是全局可以被访问的,然而栈内存不能被其他线程所访问。
    4、栈中的内存管理使用LIFO的方式完成,而堆内存的管理要更复杂了,因为它是全局被访问的。堆内存被分为,年轻一代,老一代等等,更多的细节请看, 这篇文章
    5、栈内存是生命周期很短的,然而堆内存的生命周期从程序的运行开始到运行结束。
    6、我们可以使用-Xms和-Xmx JVM选项定义开始的大小和堆内存的最大值,我们可以使用-Xss定义栈的大小
    7、当栈内存满的时候,Java抛出java.lang.StackOverFlowError异常而堆内存满的时候抛出java.lang.OutOfMemoryError: Java Heap Space错误
    8、和堆内存比,栈内存要小的多,因为明确使用了内存分配规则(LIFO),和堆内存相比栈内存非常快。
     

    原文地址:http://www.journaldev.com/4098/java-heap-memory-vs-stack-memory-difference

    关注我,获取400个的赚钱金点子,轻松开启程序员的副业生涯

     

    展开全文
  • java堆内存与栈内存

    千次阅读 2017-10-03 17:11:33
    java堆内存与栈内存Java把内存分成两种,一种叫做栈内存,一种叫做堆内存在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。当在一段代码块中定义一个变量时,java就在栈中为这个变量分配...

    java堆内存与栈内存

    1.寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制.
    2. 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中。)
    3. 堆:存放所有new出来的对象。
    4. 静态域:存放静态成员(static定义的)
    5. 常量池:存放字符串常量和基本类型常量(public static final)。
    6. 非RAM存储:硬盘等永久存储空间

    栈和常量池中的对象可以共享,对于堆中的对象不可以共享。

    栈中的数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会消失。

    堆中的对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定 ,具有很大的灵活性。

    对于字符串:其对象的引用都是存储在栈中的,如果是 编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中 。

    对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。

    题目 String s = new String(“xyz”);产生几个对象?
    答案:一个或两个,如果常量池中原来没有”xyz”,就是两个。
    new String(“China”);
    对于通过new产生一个字符串(假设为”china”)时,会先去常量池中查找是否已经有了”china”对象。
    如果没有则在常量池中创建一个此字符串对象,然后堆中再创建一个常量池中此”china”对象的拷贝对象。

    对于成员变量和局部变量:成员变量就是方法外部,类的内部定义的变量;
    局部变量就是方法或语句块内部定义的变量。局部变量必须初始化。
    形式参数是局部变量,局部变量的数据存在于栈内存中 。栈内存中的局部变量随着方法的消失而消失。
    成员变量存储在堆中的对象里面 ,由垃圾回收器负责回收。


    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中,有六个不同的地方可以存储数据:

    1. 寄存器(register)。 这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部。但是寄存器的数量极其有限,所以寄存器由编译器根据需求进行分配。你不能直接控制,也不能在程序中感觉到寄存器存在的任何迹象。

    ——最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制.

    1. 堆栈(stack)。位于通用RAM中,但通过它的“堆栈指针”可以从处理器哪里获得支持。堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些 内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序时候,JAVA编译器必须知道存储在堆栈内所有数据的确切大小和生命周期,因为它必须生成 相应的代码,以便上下移动堆栈指针。这一约束限制了程序的灵活性,所以虽然某些JAVA数据存储在堆栈中——特别是对象引用,但是JAVA对象不存储其 中。

    ——存放基本类型的变量数据和对象,数组的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中)

    1. 堆(heap)。一种通用性的内存池(也存在于RAM中),用于存放所以的JAVA对象。堆不同于堆栈的好处是:编译器不需要知道要从堆里分配多少存储区 域,也不必知道存储的数据在堆里存活多长时间。因此,在堆里分配存储有很大的灵活性。当你需要创建一个对象的时候,只需要new写一行简单的代码,当执行 这行代码时,会自动在堆里进行存储分配。当然,为这种灵活性必须要付出相应的代码。用堆进行存储分配比用堆栈进行存储存储需要更多的时间。

    ——存放所有new出来的对象。

    1. 静态存储(static storage)。这里的“静态”是指“在固定的位置”。静态存储里存放程序运行时一直存在的数据。你可用关键字static来标识一个对象的特定元素是静态的,但JAVA对象本身从来不会存放在静态存储空间里。

    ——存放静态成员(static定义的)

    1. 常量存储(constant storage)。常量值通常直接存放在程序代码内部,这样做是安全的,因为它们永远不会被改变。有时,在嵌入式系统中,常量本身会和其他部分分割离开,所以在这种情况下,可以选择将其放在ROM中

    ——存放字符串常量和基本类型常量(public static final)

    1. 非RAM存储。如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。

    ——硬盘等永久存储空间 就速度来说,有如下关系:
    寄存器 >堆栈 > 堆 > 其它
    这里我们主要关心栈,堆和常量池,对于栈和常量池中的对象可以共享,对于堆中的对象不可以共享。
    栈中的数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会消失。堆中的对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定,具有很大的灵活性。

    对于字符串:其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。

     如以下代码: Java代码 
    
            String s1 = "china"; 
            String s2 = "china";
            String s3 = "china"; 
            String ss1 = new String("china"); 
            String ss2 = new String("china"); 
            String ss3 = new String("china");   
        这里解释一下,对于通过 new 产生一个字符串(假设为 ”china” )时,会先去常量池中查找是否已经有了 ”china” 对象,如果没有则在常量池中创建一个此字符串对象,然后堆中再创建一个常量池中此 ”china” 对象的拷贝对象。
    
    
        也就是有道面试题: String s = new String(“xyz”); 产生几个对象?
    

    一个或两个。如果常量池中原来没有 ”xyz”, 就是两个。如果原来的常量池中存在“xyz”时,就是一个。

        对于基础类型的变量和常量:变量和引用存储在栈中,常量存储在常量池中。
    
    
        如以下代码: Java代码 
    
    
        int i1 = 9; 
    
    
        int i2 = 9; 
    
    
        int i3 = 9;  
    
    
        public static final int INT1 = 9; 
    
    
        public static final int INT2 = 9; 
    
    
        public static final int INT3 = 9;   
    
    
        对于成员变量和局部变量:成员变量就是方法外部,类的内部定义的变量;
    
    
     局部变量就是方法或语句块内部定义的变量。局部变量必须初始化。 形式参数是局部变量,局部变量的数据存在于栈内存中。栈内存中的局部变量随着方法的消失而消失。 成员变量存储在堆中的对象里面,由垃圾回收器负责回收。
    
    
     如以下代码: Java代码  
    
    
     class BirthDate {     
    
    
       private int day;     
    
    
       private int month;     
    
    
       private int year;         
    
    
       public BirthDate(int d, int m, int y) {         
    
    
                   day = d;          
    
    
                   month = m;          
    
    
                    year = y;     
    
    
                   }     
    
    
     // 省略get,set方法……… 
    

    }

    public class Test{

    public static void main(String args[]){ 
    
    
      int date = 9;         
    
    
      Test test = new Test();                  
    
    
      test.change(date);          
    
    
      BirthDate d1= new BirthDate(7,7,1970);            
    
    
    }         
    

    public void change1(int i){

        i = 1234;     
    
    
    }   
    

    对于以上这段代码,date为局部变量,i,d,m,y都是形参为局部变量,day,month,year为成员变量。

    下面分析一下代码执行时候的变化:

    1. main方法开始执行:int date = 9; date局部变量,基础类型,引用和值都存在栈中。

    2. Test test = new Test(); test为对象引用,存在栈中,对象(new Test())存在堆中。

    3. test.change(date); i为局部变量,引用和值存在栈中。当方法change执行完成后,i就会从栈中消失。

    4. BirthDate d1= new BirthDate(7,7,1970); d1 为对象引用,存在栈中,对象(new BirthDate())存在堆中,其中d,m,y为局部变量存储在栈中,且它们的类型为基础类型,因此它们的数据也存储在栈中。 day,month,year为成员变量,它们存储在堆中(new BirthDate()里面)。当BirthDate构造方法执行完之后,d,m,y将从栈中消失。

      5.main方法执行完之后,date变量,test,d1引用将从栈中消失,new Test(),new BirthDate()将等待垃圾回收。

    Java堆、栈和常量池详解(二)

    1. 栈(stack)与堆(heap)都是Java用来在RAM中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。

    2. 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共 享,详见第3点。

      堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要 在运行时动态分配内存,存取速度较慢。

    3. Java中的数据类型有两种。

      一种是基本类型(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()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。

      举例如下: Java代码

      public class Test {

      public static void main(String[] args)

      {

      int a1=1;         
      
      
      int b1=1;         
      
      
      int c1=2;         
      
      
      int d1=a1+b1;         
      
      
      Integer a = 1;           
      
      
      Integer b = 2;           
      
      
      Integer c = 3;           
      
      
      Integer d = 3;           
      
      
      Integer e = 321;           
      
      
      Integer f = 321;
      
      
      Long g = 3L;  
      

      System.out.println(a1==b1); //true 结果1

      System.out.println(c1==d1); //true 结果2

      System.out.println(c==d); //true 结果3

      System.out.println(e==f); //false 结果4

      }

      }

      分析:

      结果1:a1==b1如上面所述,会在栈 中开辟存储空间存放数据。

      结果2:首先它会在栈 中创建一个变量为c1的引用,然后查找有没有字面值为2的地址,没找到,就开辟一个存放2这个字面值的地址,然后将c1指向2的地址,d1为两个字面值相加也为2, 由于在栈中已经有2这个字面值,便将d1直接指向2的地址。这样,就出现了c1与d1同时均指向3的情况。 在分析下面结果以前让我们先对Java自动拆箱和装箱做个了结:在自动装箱时,把int变成Integer的时候,是有规则的,当你的int的值在-128-IntegerCache.high(127) 时,返回的不是一个新new出来的Integer对象,而是一个已经缓存在堆 中的Integer对象,(我们可以这样理解,系统已经把-128到127之 间的Integer缓存到一个Integer数组中去了,如果你要把一个int变成一个Integer对象,首先去缓存中找,找到的话直接返回引用给你就 行了,不必再新new一个),如果不在-128-IntegerCache.high(127) 时会返回一个新new出来的Integer对象。

      结果3:由于3是在范围内所以是从缓存中取数据的,c和d指向同一个对象,结果为true;

      结果4:由于321不是在范围内所以不是从缓存中取数据的而是单独有new对象,e和f并没有指向同一个对象,结果为false;

      1. String是一个特殊的包装类数据。即可以用String str = new String(“abc”);的形式来创建,也可以用String str = “abc”;的形式来创建(作为对比,在JDK 5.0之前,你从未见过Integer i = 3;的表达式,因为类与字面值是不能通用的,除了String。而在JDK 5.0中,这种表达式是可以的!因为编译器在后台进行Integer i = new Integer(3)的转换)。前者是规范的类的创建过程,即在Java中,一切都是对象,而对象是类的实例,全部通过new()的形式来创建。Java 中的有些类,如DateFormat类,可以通过该类的getInstance()方法来返回一个新创建的类,似乎违反了此原则。其实不然。该类运用了单 例模式来返回类的实例,只不过这个实例是在该类内部通过new()来创建的,而getInstance()向外部隐藏了此细节。那为什么在String str = “abc”;中,并没有通过new()来创建实例,是不是违反了上述原则?其实没有。

      4(1)String str = “abc”创建对象的过程 1 首先在常量池中查找是否存在内容为”abc”字符串对象 2 如果不存在则在常量池中创建”abc”,并让str引用该对象 3 如果存在则直接让str引用该对象

    至 于”abc”是怎么保存,保存在哪?常量池属于类信息的一部分,而类信息反映到JVM内存模型中是对应存在于JVM内存模型的方法区,也就是说这个类信息 中的常量池概念是存在于在方法区中,而方法区是在JVM内存模型中的堆中由JVM来分配的,所以”abc”可以说存在于堆中(而有些资料,为了把方法区的 堆区别于JVM的堆,把方法区称为栈)。一般这种情况下,”abc”在编译时就被写入字节码中,所以class被加载时,JVM就为”abc”在常量池中 分配内存,所以和静态区差不多。

    4(2)String str = new String(“abc”)创建实例的过程 1 首先在堆中(不是常量池)创建一个指定的对象”abc”,并让str引用指向该对象 2 在字符串常量池中查看,是否存在内容为”abc”字符串对象 3 若存在,则将new出来的字符串对象与字符串常量池中的对象联系起来 4 若不存在,则在字符串常量池中创建一个内容为”abc”的字符串对象,并将堆中的对象与之联系起来 intern 方法可以返回该字符串在常量池中的对象的引用,可以通过下面代码简单的测试 Java代码

    class StringTest {

    public static void main(String[] args) {

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

    }

    }

    一个初始为空的字符串池,它由类 String 私有地维护。 当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用。 它遵循以下规则:对于任意两个字符串 s 和 t ,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true 。 所以String str1 = “abc”,str1引用的是常量池(方法区)的对象,而String str2 = new String(“abc”),str2引用的是堆中的对象,所以内存地址不一样,但是内容一样,所以==为false,而equals是true

    4(3)String str1 = “abc”; String str2 = “ab” + “c”; str1==str2是ture 是因为String str2 = “ab” + “c”会查找常量池中时候存在内容为”abc”字符串对象,如存在则直接让str2引用该对象,显然String str1 = “abc”的时候,上面说了,会在常量池中创建”abc”对象,所以str1引用该对象,str2也引用该对象,所以str1==str2

    4(4)String str1 = “abc”; String str2 = “ab”; String str3 = str2 + “c”; str1==str3是false 是因为String str3 = str2 + “c”涉及到变量(不全是常量)的相加,所以会生成新的对象,其内部实现是先new一个StringBuilder,然后 append(str2),append(“c”);然后让str3引用toString()返回的对象 如果想了解更多的细节,可以自己查看反编译的代码,查看反编译代码可以用javap,

    即 javap -c -verbose 要查看的类文件(.class不要)

    比如上面的代码的示例

    javac StringTest.java //编译

    javap -c -verbose StringTest //反编译

    4(5)String str1 = “abc”;

      String str2 = "abc";
    
    
     System.out.println(str1==str2);  //true 注意,
    
    
     我们这里并不用str1.equals(str2);的方式,因为这将比较两个字符串的值是否相等。==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回真值。而我们在这里要看的是,str1与str2是否都指向了同一个对象。 结果说明,JVM创建了两个引用str1和str2,但只创建了一个对象,而且两个引用都指向了这个对象。
    

    4(6)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在运行时根据新值悄悄创建了一个新对象,然后将这 个对象的地址返回给原来类的引用。这个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的环境中,会带有一定的不良影响。
    

    4(7)

     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也没有创建新的对象,从而再次实现栈中数据的共享。
    

    4(8)

    我们再接着看以下的代码。

    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类的值不可修改,所有的数据类型包装类都不能更改其内部的值。

    2. 结论与建议:

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

    (2)使用String str = “abc”;的方式,可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String(“abc”);的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。这个思想应该是 享元模式的思想,但JDK的内部在这里实现是否应用了这个模式,不得而知。

    (3)当比较包装类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==。

    (4)由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率

    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堆内存初始大小

    千次阅读 2019-06-28 10:08:19
    Java堆内存初始大小 大厂面试题: 1、JVM垃圾回收时候如何确定垃圾?是否知道什么是GC Roots 2、你说你做过JVM参数调优和参数配置,请问如何盘点查看JVM系统默认值 3、你平时工作中用过的JVM常用基本配置参数有...
  • Java堆内存Heap与非堆内存Non-Heap

    万次阅读 2011-03-10 23:21:00
    Java堆内存Heap与非堆内存Non-Heap
  • Matlab内存设置:Java 堆内存预设

    千次阅读 2018-10-23 15:02:39
    您可以调整 MATLAB® 分配给 Java® 对象的内存量。 注意 ... Java 堆内存】。 2、使用滑块或微调框选择 Java 堆大小值。 注意 增加 Java 堆大小会减少可用于在数组中存储数据的内存量。 3...
  • Java 堆内存和栈内存

    千次阅读 2016-03-23 19:59:12
    Java堆内存和栈内存的简单理解
  • java 堆内存 与栈内存

    万次阅读 2016-10-16 10:58:22
    Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过...
  • Java堆内存设置

    万次阅读 2018-07-12 22:41:24
    堆内存设置原理JVM堆内存分为2块:永久空间和堆空间。永久即持久代(Permanent Generation),主要存放的是Java类定义信息,与垃圾收集器要收集的Java对象关系不大。Heap = {Old + NEW = {Eden,from,to}},Old即...
  • 浅析Java堆内存和栈内存的区别

    万次阅读 2018-03-06 09:36:55
    参考 堆内存:https://baike.baidu.com/item/%E5%A0%86%E5%86%85%E5%AD%98/7270805?fr=aladdin ...Java把内存划分成两种:一种是栈内存,一种是堆内存。 一、栈内存 存放基本类型的变量,对象的引用和方...
  • Java 堆内存模型

    千次阅读 2015-07-23 21:49:37
    堆内存 Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。 在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden...
  • Java堆内存溢出

    千次阅读 2017-09-17 16:20:42
    2.2 Java堆内存的OOM异常是实际中常见的内存溢出情况,当出现Java堆内存溢出时,异常堆栈信息: java.lang.OutOfMemoryError: Java heap space 3.代码 设置虚拟机启动参数 -Xms与-Xmx参数设置为一样...
  • Java虚拟机(二)——Java堆内存划分

    千次阅读 2016-08-23 17:21:37
    对于大多数应用来说,Java堆(Java Heap)是Java 虚拟机所管理的内存中最大的一块。此内存区域的唯一目的就是存在对象实例,几乎所有的对象实例都在这里分配内存。这个区域也是Java 垃圾收集器管理的主要区域,因此...
  • Java堆内存的10个要点

    千次阅读 2012-03-04 13:59:19
    文中介绍了Java堆的学习教程以及Java堆内存(heap memory)的十个要点。 文章内容如下: 我刚开始学习Java编程时,可不知道什么是堆内存或堆空间(heap space),甚至根本不管对象创建时都放在哪里去了。正式了写...
  • Java 开发对JVM(Java虚拟机)的了解很有必要,网上看到,收集整理转载一下, 堆(Heap)和非堆(Non-heap)内存  ...”“在JVM中堆之外的内存称为非堆内存(Non-heap memory)”。可以看出JVM主要管理两种类型的
  • 摘要:Java堆容量不足可以对性能造成很大影响,这样无疑就给程序带来不可必要的麻烦,本文总结了影响Java堆容量不足的五大原因以及巧妙地去优化? 本文作者Pierre是一名有10多年经验的高级系统架构师,他的主要...
  • 了解堆栈的区别看这里:java堆和栈有什么区别 每一个jvm实例都会被分配一个被所有线程共享的堆内存空间,用来存放对象和数组,作为jvm的数据集中管理区,存取效率、空间释放就成为了重中之重,jvm通过多区架构来...
  • Java堆内存是线程共享的!面试官:你确定吗?

    千次阅读 多人点赞 2020-03-10 10:06:26
    Java作为一种面向对象的,跨平台语言,其对象、...在JVM的内存结构中,比较常见的两个区域就是堆内存和栈内存(如无特指,本文提到的栈均指的是虚拟机栈),关于堆和栈的区别,很多开发者也是如数家珍,有很多书籍,...
  • 通过对Java堆进行分析,发现数据量较大的实例类型为char[],其中最大的一个char[]实例大小为 127MB ,对其内容进行分析,发现与某接口的方法有关。 进一步分析发现,该接口在某一参数的情况下,就会产生这种大对象...
  • Java堆内存富余却疯狂FullGc问题排查

    千次阅读 2015-05-29 16:22:40
    但是观察报警Java实例的jvm监控,堆内存仅仅用了1G多一点,还富余近700M。很困惑,剩余这么多内存没被使用,那么说明老年代内存是充足的才对啊! 同事提醒我是不是申请了什么大对象,诚然,老年代只会有两种类型的...
  • 使用JDK8 Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。 在 Java 中,堆被划分成两个不同的...这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收
  • 深入JVM 原理(一)Java内存模型:http://blog.csdn.net/qq_34707744/article/details/79278169 深入JVM原理(二)Java对象访问模式: http://blog.csdn.net/qq_34707744/article/details/79279979 深入JVM...
  • 服务器是8G内存,JVM给了4G,内存dump文件大小为3.8G,我取到本机(32位Windows机,内存3G)上,用MAT打开查看直接报内存溢出,用Jprofiler看了整整一天还没打开成功。 请教用什么工具可以迅速打开查看啊
  • 拓展:Java堆内存详解 JVM堆内存分为2块:Heap Space 和 Permanent Space。 Heap  被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From ...
  • 作者 l Hollis本文经授权转载自Hollis(ID:hollischuang)Java作为一种面向对象的,跨平台语言,其对象、内存等一直是比较难的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 399,606
精华内容 159,842
关键字:

java堆内存

java 订阅