《深入理解Java虚拟机》内存分配策略

上节学习回顾

1、判断对象存活算法:引用计数法和可行性分析算法

2、垃圾收集算法:标记-清除算法、复制算法、标记-整理算法

3、垃圾收集器:

Serial:新生代收集器,采用复制算法,单线程。

ParNew:新生代收集器,采用复制算法,多线程。

Parallel Scavenge:新生代收集器,采用复制算法,多线程,注重吞吐量。

Serial Old:老年代收集器,采用标记-整理算法,单线程。

Parallel Old:老年代收集器,采用标记-整理算法,多线程,与Parallel Scavenge结合使用。

CMS:老年代收集器,采用标记-清除算法,相比以上收集器收集过去相对复杂,停止时间短。

G1:年轻代和老年代收集器,基本采用标记-整理算法,局部采用复制算法,收集过程跟CMS相当,但概念差异,是目前最新的收集器之一,使用范围暂时有待检验。

本节学习重点

本节主要通过测试代码来学习堆中对象的内存分配和回收策略,期间会通过打印GC和内存分配的日志来分析。首先,通过下图先熟悉一下对的内存分配区域图:

从上一节的学习和图中可以知道,目前新生代收集算法采用的都是复制算法,复制算法把新生代内存分为一个较大的Eden空间和两个较小的Survivor空间。HotSpot默认的分配比例是8:1:1.,例如新生代一共分配10MB,那么Eden占用8MB,而两个Survivor各占1MB。

注:以下所有测试例子的都是基于JDK8进行测试,默认使用的是Parallel Scavenge/Parallel Old收集器组合,为了跟书本保持一致,我下面会使用-XX:+UseSerialGC参数切换回Serial收集器,因为可能不同收集器且不用JDK版本可能会有不一样的情况。

  • 对象优先在Eden分配

大多数情况下,对象在新生代Eden去中分配,但Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。接下来,我们通过-XX:PrintGCDetails参数打印测试代码执行日志进行详细分析。

测试代码:

    private static final int _1MB = 1024*1024;

    /**
     * VM参数:-XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
     */
    public static void testAllocation(){
        byte[] allocation1,allocation2,allocation3,allocation4;

        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
        allocation4 = new byte[4 * _1MB];
    }

执行结果:

[GC (Allocation Failure) [DefNew: 6824K->268K(9216K), 0.0087400 secs] 6824K->6412K(19456K), 0.0087927 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]
Heap
def new generation   total 9216K, used 4446K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K,  51% used [0x00000000fec00000, 0x00000000ff014930, 0x00000000ff400000)
from space 1024K,  26% used [0x00000000ff500000, 0x00000000ff543018, 0x00000000ff600000)
to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
tenured generation   total 10240K, used 6144K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K,  60% used [0x00000000ff600000, 0x00000000ffc00030, 0x00000000ffc00200, 0x0000000100000000)
Metaspace       used 2876K, capacity 4486K, committed 4864K, reserved 1056768K
class space    used 275K, capacity 386K, committed 512K, reserved 1048576K

情况分析:

先对GC的日志说明一下,有“[GC……]”字眼的就是GC日志记录,而Heap以下的信息是JVM关闭前的堆使用情况信息描述。其中GC代表MinorGC,如果是Full GC的话会直接写着Full GC,[DefNew: 6824K->268K(9216K), 0.0087400 secs]中DefNew代表使用的是代表Serial收集器,6824K->268K(9216K)中6284K代表新生代收集前使用内存,268K代表收集后的使用内存,而9216代表新生代的总分配内存,0.0087400 secs为新生代收集时间。外层的6824K->6412K(19456K)代表整个Java堆的收集前使用内存->收集后使用内存(总分配内存),0.0087927 secs为整个GC的收集时间。整串GC日志都是类Json格式,比较容易看。

