jvm基础-内存管理

来源:互联网 发布:淘宝会员等级表 编辑:程序博客网 时间:2024/06/08 10:08

好记性不如烂笔头。。。 工作这么多年还是要有点沉淀。。。

一 JVM内存范围


1、方法区(Method Area): 对于我们使用HotSpot虚拟机的程序员来说,方法区即平时我们所说的永久代(Perm Gen),它用于存储已被虚拟机加载的类信息,常量,以及静态变量等数据。虽然java虚拟机规范将方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做非堆(No-Heap),目的是为了与堆进行区分,HotSpot虚拟机设计团队只是为了让方法区与java堆统一使用分代GC机制,才将它命名为永久代,与新生代(New Gen)老年代(Tenured Gen)使用同一套内存管理代码。运行时常量池(Runtime Constant Pool)属于方法区的一部分。

2、虚拟机栈(VM stack):虚拟机栈描述的是Java方法执行的内存模型,每个方法在执行同时会创建一个栈帧(Stack Frame),用于存储局部变量表(存放编译可知的基本数据类型,对象引用和returnAddress),操作数栈,动态链接,方法出口等信息。每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中的入栈与出栈的过程。

3、本地方法栈(Native stack):与虚拟机栈类似,区别就是虚拟机栈执行的是java方法(字节码),而本地方法栈执行的是Native方法。

4、堆(Heap):堆是虚拟机所管理的内存最大的一块,这块内存唯一的作用就是存放对象实例。几乎所有的对象实例以及数组都要在堆上分配内存。同时,我们平时所说的垃圾回收也大部分(堆外还有方法区与直接内存)集中在堆上,这是垃圾收集器管理的最主要的区域,后面会对堆这块进行详细介绍。

5、程序计数器:可以当作当前线程的执行的字节码的行号指示器,分支、循环、跳转、异常处理等基础功能都需要依赖程序计数器来指定到下一条要执行的字节码指令。

6、直接内存(Direct Memory):也就是狭义上的堆外内存,jdk1.4后引入了NIO包,为了提高I/O性能,该包下面提供了一个使用Native方法直接分配堆外内存的类-DirectByteBuffer,数据存储在直接内存,堆中的DirectByteBuffer对象作为该块内存的引用。该部分内存的分配不受java堆大小的限制,只会受本机总内存以及处理器寻址空间的限制。JDK5.0之后,代码中能直接操作本地内存的方式有2种:使用未公开的Unsafe和NIO包下ByteBuffer。


二 堆内存分配


1、对象优先在Eden分配:大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够的空间进行分配时,虚拟机将会进行一次Minor GC

2、大对象直接进入老年代:这样做的目的是为了防止在Eden区以及两个survivor区发生大量的内存的复制。虚拟机提供了一个参数-XX:PretenureSizeThreshold参数用来设置这个大对象的值。

3、长期存活的对象将进入老年代虚拟机给每个对象分配了一个对象年龄(Age)计数器,如果对象在Eden区出生并且经过第一次Minor GC后仍然存活,并且能够被Survivor区容纳的话,将会被移到Survivor空间中,并且年龄设为1.对象在Survivor空间中每‘熬过’一次Minor GC,年龄就增加1岁。当他年龄增加到一定程度(默认15)时,就会晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过-XX:MaxTenuringThreshold设置。


Minor GC的过程。下面逐步说明GC过程:

a、新生代GC时,先将Eden区的存活的对象复制到To Survivor,年龄置为1,清空Eden区;
     b、然后回收From Survivor,首先将From Survivor中存活的对象年龄加1,然后判断年龄是否达到进入老年代的准入条件(默认15岁,可通过-XX:MaxTenuringThreshold设置),如果达到准入条件则进入老年代,否则进入To Survivor;清空From Survivor。
    c、交换From Survivor 与To Survivor,保证每次GC前To Survivor空间都是空的。

    d、下次GC时循环上述过程。

