精华内容
参与话题
问答
  • 内存分配策略

    2020-12-31 22:23:14
    内存分配策略 优先分配到eden 大多数情况下,对象在新生代 Eden 区中分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC 。 Minor GC(新生代 GC):指发生在新生代的垃圾收集动作,因为 Java...

    内存分配策略

    • 优先分配到eden

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

    Minor GC(新生代 GC):指发生在新生代的垃圾收集动作,因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 发生的非常频繁,一般回收速度也比较快。

    Major GC(老年代 GC):指发生在老年代的 GC,出现了 Major GC ,经常会伴随至少一次的 Minor GC(不绝对)。Major GC 的速度一般会比 Minor GC 慢 10 倍以上。

    • 大对象直接分配到老年代

      所谓的大对象是指,需要大量连续内存空间的 Java 对象。最典型的大对象就是很长的字符串和数组,经常出现大对象会导致内存还有不少空间时就提前出发垃圾收集以获取足够的连续空间。

      虚拟机提供了一个 -XX:PretenureSizeThreshold 参数,令大于这个设置值的对象直接在老年代分配。这样做的目的是避免在 Eden 区及两个 Survivor 区之间发生大量的内存复制。

    • 长期存活对象分配到老年代

      即然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能够识别哪些对象应该放在新生代,哪些对象应该放在老年代。为了做到这点,虚拟机给每个对象定义了一个对象年龄计数器。如果对象在 Eden 出生并经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并且对象年龄设为 1 。对象在 Survivor 中每“熬过”一次 Minor GC,年龄就增加 1 岁,当它的年龄增加到一定程度时(默认为 15 岁),就将会被晋升到老年代中。对象进入老年代的阈值,可以通过参数 -XX:MaxTenuringThreshold 设置。

    • 动态对象年龄判断

      为了能更好地适应不同程序中的内存状况,虚拟机并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代。如果在 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到 MaxTenuringThreshold 中要求的年龄。

    • 空间分配担保

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

    逃逸分析与栈上分配

    逃逸分析:分析对象的作用域(受访问权限仅在在方法内) —》将未逃逸对方分配到栈中(进栈,出栈),不需要进行垃圾回收

    package MyDemo.work;
    
    public class StackAllocation {
        public StackAllocation obj;
    
        //方法返回StackAllocation对象,发生逃逸
        public StackAllocation getInstance() {
            return obj == null ? new StackAllocation() : obj;
        }
    
        //为成员属性赋值,发生逃逸
        public void setObj(){
            this.obj = new StackAllocation();
        }
    
        //对象的作用域仅在当前方法中有效,没有发生逃逸
        public void useStackAllocation(){
            StackAllocation s = new StackAllocation();
        }
    
        //引用成员变量的值,发生逃逸
        public void useStackAllocation2(){
            StackAllocation s = getInstance();
        }
    }
    

    避免在循环体中创建对象,即使该对象占用内存空间不大。
    尽量及时使对象符合垃圾回收标准。
    不要采用过深的继承层次。
    访问本地变量优于访问类中的变量。

    展开全文
  • 内存分配策略与回收策略 参考:《深入理解Java虚拟机》-jvm高级特性与最佳实现(周志明著) 前言 Java技术体系所倡导的内存管理最终可以贵大为自动化解决两个问题:给对象分配内存以及回收分配给对象的内存。 ...

    内存分配策略与回收策略

    参考:《深入理解Java虚拟机》-jvm高级特性与最佳实现(周志明著)

    前言

    Java技术体系所倡导的内存管理最终可以贵大为自动化解决两个问题:给对象分配内存以及回收分配给对象的内存。

    如下的内容是基于Client模式下默认的垃圾收集器组合

    一、内存分配的普遍规则

    1、对象优先在Eden空间分配,大多情况下,对象在新生代的Eden空间进行分配,当Eden空间没有足够的内存时,将触发一次Minor GC,可以通过设置-XX:+PrintGCDetails 这个收集器日志参数,高速虚拟机在发生垃圾回收的时候打印GC日志。

    注意:

    Minor GC 和 Full GC 有什么不一样吗?

    新生代GC(Minor GC):是指新生代的垃圾收集动作,因为Java对象大多朝生夕灭,所以Minor GC发生的非常频繁。一般回收速度也比较快。

    老年代GC(Major GC / Full GC):Major GC清理Tenured区,用于回收老年代,出现Major GC通常会出现至少一次Minor GC。

    Full GC是针对整个新生代、老生代、元空间(metaspace,java8以上版本取代perm gen)的全局范围的GC。Full GC不等于Major GC,也不等于Minor GC+Major GC,发生Full GC需要看使用了什么垃圾收集器组合,才能解释是什么样的垃圾回收。

    2、大对象直接进入老年代,最典型的大对象就算是指那种很长的字符串以及数组。虚拟机提供了-XX:PretenureSizeThreshold 参数,令大于这个设置值的对象直接在老年代分配。这样做的目的是避免在Eden空间以及两个Survivor区之间发生大量的 内存复制。

    需要注意的是,-XX:PretenureSizeThreshold 这个参数对Parallel Scavenge 收集器不起作用,如果遇到必须使用该设置的场合,可以考虑使用ParNew 加 CMS 收集器组合

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

    虚拟机给每个对象都定义了一个年龄计数器,如果对象在Eden出生,并经过第一次Minor GC 后存活,并且被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设置为1,对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加超过默认(15岁)的时候,会被晋升到老年代中,对象晋升老年代的年龄阈值,可以通过设置参数 -XX:MaxTenuringThreshold设置

    4、动态对象年龄判断

    为了更好地适应不同程序的内存情况,虚拟机并不是永远地要求对象的年龄必须达到MaxTenuringThreshold 才能晋升老年代。如果再Survivor空间中相同年龄的所有对象大小的总和超过了Survivor空间的一半,年龄大于等于该年龄的对象就可以直接进入到老年代。

    5、空间分配担保

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

     

     

    展开全文
  • 对象的内存分配策略测试环境jdk1.6 32位对象的内存分配,就是在堆上分配,对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在tlab上分配。少数情况下也可能会直接分配在老年代中,分配的...

    对象的内存分配策略

    测试环境jdk1.6 32位

    对象的内存分配,就是在堆上分配,对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在

    tlab上分配。少数情况下也可能会直接分配在老年代中,分配的规则不是百分百确定,其细节取决于当前使用的是那种垃圾收集器组合,还有虚拟机中于内存相关的参数配置,下面介绍的是几条普遍的内存分配规则。

    对象优先在Eden分配

    大多数的对象在新生代Eden区中分配内存空间,如果Eden空间不够,虚拟机将自动发起一次Minor GC 垃圾回收

    测试工具:eclipse

    1. 测试代码

    package cn.erong.test;
    
    public class Jtest {
    	private static final int _1M = 1024*1024;
    	public static void main(String[] args) {
    		byte[] allocation1,allocation2,allocation3,allocation4;
    		allocation1 = new byte[2*_1M];
    		allocation2 = new byte[2*_1M];
    		allocation3 = new byte[2*_1M];
    		allocation4 = new byte[4*_1M];
    	}
    }	
    

    2. 配置jvm参数

    -XX:+PrintGCTimeStamps --打印gc时间信息
    -XX:+PrintGCDetails --打印GC详细信息
    -verbose:gc --开启gc日志
    -Xloggc:d:/gc.log --设置gc日志的存放位置 
    -Xms20M --设置java堆最小为20M
    -Xmx20M --设置java堆最大为20M
    -Xmn10M --设置新生代内存大小10M
    -XX:SurvivorRatio=8 --设置新生代内存区域中Eden区域和Survivor区域的比例

    3. JVM参数加入到Run configurations->对应的application->Arguments->Vm arguments,然后run 运行


    GC日志如下:

    Java HotSpot(TM) Client VM (25.151-b12) for windows-x86 JRE (1.8.0_151-b12), built on Sep  5 2017 19:31:49 by "java_re" with MS VC++ 10.0 (VS2010)
    Memory: 4k page, physical 3567372k(1058760k free), swap 7133056k(3189624k free)
    CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:NewSize=10485760 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:-UseLargePagesIndividualAllocation 
    0.096: [GC (Allocation Failure) 0.096: [DefNew: 6963K->483K(9216K), 0.0058886 secs] 6963K->6627K(19456K), 0.0060831 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
    Heap
     def new generation   total 9216K, used 4743K [0x03c00000, 0x04600000, 0x04600000)
      eden space 8192K,  52% used [0x03c00000, 0x040290e8, 0x04400000)
      from space 1024K,  47% used [0x04500000, 0x04578d98, 0x04600000)
      to   space 1024K,   0% used [0x04400000, 0x04400000, 0x04500000)
     tenured generation   total 10240K, used 6144K [0x04600000, 0x05000000, 0x05000000)
       the space 10240K,  60% used [0x04600000, 0x04c00030, 0x04c00200, 0x05000000)
     Metaspace       used 84K, capacity 2242K, committed 2368K, reserved 4480K

    注意:

    1. 新生代GC(Minor GC):指的是发生在新生代中的垃圾收集动作,很频繁

    2. 老年代GC(Major GC/Full GC):指的发生在老年代中的GC,出现了Major GC,经常伴随至少一次Minor GC(但非绝对),速度上比上面的Minor GC要慢10倍以上

    3. Allocation Failure 表示引起GC的原因是因为年轻代中没有足够的内存空间存放对象而触发的,这里的GC表示Minor GC 

    日志解读:

    可以看出发生了一次Minor GC,发生前allocation1-allocation3是放在新生代中,从日志看出大概是占用了6M的空间,后面在创建allocation4由于内存空间不够(allocation4是4M,新生代中可用的为9M),这样空间不够会触发一次复制算法,另一个Survivor内存空间(用来存放存活对象的内存区域)为1M,这样无法放入,由于分担机制,将会把allocation1->allocation3放入到老年代中。最后的allocation4存放在Eden内存区域中。

    从后面的内存区域的占有率可以验证这点

     def new generation   total 9216K, used 4743K [0x03c00000, 0x04600000, 0x04600000) ---新生代
      eden space 8192K,  52% used [0x03c00000, 0x040290e8, 0x04400000)  --eden使用内存区域为4M
      from space 1024K,  47% used [0x04500000, 0x04578d98, 0x04600000)
      to   space 1024K,   0% used [0x04400000, 0x04400000, 0x04500000)
     tenured generation   total 10240K, used 6144K [0x04600000, 0x05000000, 0x05000000)--老年代 使用为6M

    大对象直接进入老年代

    大对象指的是,需要大量连续内存空间的Java对象,最典型的大对象是很长的字符串和数组。

    对于虚拟机而言,出现大对象容易导致内存空间还有不少空间就提前出发垃圾收集,这样耗费系统资源

    虚拟机提供一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接分配到老年代中。这样避免了新生代的多次的内存复制(新生代采用复制算法收集内存)

    注意: PretenureSizeThreshold参数只对Serial和ParNew两款收集器有效,Parallel Scavenge收集器不认识这个收集器,Parrallel Scavenge收集器一般不需要设置,如果遇到必须使用此参数,可以考虑ParNew和CMS收集器的组合

    测试代码:

    public class Jtest {
    	private static final int _1M = 1024*1024;
    	public static void main(String[] args) {
    		byte[] allocation4;
    		allocation4 = new byte[4*_1M];
    	}
    }

    JVM参数设置,在上面的案例的基础上加上:-XX:PretenureSizeThreshold=3145728

    Heap
     def new generation   total 9216K, used 984K [0x03c00000, 0x04600000, 0x04600000)
      eden space 8192K,  12% used [0x03c00000, 0x03cf6010, 0x04400000)
      from space 1024K,   0% used [0x04400000, 0x04400000, 0x04500000)
      to   space 1024K,   0% used [0x04500000, 0x04500000, 0x04600000)
     tenured generation   total 10240K, used 4096K [0x04600000, 0x05000000, 0x05000000) --从这里看出,4M对象直接放入老年代中
       the space 10240K,  40% used [0x04600000, 0x04a00010, 0x04a00200, 0x05000000)
     Metaspace       used 84K, capacity 2242K, committed 2368K, reserved 4480K

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

    虚拟机是采用分代收集的思想来决定内存使用何种收集算法进行内存回收的。那么就需要区分哪些对象放在新生代中,哪些对象放在老年代中。为了做到这点,虚拟机给每个对象定义了一个对象年龄(Age)计数器。

    如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能够被Survivor容纳的话,将移动到Survivor空间中,并且对象年龄设为1,并且没熬过一次Minor GC,年龄就会增加一岁,默认增加到15岁,自动晋升到老年代中

    默认年龄可以设置:  -XX:MaxTenuringThreshold

    测试代码:

    public class MaxAgeTest {
    	private static final int _1M = 1024*1024;
    	public static void main(String[] args) {
    		byte[] allocation1,allocation2,allocation3,allocation4;
    		allocation1 = new byte[_1M/4];//在第二次GC时候进入到老年代中
    		allocation2 = new byte[4*_1M];
    		allocation3 = new byte[4*_1M];//触发一次Minor GC,allocation3放入Eden区
    		allocation3 = null;//手动触发一次GC,Eden区清空了
    		allocation4 = new byte[4*_1M];//Eden再次放入一个4M对象
    	}
    }

    jvm参数:

    -Xms20M
    -Xmx20M
    -XX:+PrintGCTimeStamps 
    -XX:+PrintGCDetails 
    -verbose:gc 
    -Xloggc:d:/gc.log
    -Xmn10M
    -XX:SurvivorRatio=8
    -XX:MaxTenuringThreshold=1

    动态对象年龄判定

    虚拟机并不是永远要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代中,如果Survivor空间中相同年龄的对象的总和大于Survivor空间的一半,就将年龄大于等于该年龄的对象直接放入到老年代中

    测试代码:

    public class Jtest {
    	private static final int _1M = 1024*1024;
    	public static void main(String[] args) {
    		byte[] allocation1,allocation2,allocation3,allocation4;
    		allocation1 = new byte[_1M/4];
    		allocation2 = new byte[_1M/4];
    		allocation3 = new byte[4*_1M];
    		allocation4 = new byte[4*_1M];
    		allocation4 = null;
    		allocation4 = new byte[4*_1M];
    		
    	}
    }	

    JVM参数:

    -Xms20m
    -Xmx20m
    -XX:+PrintGCTimeStamps 
    -XX:+PrintGCDetails 
    -verbose:gc 
    -Xloggc:d:/gc.log
    -Xmn10M
    -XX:SurvivorRatio=8
    -XX:MaxTenuringThreshold=15

    GC日志:

    0.095: [GC (Allocation Failure) 0.095: [DefNew: 5427K->995K(9216K), 0.0037095 secs] 5427K->5091K(19456K), 0.0039170 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    0.099: [GC (Allocation Failure) 0.099: [DefNew: 5091K->0K(9216K), 0.0013279 secs] 9187K->5090K(19456K), 0.0014083 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    Heap
     def new generation   total 9216K, used 4260K [0x03e00000, 0x04800000, 0x04800000)
      eden space 8192K,  52% used [0x03e00000, 0x042290e8, 0x04600000)
      from space 1024K,   0% used [0x04600000, 0x04600000, 0x04700000)
      to   space 1024K,   0% used [0x04700000, 0x04700000, 0x04800000)
     tenured generation   total 10240K, used 5090K [0x04800000, 0x05200000, 0x05200000)
       the space 10240K,  49% used [0x04800000, 0x04cf8ac0, 0x04cf8c00, 0x05200000)
     Metaspace       used 84K, capacity 2242K, committed 2368K, reserved 4480K

    从日志的结果可以看出,最后2个1/4M的对象和一个4M的对象放入到老年代中,一个4M的放入在新生代的Eden区中,为什么会这样?

    第一次的GC,由于allocation4的创建而触发的,由于此时Eden区域中已经存放了allocation1->allocation3,内存空间不够。

    这样理论上是allocation3是分担进入到老年代中,但是为什么allocation1,allocation2也是直接进入到了老年代中,而没有等到15岁呢,首次不是应该放在Survivor区域中吗?

    因为两个对象加起来达到了Survivor的一半,并且是同年的,就直接进入老年代了

    第二次GC,触发前allocation4在新生代中,手动将引用置null,触发一次GC,后面又创建了allocation4,最后还是放在Eden中

    空间分配担保

    前面有介绍过,进行新生代的垃圾收集Minor GC时候,将存活对象移动到另一块的Survivor区域中,如果该内存区域空间不够,就会通过担保机制,放入到老年代中

    现在说下,这个担保细节?

    1. 在JDK 1.6 update 24版本前,如果发生Minor GC,虚拟机会检查老年代最大可用连续空间是否大于新生代对象总和,如果成立的话, 那么Minor GC可以确保是安全的,不过不是,则是检查参数HandlePromotionFailure是否允许担保失败,如果允许,那么会检查老年代的最大可用连续空间是否大于历次晋升的对象的平均值,如果是,那么就会尝试进行一次可能有风险的Minor GC,如果小于或者HandlerPromotionFailure不允许冒险,就会进行一次老年代垃圾收集 Full GC.

    2. 在JDK 1.6 update 24后,HandlerPromotionFailure参数是失败的,此时只要老年代的连续可用空间大于新生代对象总大小或者历次晋升的对象平均值就会进行Minor GC,否则进行Full GC.


    总结下,JVM的垃圾收集策略

    1. 对象优先在Eden区域分配,如果空间不够,触发一次 Minor GC 

    2. 大对象直接进入到老年代中,可以通过参数设置,只要对象大于这个参数-XX:PretenureSizeThreshold的值,直接进入老年代

    3. 长期存活的对象将进入老年代,只要对象的年龄等于这个参数-XX:MaxTenuringThreshold的值,下次gc进入老年代

    4. 如果Survivor年龄相同的对象的总空间大于Survivor空间的一半,只要年龄大于等于这个年龄的对象,直接进入老年代

    5. 执行新生代垃圾收集时候,存在一个担保原则,老年代的连续可用空间大于新生代对象总大小或者历次晋升的对象平均值就会进行Minor GC,否则进行Full GC.

    展开全文
  • Java内存区域划分和内存分配策略

    千次阅读 多人点赞 2020-05-15 12:32:46
    Java内存区域划分和内存分配策略 如果不知道,类的静态变量存储在那? 方法的局部变量存储在那? 赶快收藏 Java内存区域主要可以分为共享内存,堆、方法区和线程私有内存,虚拟机栈、本地方法栈和程序计数器。如下...

    Java内存区域划分和内存分配策略

    如果不知道,类的静态变量存储在那? 方法的局部变量存储在那? 赶快收藏

    Java内存区域主要可以分为共享内存,方法区和线程私有内存,虚拟机栈、本地方法栈和程序计数器。如下图所示,本文将详细讲述各个区域,同时也会讲述创建对象过程,内存分配策略, 和对象访问定位原理。觉得写得好的,可以点个收藏,绝对不亏。

    Java内存区域

    程序计数器

    程序计数器,可以看作程序当前线程所执行的字节码行号指示器。字节码解释器工作时就是通过改变计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理都需要依赖计数器完成。线程执行Java方法时,记录其正在执行的虚拟机字节码指令地址,线程执行Native方法时,计数器记录为空。程序计数器时唯一在Java虚拟机规范中没有规定任何OutOfMemoryError情况区域。

    理论可知,线程是通过轮流获取CPU执行时间以实现多线程的并发。为了暂停的线程下一次获得CPU执行时间,能正常运行,每一个线程内部都需要维护一个程序计数器,用来记住暂停线程暂停的位置。

    注意:光理论是不够的,在此送大家一套2020最新Java架构实战教程+大厂面试宝典,点击此处 进来获取 一起交流进步哦!

    Java虚拟机栈

    Java虚拟机栈同程序计数器一样,也是线程私有的,虚拟机栈描述的是Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表,操作数栈、动态链接和方法出入口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。

    本地方法栈

    与虚拟机栈相似。虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。

    Java堆

    所有线程共享的一块内存区域。Java虚拟机所管理的内存中最大的一块,因为该内存区域的唯一目的就是存放对象实例。几乎所有的对象实例都在这里分配内存,同时堆也是垃圾收集器管理的主要区域。因此很多时候被称为"GC堆"

    方法区

    和堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、和编译器编译后的代码(也就是存储字节码文件.class)等数据

    方法区中有一个运行时常量池,编译后期生成的各种字面量和符号引用,存放在字节码文件中的常量池中。当类加载进入方法区时,就会把该常量池中的内容放入方法区中的运行时常量池。此外也可以在程序运行期间,将新的常量放入运行时常量池,比如String.intern()方法,该方法先从运行时常量池中查找是否有该值,如果有,则返回该值的引用,否则将该值加入运行时常量池。

    实例详讲

    class Demo1_Car{
        public static void main(String[] args) {
            Car c1 = new Car();
            //调用属性并赋值
            c1.color = "red";
            c1.num = 8;
            //调用行为
            c1.run();
            Car c2 = new Car();
            c2.color = "black";
            c2.num = 4;
            c2.run();
        }
    }
    Class Car{
        String color;
        int num;
        public void run() {
        	System.out.println(color + ".." + num);
    	}
    }
    

    • 首先运行程序,Demo1_car.java就会变为Demo1_car.classDemo1_car.class加入方法区,检查是否字节码文件常量池中是否有常量值,如果有,那么就加入运行时常量池。
    • 遇到main方法,创建一个栈帧,入虚拟机栈,然后开始运行main方法中的程序。
    • Car c1 = new Car(), 第一次遇到Car这个类,所以将Car.java编译为Car.class文件,然后加入方法区.然后new Car(),在堆中创建一块区域,用于存放创建出来的实例对象,地址为0X001.其中有两个属性值colornum。默认值是null和 0
    • 然后通过c1这个引用变量去设置colornum的值,调用run方法,然后会创建一个栈帧,用来存储run方法中的局部变量等。run 方法中就打印了一句话,结束之后,该栈帧出虚拟机栈。又只剩下main方法这个栈帧。
    • 接着又创建了一个Car对象,所以又在堆中开辟了一块内存,之后就是跟之前的步骤一样了。

    创建对象过程

    虚拟机在遇到一条new指令时,会首先检查这个指令的参数是否可以在方法区中定位到一个类的符号引用,并且检查这个符号引用所代表的类是否已经被加载,解析和初始化过。如果没有,则必须先执行类加载过程.

    类加载完之后,需要为对象分配内存,有两种分配内存的方法

    • 指针碰撞法(要求堆内存规整)

      Java堆中空闲内存和已使用内存分别存放在堆的两边,中间存放一个指针作为分界点的指示器,在为对象分配内存时只需要将指针向空闲区域移动创建对象所需要的内存大小即可。

    • 空闲列表法

      如果堆内存中已使用内存区域和空闲区域相互交错,此时虚拟机需要维护一个列表,记录哪些内存块是可用的,在分配时从列表中找到一块足够大的内存区域划分给对象实例并更新列表上的记录。

    多线程情况下,线程同时分配内存可能会造成冲突,比如使用指针碰撞法,线程A正在分配内存,还没有改变指针指向,线程B,又同时使用原来的指针进行内存分配。防止冲突有两种方法

    • CAS操作:虚拟机采用CAS操作,加上失败重试的方式保证内存分配的原子性
    • 本地线程分配缓冲(TLAB):预先为线程分配一部分堆内存空间(线程私有,所以不存在同步问题)用于对象实例的内存分配。只有当TLAB用完,需要分配新的TLAB时,才需要进行同步操作。

    内存分配完之后,虚拟机需要将分配到的内存空间均初始化为零值(不包括对象头)。在虚拟机中,执行完new指令后会接着执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来

    对象在内存中的布局

    对象在内存中的布局如下图所示,分为对象头、实例数据、对齐填充
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传在这里插入图片描述

    • 对象头(可以参考Java锁升级)

      mark Word, 用于存储对象自身的运行时数据,如哈希码、GC分代年龄以及锁状态标志等。类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

    • 实例数据

      对象真正存储的有效信息,也是程序代码中所定义的各种类型的字段内容。

    • 对齐填充

      并非必然存在,仅仅起着占位符的作用。

    对象的访问定位

    Java程序需要通过栈上的reference数据来操作堆上的具体对象。共有两种策略进行对象的访问定位

    • 句柄访问

      Java堆中划分出一块内存来作为句柄池,reference中存储的是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息,需要两次寻址。

    • 直接指针访问

      Java堆中对象的布局中需要考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。

    使用句柄访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中实例数据指针,而reference本身不需要修改。

    问题

    只需要记住一件事,就是Java对象的内存分配均是在堆中进行的。所以对象都存储在堆中

    但是有人可能会怀疑方法的临时变量不是存储在虚拟机栈中吗?这里我要解释一下,虚拟机栈维护了一个局部变量表,表中存储的是对象的引用,而真正存储对象的地方在堆,如果局部变量都在堆里分配,那么虚拟机栈早爆满了

    同样类的静态变量,有人又会怀疑在方法区中存储。其实不是的,方法区只存储引用,具体对象是存储在堆中的,具体实现可以发现,类静态对象是与class对象一起分配的内存。

    注意:光理论是不够的,在此送大家一套2020最新Java架构实战教程+大厂面试宝典,点击此处 进来获取 一起交流进步哦!

    参考

    深入理解java虚拟机

    展开全文
  • JVM 内存分配策略

    2018-12-27 13:42:17
    在操作系统中将内存分配策略分为三种,分别是: 1) 静态内存分配 2) 栈内存分配   3) 堆内存分配   静态内存分配是指在程序编译时就能确定每个数据在运行时的存储空间需求,因此在编译时就可以给它们分配固定...
  • 主要介绍了浅谈Java内存区域划分和内存分配策略,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • JVM内存分配策略

    2019-11-08 18:48:40
    内存分配策略 Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决两个问题:给对象分配内存以及回收分配给对象的内存。 对象的内存分配,就是在堆上分配,对象主要分配在新生代的Eden区上,分配的规则...
  • JVM内存分配 策略

    2020-07-10 10:25:39
    前面已经介绍了内存回收,下面介绍几条最普遍的内存分配策略: ①对象优先在 Eden 区分配:大多数情况下,对象在先新生代 Eden 区中分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Young GC。 ②大...
  • 主要介绍了Java GC 机制与内存分配策略详解的相关资料,需要的朋友可以参考下
  • JVM内存结构及内存分配策略
  • 垃圾收集器与内存分配策略(六)——内存分配与回收策略 对象的内存分配,一般来说就是在堆上的分配(但也可能经过JIT编译后被拆散为标量类型并间接地栈上分配),对象分配的细节取决于当前使用的是哪一种垃圾收集器...
  • JVM的内存分配策略

    2018-10-31 21:01:44
    转载地址:JVM总结(二):JVM的内存分配策略 目录 内存分配策略 对象优先在新生代Eden分配 大对象直接进入老年代 长期存活的对象将进入老年代 动态对象年龄判定 空间分配担保 一、内存分配策略  1.Java...
  • Java 内存分配策略Java 程序运行时的内存分配策略有三种,分别是静态分配,栈式分配,和堆式分配,对应的,三种存储策略使用的内存空间主要分别是静态存储区(也称方法区)、栈区和堆区。静态存储区(方法区):主要...

空空如也

1 2 3 4 5 ... 20
收藏数 5,981
精华内容 2,392
关键字:

内存分配策略