在上述测试例子中,我把JVM设置了不可扩展内存20MB,其中新生代10MB,老年代10MB,而新生代区域的分配比例是8:1:1,使用Serial/Serial Old组合收集器。从代码可以看出,allocation1、allocation2、allocation3一共需要6MB,而Eden一共有8MB,优先分配到Eden。但再分配allocation4的时候Eden空间不够,执行了一次Minor GC,在GC中,由于Survivor只有1MB,不够存放allocation1、allocation2、allocation3,所以直接迁移到老年代了,最后Eden空闲出来了就可以放allocation4了。最后通过Heap打印信息可以看到JVM内存分配的最后状态,“def new generation   total 9216K, used 4446K”为allocation4最后分配新生代所占用的4MB,而“tenured generation   total 10240K, used 6144K”则是老年代被allocation1、allocation2、allocation3所占用的6MB了。

  • 大对象直接进入老年代

所谓大对象是指需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组(例如上面例子的byte[]数组)。大对象对虚拟机内存分配来说是一个坏消息,经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来“安置”它们。虚拟机提供了一个-XX:PretenureSizeThreshold参数来设置大对象的界限,大于此值则直接分配在老年代去了。下面用代码测试一下吧。

测试代码:

    private static final int _1MB = 1024*1024;

    /**
     * VM参数:-XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
     * -XX:+PretenureSizeThreshold=3145728
     */
    public static void testAllocation(){
        byte[] allocation1;

        allocation1 = new byte[4 * _1MB];
    }

执行结果:

Heap
def new generation   total 9216K, used 844K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K,  10% used [0x00000000fec00000, 0x00000000fecd30a8, 0x00000000ff400000)
from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation   total 10240K, used 4096K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K,  40% used [0x00000000ff600000, 0x00000000ffa00010, 0x00000000ffa00200, 0x0000000100000000)
Metaspace       used 2875K, capacity 4486K, committed 4864K, reserved 1056768K
class space    used 275K, capacity 386K, committed 512K, reserved 1048576K

情况分析:

从以上打印JVM堆信息可以看出,allocation1分配的是4MB,大于PretenureSizeThreshold定义的3MB阀值,所以直接分配到老年代去了。

  • 长期存活的对象将进入老年代

由于Minor GC跟Full GC是差别的,Minor的主要对象还是新生代,对象在Minor后并不都会直接进入老年代,除非Survivor空间不够,否则此存活对象会经过多次Minor GC后还生存的话才进入老年代,而虚拟机默认的Minor GC次数为15次,可通过-XX:MaxTenuringThreshold进行次数设置。

测试代码:

private static final int _1MB = 1024*1024;

    /**
     * VM参数:-XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
     * -XX:MaxTenuringThreshold=15 OR 1
     */
    public static void testAllocation(){
        byte[] allocation1,allocation2,allocation3;

        allocation1 = new byte[1 * _1MB / 4];
        allocation2 = new byte[4 * _1MB];
        allocation3 = new byte[4 * _1MB];
        allocation3 = null;
        allocation3 = new byte[4 * _1MB];
    }

执行结果:

MaxTenuringThreshold = 15