4、Survivor空间中相同年龄的所有对象大小的总和大于Survivor空间的一半时,年龄等于或者大于该年龄的对象就可以直接进入老年代中,而无需等到MaxTenuringThreshold这个阈值。

5、Full GC的发生条件:在发生Minor GC之前,虚拟机会去检查老年代中最大可用连续内存空间是否大于新生代所有对象总空间,如果这个条件成立,那么可以确保这次minor GC是安全的,如果不成立,则虚拟机回去查看HandlePromotionFailure设置的值是否允许担保失败。如果允许,则虚拟机会继续检查老年代中最大可用连续内存空间大小是否大于历次晋升到老年代对象的平均大小,如果大于,则会尝试进行一次Minor GC,即便这次Minor GC是有风险的。如果小于,或者HandlePromotionFailure设置不允许冒险,那这时要改为进行一次Full GC

注意:在JDK 6 update 24后,HandlePromotionFailure不再会影响虚拟机分配担保策略,规则变为只要老年代连续可用内存大小大于新生代所有对象总大小或者历次晋升老年代对象的平均大小就进行Minor GC,否则进行Full GC。

下面通过代码验证一下内存分配策略。

下面通过代码验证一下内存分配策略。

第一步:分配堆内各区域内存大小:堆大小20M,其中新生代10M,老年代10M;新生代中Eden与Survivor比例采用默认的8:1,即Eden区8M,两个Survivor区分别1M。此外,打印出GC信息。配置如下:

-verbose:gc  -XX:+PrintGCDetails  -Xms20M -Xmx20M  -Xmn10M  -XX:SurvivorRatio=8

第二步:执行如下代码

/**
 * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
 
 * @author huolie
 * @version $Id: TestGC.java, v 0.1 2016年11月10日 下午8:35:42 huolie Exp $
 */
public class TestGC {
    private final static int _1MB = 1024 1024;
    /**
     
     * @param args
     */
    public static void main(String[] args) {
        byte[] bt1, bt2, bt3, bt4;
        bt1 = new byte[2 * _1MB];
        bt2 = new byte[2 * _1MB];
        bt3 = new byte[2 * _1MB];
        bt4 = new byte[0 * _1MB];
    }
}

a、验证对象优先在Eden分配。

b、验证Eden区空间不足时进行Minor GC。修改上面代码,将bt4=new byte[0 * _1MB];修改为bt4=new byte[1 * _1MB]; 运行代码

c、验证大对象直接进入老年代。修改代码,将bt4=new byte[0 * _1MB];修改为bt4=new byte[4 * _1MB];同时启动参数中增加-XX:PretenureSizeThreshold=4M,运行代码

d、验证Full GC的发生。修改代码,增加bt5=new byte[7 * _1MB];保持-XX:PretenureSizeThreshold=4M。运行代码

三、关于启动参数的配置说明

1、-Xms, -XX:MinHeapFreeRatio,-Xmx,-XX:MaxHeapFreeRatio 这四个参数用来控制堆内存(新生代与老年代)整体大小,-Xms用于设置初始堆大小,空余堆内存小于MinHeapFreeRatio(默认40%)时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于MaxHeapFreeRatio(默认70%)时,JVM会减少堆直到 -Xms的最小限制。

2、-Xmn,-XX:NewRatio,-XX:SurvivorRatio 三个参数用来控制新生代(包括Eden与Survivor)、老年代的内存分配。其中-Xmn用来控制新生代大小,-XX:NewRatio用来控制新生代与老年代的比例,-XX:SurvivorRatio 用来控制新生代中Eden区与Survivor区的比例,默认为8:1:1。当-Xmn与-XX:NewRatio两个同时配置时,-Xmn优先级高。通过-Xmn与-XX:SurvivorRatio 或者-XX:NewRatio,-XX:SurvivorRatio 就可以对新生代内存以及老年代内存进行划分。

3、-XX:PermSize,-XX:MaxPermSize 用于设置永久代(Prem Gen)的大小。








0 0
原创粉丝点击