[GC (Allocation Failure) [DefNew: 5031K->524K(9216K), 0.0132760 secs] 5031K->4620K(19456K), 0.0133385 secs] [Times: user=0.00 sys=0.02, real=0.02 secs]
[GC (Allocation Failure) [DefNew: 4620K->0K(9216K), 0.0007228 secs] 8716K->4614K(19456K), 0.0007564 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation   total 9216K, used 4178K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K,  51% used [0x00000000fec00000, 0x00000000ff014930, 0x00000000ff400000)
from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation   total 10240K, used 4614K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K,  45% used [0x00000000ff600000, 0x00000000ffa81ac0, 0x00000000ffa81c00, 0x0000000100000000)
Metaspace       used 2876K, capacity 4486K, committed 4864K, reserved 1056768K
class space    used 275K, capacity 386K, committed 512K, reserved 1048576K

MaxTenuringThreshold = 1

[GC (Allocation Failure) [DefNew: 5031K->524K(9216K), 0.0065023 secs] 5031K->4620K(19456K), 0.0065518 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]
[GC (Allocation Failure) [DefNew: 4620K->0K(9216K), 0.0013012 secs] 8716K->4614K(19456K), 0.0013644 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation   total 9216K, used 4178K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K,  51% used [0x00000000fec00000, 0x00000000ff014930, 0x00000000ff400000)
from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation   total 10240K, used 4614K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K,  45% used [0x00000000ff600000, 0x00000000ffa81ac0, 0x00000000ffa81c00, 0x0000000100000000)
Metaspace       used 2876K, capacity 4486K, committed 4864K, reserved 1056768K
class space    used 275K, capacity 386K, committed 512K, reserved 1048576K

情况分析:

从以上输出信息可以看到,无论是MaxTenuringThreshold=15还是1,执行结果都是一样的,至少跟书本描述的不一致,我怀疑是因为JDK版本不用而机制有所差异,我立刻切换到JDK6下执行同样的操作如下:

MaxTenuringThreshold = 15

[GC [DefNew: 4679K->375K(9216K), 0.0044310 secs] 4679K->4471K(19456K), 0.0044650 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 4635K->375K(9216K), 0.0086340 secs] 8731K->4471K(19456K), 0.0086660 secs] [Times: user=0.00 sys=0.02, real=0.01 secs]
Heap
def new generation   total 9216K, used 4635K [0xee330000, 0xeed30000, 0xeed30000)
eden space 8192K,  52% used [0xee330000, 0xee758fe0, 0xeeb30000)
from space 1024K,  36% used [0xeeb30000, 0xeeb8dc68, 0xeec30000)
to   space 1024K,   0% used [0xeec30000, 0xeec30000, 0xeed30000)
tenured generation   total 10240K, used 4096K [0xeed30000, 0xef730000, 0xef730000)
the space 10240K,  40% used [0xeed30000, 0xef130010, 0xef130200, 0xef730000)
compacting perm gen  total 16384K, used 1912K [0xef730000, 0xf0730000, 0xf3730000)
the space 16384K,  11% used [0xef730000, 0xef90e3b8, 0xef90e400, 0xf0730000)
No shared spaces configured.

MaxTenuringThreshold = 1

[GC [DefNew: 4679K->375K(9216K), 0.0037650 secs] 4679K->4471K(19456K), 0.0037960 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 4471K->0K(9216K), 0.0010150 secs] 8567K->4471K(19456K), 0.0010580 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation   total 9216K, used 4423K [0xee350000, 0xeed50000, 0xeed50000)
eden space 8192K,  54% used [0xee350000, 0xee7a1fa8, 0xeeb50000)
from space 1024K,   0% used [0xeeb50000, 0xeeb50000, 0xeec50000)
to   space 1024K,   0% used [0xeec50000, 0xeec50000, 0xeed50000)
tenured generation   total 10240K, used 4471K [0xeed50000, 0xef750000, 0xef750000)
the space 10240K,  43% used [0xeed50000, 0xef1adc50, 0xef1ade00, 0xef750000)
compacting perm gen  total 16384K, used 1912K [0xef750000, 0xf0750000, 0xf3750000)
the space 16384K,  11% used [0xef750000, 0xef92e3b8, 0xef92e400, 0xf0750000)
No shared spaces configured.

以上是JDK6环境下的输出,确实看到了差异,当MaxTenuringThreshold=15时,allocation1还停留在Survivor中,当MaxTenuringThreshold=1时,在Minor GC时就被迁移到老年代去了。看了JDK版本所做调整确实有所差异,具体的差异细节,可能后续还要进一步去了解。

  • 动态对象年龄判定

为了能更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象直接可以进入老年代,无须等到MaxTenuringThreshold中要求的年龄。下面通过测试代码对allocation2进行注释前和注释后的收集情况进行对比。

测试代码:

    private static final int _1MB = 1024*1024;

    /**
     * VM参数:-XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
     */
    public static void testAllocation(){
        byte[] allocation1,allocation2,allocation3,allocation4;

        allocation1 = new byte[1 * _1MB / 4];
        allocation2 = new byte[2 * _1MB / 4];//注释前后对比
        allocation3 = new byte[4 * _1MB];
        allocation4 = new byte[4 * _1MB];
        allocation3 = null;
        allocation4 = new byte[4 * _1MB];
    }

执行结果:

allocation2被注释的情况下

[GC (Allocation Failure) [DefNew: 5031K->524K(9216K), 0.0515256 secs] 5031K->4620K(19456K), 0.0515942 secs] [Times: user=0.00 sys=0.05, real=0.05 secs]
[GC (Allocation Failure) [DefNew: 4620K->0K(9216K), 0.0928683 secs] 8716K->8716K(19456K), 0.0929016 secs] [Times: user=0.00 sys=0.10, real=0.09 secs]
Heap
def new generation   total 9216K, used 4178K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K,  51% used [0x00000000fec00000, 0x00000000ff014930, 0x00000000ff400000)
from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation   total 10240K, used 8716K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K,  85% used [0x00000000ff600000, 0x00000000ffe83040, 0x00000000ffe83200, 0x0000000100000000)
Metaspace       used 2876K, capacity 4486K, committed 4864K, reserved 1056768K
class space    used 275K, capacity 386K, committed 512K, reserved 1048576K

allocation2没有注释的情况下

[GC (Allocation Failure) [DefNew: 5379K->1023K(9216K), 0.0082513 secs] 5379K->5132K(19456K), 0.0083072 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]
[GC (Allocation Failure) [DefNew: 5120K->0K(9216K), 0.0078267 secs] 9228K->9227K(19456K), 0.0078778 secs] [Times: user=0.00 sys=0.01, real=0.00 secs]
Heap
def new generation   total 9216K, used 4260K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K,  52% used [0x00000000fec00000, 0x00000000ff0290e0, 0x00000000ff400000)
from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation   total 10240K, used 9227K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K,  90% used [0x00000000ff600000, 0x00000000fff02fd8, 0x00000000fff03000, 0x0000000100000000)
Metaspace       used 2875K, capacity 4486K, committed 4864K, reserved 1056768K
class space    used 275K, capacity 386K, committed 512K, reserved 1048576K

情况分析:

从以上打印信息可以看到,代码执行并没有想上文描述的那样判断Survivor的空间是否被同龄对象占据一半就迁移到老年代,而是像上一个测试那样,并没有经过Survivor而都直接进入老年代了,这可能是JDK8对收集规则的一些改进,我继续尝试使用JDK6再测试一次:

allocation2被注释的情况下

[GC [DefNew: 4679K->375K(9216K), 0.0033230 secs] 4679K->4471K(19456K), 0.0033590 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[GC [DefNew: 4471K->375K(9216K), 0.0145510 secs] 8567K->8567K(19456K), 0.0145810 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
def new generation   total 9216K, used 4798K [0xee380000, 0xeed80000, 0xeed80000)
eden space 8192K,  54% used [0xee380000, 0xee7d1fa8, 0xeeb80000)
from space 1024K,  36% used [0xeeb80000, 0xeebddc40, 0xeec80000)
to   space 1024K,   0% used [0xeec80000, 0xeec80000, 0xeed80000)
tenured generation   total 10240K, used 8192K [0xeed80000, 0xef780000, 0xef780000)
the space 10240K,  80% used [0xeed80000, 0xef580020, 0xef580200, 0xef780000)
compacting perm gen  total 16384K, used 1912K [0xef780000, 0xf0780000, 0xf3780000)
the space 16384K,  11% used [0xef780000, 0xef95e3b8, 0xef95e400, 0xf0780000)
No shared spaces configured.

allocation2没有注释的情况下

[GC [DefNew: 5191K->887K(9216K), 0.0046370 secs] 5191K->4983K(19456K), 0.0048820 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC [DefNew: 4983K->0K(9216K), 0.0038680 secs] 9079K->9079K(19456K), 0.0039040 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation   total 9216K, used 4423K [0xee3c0000, 0xeedc0000, 0xeedc0000)
eden space 8192K,  54% used [0xee3c0000, 0xee811fa8, 0xeebc0000)
from space 1024K,   0% used [0xeebc0000, 0xeebc0000, 0xeecc0000)
to   space 1024K,   0% used [0xeecc0000, 0xeecc0000, 0xeedc0000)
tenured generation   total 10240K, used 9079K [0xeedc0000, 0xef7c0000, 0xef7c0000)
the space 10240K,  88% used [0xeedc0000, 0xef69dc70, 0xef69de00, 0xef7c0000)
compacting perm gen  total 16384K, used 1912K [0xef7c0000, 0xf07c0000, 0xf37c0000)
the space 16384K,  11% used [0xef7c0000, 0xef99e3e8, 0xef99e400, 0xf07c0000)
No shared spaces configured.

通过以上JDK6执行的信息可以看出,跟书本上描述的情况一致,当只有allocation1占据Survivor时,还不到一半空间,所以还停留在Survivor的空间。但当allocation2也存在时,执行第一次Minor GC的时候allocation1、allocation2应同时被迁移到Survivor,但allocation1、allocation2的总和已经达到了Survivor的一半,所以立刻被迁移到老年代了。情况跟测试MaxTenuringThreshold参数的时候一样,JDK8相对老版本JDK的处理方式还是有差异的。

  • 空间分配担保

在发生Minor GC前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象的空间,如果这个条件成立,那么Minor GC可以确保安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这个Minor GC是有风险的;如果小于或者HandlePromotionFailure设置不允许冒险,那么这时也要改为进行一次Full GC了。说白了就是虚拟机避免Full GC执行的次数而去做的检查机制。

取平均值进行比较其实仍然是一种动态概率的手段,也就是说,如果某次Minor GC存活后的对象突增,远远高于平均值的话,依然会导致担保失败(Handle Promotion Failure)。如果出现了HandlePromotionFailure失败,那就只好在失败后重新发起一次Full GC。虽然担保失败时绕的圈子是最大的,但大部分情况下都还是会将HandlePromotionFailure开关打开,避免Full GC过于频繁。

另外需要提醒,在JDK 6 Update 24之后,虚拟机已经不再使用HandlePromotionFailure参数了,规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Full GC。

  • 总结

本章节主要是通过测试用例对虚拟机收集规则的一下验证,通过测试例子也可知,不同JDK版本的规则可能会有所改变,但垃圾收集的本质是不变的。不同情况下虚拟机的收集器组合也是不一样的,主要掌握了各种收集算法和收集器的情况,就可根据实际情况去是使用,使用过程中也可以根据自己掌握的知识通过具体的调节参数进行对比和调优,一切的前提还是要知道垃圾收集是怎么一回事。

时间: 2024-10-16 14:34:42

《深入理解Java虚拟机》内存分配策略的相关文章

[问题贴]小白请教几个关于Java虚拟机内存分配策略的问题

最近在看周志明所著的<深入理解Java虚拟机>,有几个问题不太明白,希望大家帮我回答一下.先说一下我进行试验的环境: 操作系统:Mac OS X 10.11.6 EI Capitan Java环境: java version "1.8.0_92" Java(TM) SE Runtime Environment (build 1.8.0_92-b14) Java HotSpot(TM) 64-Bit Server VM (build 25.92-b14, mixed mode

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

2.1 内存分配策略 按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的. 静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求. 栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据区

JAVA虚拟机内存分配与回收机制

Java虚拟机(Java Virtual Machine) 简称JVM Java虚拟机是一个想象中的机器,在实际的计算机上通过软件模拟来实现.Java虚拟机有自己想象中的硬件,如处理器.堆栈.寄存器等,还具有相应的指令系统. Java把内存划分成两种:一种是栈内存,一种是堆内存. 在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配. 当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存

Java虚拟机内存分配详解

简介 了解Java虚拟机内存分布的好处 1.了解Java内存管理的细节,有助于程序员编写出性能更好的程序.比如,在新的线程创建时,JVM会为每个线程创建一个专属的栈 (stack),其栈是先进后出的数据结构,这种方式的特点,让程序员编程时,必须特别注意递归方法要尽量少使用,另外栈的大小也有一定的限制,如果过多 的递归,容易导致stack overflow. 2.了解Java内存管理的细节,一旦内存管理出现问题,有助于找到问题的根本原因所在. 3.了解Java内存管理的内幕,有助于优化JVM,从而

java虚拟机 内存分配

分为以下几个运行时数据区: 程序计数器,java虚拟机栈,本地方法栈,java堆,方法区 程序计数器: 线程私有,记录正在执行的虚拟机字节码指令地址,执行本地方法则为空,是唯一一个java虚拟机内存中没有PutOfMemoryError情况 java虚拟机栈: 线程私有,生命周期也与线程相同,用于支持虚拟机进行方法调用和方法执行.对执行引擎来讲,只有栈顶的栈帧是有效的,称为当前栈帧,关联的方法为当前方法,所有字节指令都只针对当前栈帧操作. 有两种异常情况: 当线程请求的栈帧大于虚拟机所允许的深度

深入理解Java虚拟机—内存管理机制

前面说过了类的加载机制,里面讲到了类的初始化中时用到了一部分内存管理的知识,这里让我们来看下Java虚拟机是如何管理内存的. 先让我们来看张图 有些文章中对线程隔离区还称之为线程独占区,其实是一个意思了.下面让我们来详细介绍下这五部分: 运行时数据区 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域都拥有自己的用途,并随着JVM进程的启动或者用户线程的启动和结束建立和销毁. 先让我们了解下进程和线程的区别: 进程是资源分配的最小单位,线程是程序执行的

Java虚拟机内存管理机制

自动内存管理机制 Java虚拟机(JVM)在执行Java程序过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有的区域则是依赖用户线程的启动和结束而建立和销毁.根据<Java虚拟机规范 第2版>规定,运行时数据区包括: 1.程序计数器 一块较小的内存空间,不在Ram上,而是直接划分在CPU上的,程序员无法直接操作它.当前线程所执行的字节码的行号指示器,通过改变这个计数器的值来选取下一条需要执行的字节码指令.每条

JVM性能调优 第七章 内存分配策略

理解了jvm内存分配策略不仅是程序性能调优的重要知识,还能够给养成自己一种良好的代码思路,一个程序的代码差异往往都是在这里体现出来的. 一.对象优先分配到Eden区域   一般来说,新创建的对象都会直接分配到Eden区域,如果Eden区域内存不够,JVM就会触发GC(垃圾回收),一般来说在JVM中有3种GC: Minor GC:指发生在新生代的垃圾收集动作,非常频繁,速度较快. Major GC:指发生在老年代的GC,出现Major GC,经常会伴随一次Minor GC,同时Minor GC也会

深入理解java虚拟机-----&gt;垃圾收集器与内存分配策略(下)

1.  前言 内存分配与回收策略 JVM堆的结构分析(新生代.老年代.永久代) 对象优先在Eden分配 大对象直接进入老年代 长期存活的对象将进入老年代 动态对象年龄判定 空间分配担保  2.  垃圾收集器与内存分配策略 Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决两个问题: 给对象分配内存; 回收分配给对象的内存. 对象的内存分配,往大方向上讲就是在堆上的分配,对象主要分配在新生代的Eden区上.少数也可能分配在老年代,取决于哪一种垃圾收集器组合,还有虚拟机中的相关内存的参