我们说Java是自动进行内存管理的,所谓自动化就是,不需要程序员操心,Java会自动进行内存分配内存回收这两方面。

  前面我们介绍过如何通过垃圾回收器来回收内存,那么本篇博客我们来聊聊如何进行分配内存。

  对象的内存分配,往大方向上讲,就是堆上进行分配(但也有可能经过JIT编译后被拆散为标量类型并间接的在栈上分配),对象主要分配在新生代 Eden 区上,如果启动了本地线程分配缓冲,将按线程优先在 TLAB 上分配。少数情况下也可能会直接分配在老年代上(下面会详细介绍),分配的规则并不是百分之百固定的,其细节取决于当前使用哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数设置。

  本篇博客会介绍几条最普遍的内存分配规则。通过增加 -XX:+UseParallelGC 参数,表示使用的垃圾收集器是 Parallel Scavenge + Serial Old ,通过这两个垃圾收集器组合进行校验。

1、Minor GC 、Major GC 和 Full GC

  下面会出现这几个概念,所以这里首先介绍一下。

  ①、Minor GC

  也叫Young GC,指的是新生代 GC,发生在新生代(Eden区和Survivor区)的垃圾回收。因为Java对象大多是朝生夕死的,所以 Minor GC 通常很频繁,一般回收速度也很快。

  ②、Major GC

  也叫Old GC,指的是老年代的 GC,发生在老年代的垃圾回收,该区域的对象存活时间比较长,通常来讲,发生 Major GC时,会伴随着一次 Minor GC,而 Major GC 的速度一般会比 Minor GC 慢10倍。

  ②、Full GC

  指的是全区域(整个堆)的垃圾回收,通常来说和 Major GC 是等价的。  

1、对象优先在 Eden 上分配

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

package com.ys.algorithmproject.leetcode.demo.JVM;

/**
* Create by YSOcean
* 对象优先在Eden区上分配
*/
public class EdenTest {
private static final int _1MB = *; /**
* 虚拟机参数设置:-XX:+UseParallelGC -XX:+PrintGCDetails -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
* @param args
*/
public static void main(String[] args) {
byte[] a = new byte[*_1MB];
byte[] b = new byte[*_1MB];
byte[] c = new byte[*_1MB];
byte[] d = new byte[*_1MB];
}
}

  运行时的虚拟机参数设置为:

-XX:+UseParallelGC -XX:+PrintGCDetails -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=

  ①、 -XX:+UseParallelGC 参数,表示使用的垃圾收集器是 Parallel Scavenge + Serial Old ;

  ②、-XX:+PrintGCDetails 参数,表示打印详细的GC日志,便于我们查看GC情况

  ③、-Xms20M -Xmx20M 这两个参数分别表示设置最大堆,最小堆内存都是20M

  ④、-Xmn 参数表示设置新生代大小为 10M

  ⑤、-XX:SurvivorRatio=8 新生代中的 Eden 区和 Survivor 区的比值为8:1,注意 Survivor是有两个的。

  运行打印的GC日志为:

  我们首先分析设置的JVM参数,表示堆中内存为20M,新生代和老年代分别各占一半为10M,并且新生代的Eden区为8M,剩下两个 Survivor 各为 1M。

  在看代码,首先分配了三个大小都为2M的对象 a,b,c。这时候新生代对象的 Eden区已经被占用了6M,这时候来了一个对象d,大小为3M,发现新生代Eden区已经不足以分配对象d了,于是发起一次Minor GC。GC期间虚拟机又发现现在已有3个 2MB对象无法全部放入Survivor空间(Survivor空间只有1MB),所以只好通过分配担保机制提前转移到老年代中,然后将这个对象d分配到新生代Eden区中。

  我们查看日志,在eden区中,总共8192K的空间,被使用了38%,约等于3113K,大概就是对象d(3MB)的大小。其次在老年代中,总共10240K(10MB),被使用了6865K,大概也就是a,b,c这三个对象的大小(6MB)。

2、大对象直接进行老年代

  通常大对象是指需要大量连续内存空间的Java对象,比较典型的就是那种很长的字符串以及数组。

  系统中出现大量大对象是很影响性能的,这样会导致还有不少空间时就提前触发垃圾回收来放置这些对象。

package com.ys.algorithmproject.leetcode.demo.JVM;

/**
* Create by YSOcean
* 大对象直接在老年代上分配
*/
public class OldTest {
private static final int _1MB = *; /**
* 虚拟机参数设置:-XX:+UseParallelGC -XX:+PrintGCDetails -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
* @param args
*/
public static void main(String[] args) {
byte[] a = new byte[*_1MB]; }
}

  运行时虚拟机参数还和上面一样,运行的GC日志如下:

  

  可以看到老年代 ParOldGen直接被使用了 8192K,而新生代只被占用了1820K。

  PS:可以通过设置-XX:PretenureSizeThreshold 参数,大于这个参数设置值的对象直接在老年代中分配,但是这个参数只对 Serial 和 ParNew 这两款垃圾收集器有效,Parallel Scavenge 收集器不认识这个参数。

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

  我们知道Java虚拟机是通过分代收集的思想来管理内存,新创建的对象通常放在新生代,除此之外,还有一些对象放在老年代。为了识别哪些对象放在新生代,哪些对象放在老年代,虚拟机给每个对象定义了一个年龄计数器(Age),如果对象在新生代Eden创建,并经历一次 Minor GC 后仍然存活,并且能够被 Survivor 容纳的话,虚拟机会将该对象移动到 Survivor 区域,并将对象的年龄Age+1。

  新生代对象每熬过一次 Minor GC,年龄就增加1,当它的年龄增加到一定阈值时(默认是15岁),就会被晋升到老年代中。

  这个年龄阈值可以通过如下参数来设置(N表示晋升到老年代的阈值):

-XX:MaxTenuringThreshold=N

  验证代码如下:

package com.ys.algorithmproject.leetcode.demo.JVM;

/**
* Create by YSOcean
* 新生代对象经过N次Minor GC后,晋升到老年代
*/
public class OldAgeTest {
private static final int _1MB = 1024*1024; /**
* 虚拟机参数设置:-XX:MaxTenuringThreshold=1 -XX:+UseParallelGC -XX:+PrintGCDetails -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
* @param args
*/
public static void main(String[] args) {
byte[] a = new byte[_1MB];
System.gc(); } }

  注意:这里我们设置 -XX:MaxTenuringThreshold=1,也就是经历一次gc,新生代对象就直接进入老年代了,然后手动调用了 System.gc() 方法,表示让虚拟机进行垃圾回收。打印的日志如下:

  

  注意看,代码中我们只创建了一个 1MB大小的对象,但是老年代占用了1999K的内存,而新生代确只有246K。

  接下来可以将 -XX:MaxTenuringThreshold 参数设置的更大一点,来对比打印的日志,这里读者可以自己进行验证。

4、新生代Survivor 区相同年龄所有对象之和大于 Survivor 所有对象之和的一半,大于等于该年龄的对象进入老年代

  Java虚拟机并不会死板的根据上面第3点说的,设置-XX:MaxTenuringThreshold 的阈值,只有对象经历该阈值次GC后,才会进入到老年代。而是会根据新生代对象的年龄来动态的决定哪些对象可以进入到老年代。

  也就是说,新生代经历一次 Minor GC 后,Survivor 区域存活对象的所有相同年龄之和大于整个 Survivor 区域的所有对象之和,那么该区域大于等于这个年龄的对象就会进入老年代,而无需等到 -XX:MaxTenuringThreshold 设置的阈值。

5、空间分配担保原则

  在前面介绍 垃圾回收 时,我们介绍过现在Java虚拟机采用的是分代回收算法,新生代采用复制收集算法,而老年代采用标记整理,或者标记清除算法。

  

  新生代内存分为一块 Eden区,和两块 Survivor 区域,当发生一次 Minor GC时,虚拟机会将Eden和一块Survivor区域的所有存活对象复制到另一块Survivor区域,通常情况下,Java对象朝生夕死,一块 Survivor 区域是能够存放GC后剩余的对象的,但是极端情况下,GC后仍然有大量存活的对象,那么一块 Survivor 区域就会存放不下这么多的对象,那么这时候就需要老年代进行分配担保,让无法放入 Survivor 区域的对象直接进入到老年代,当然前提是老年代还有空间能够存放这些对象。但是实际情况是在完成GC之前,是不知道还有多少对象能够存活下来的,所以老年代也无法确认是否能够存放GC后新生代转移过来的对象,那么这该怎么办呢?

  前面我们介绍的都是Minor GC,那么何时会发生 Full GC?

  在发生 Minor GC 时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间,如果大于,则改为 Full GC。如果小于,则查看 HandlePromotionFailure 设置是否允许担保失败,如果允许,那只会进行一次 Minor GC,如果不允许,则也要进行一次 Full GC。

-XX:-HandlePromotionFailure

  回到第一个问题,老年代也无法确认是否能够存放GC后新生代转移过来的对象,那么这该怎么办呢?

  也就是取之前每一次回收晋升到老年代对象容量的平均大小作为经验值,然后与老年代剩余空间进行比较,来决定是否进行 Full GC,从而让老年代腾出更多的空间。

  通常情况下,我们会将 HandlePromotionFaile 设置为允许担保失败,这样能够避免频繁的发生 Full GC。

Java虚拟机详解(六)------内存分配的更多相关文章

  1. Java虚拟机详解02----JVM内存结构

    主要内容如下: JVM启动流程 JVM基本结构 内存模型 编译和解释运行的概念 一.JVM启动流程: JVM启动时,是由java命令/javaw命令来启动的. 二.JVM基本结构: JVM基本结构图: ...

  2. Java虚拟机详解----JVM内存结构

    http://www.cnblogs.com/smyhvae/p/4748392.htm 主要内容如下: JVM启动流程 JVM基本结构 内存模型 编译和解释运行的概念 一.JVM启动流程: JVM启 ...

  3. Java虚拟机详解----JVM常见问题总结

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...

  4. Java虚拟机垃圾收集器与内存分配策略

    Java虚拟机垃圾收集器与内存分配策略 概述 那些内存须要回收,什么时候回收.怎样回收是GC须要完毕的3件事情. 程序计数器.虚拟机栈与本地方法栈这三个区域都是线程私有的,内存的分配与回收都具有确定性 ...

  5. Java虚拟机详解(二)------运行时内存结构

    首先通过一张图了解 Java程序的执行流程: 我们编写好的Java源代码程序,通过Java编译器javac编译成Java虚拟机识别的class文件(字节码文件),然后由 JVM 中的类加载器加载编译生 ...

  6. JVM完整详解:内存分配+运行原理+回收算法+GC参数等

    不管是BAT面试,还是工作实践中的JVM调优以及参数设置,或者内存溢出检测等,都需要涉及到Java虚拟机的内存模型.内存分配,以及回收算法机制等,这些都是必考.必会技能. JVM内存模型 JVM内存模 ...

  7. Java虚拟机详解04----GC算法和种类【重要】

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...

  8. Java虚拟机详解04----GC算法和种类

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...

  9. Java 虚拟机详解

    深入理解JVM 1   Java技术与Java虚拟机 说起Java,人们首先想到的是Java编程语言,然而事实上,Java是一种技术,它由四方面组成: Java编程语言.Java类文件格式.Java虚 ...

随机推荐

  1. 【学习笔记】动态规划—斜率优化DP(超详细)

    [学习笔记]动态规划-斜率优化DP(超详细) [前言] 第一次写这么长的文章. 写完后感觉对斜优的理解又加深了一些. 斜优通常与决策单调性同时出现.可以说决策单调性是斜率优化的前提. 斜率优化 \(D ...

  2. HTML表单部分学习笔记

    第一部分: <!-- action :指定处理提交表单的格式 method :指定提交表单的http方法 enctype:指明用来把表单提交给服务器时的互联网媒体形式 --> <fo ...

  3. MyBatis foreach标签的用法

    From<MyBatis从入门到精通> 一.foreach实现in集合 1.映射文件中添加的代码: <!-- 4.4 foreach用法 SQL语句有时会使用IN关键字,例如id i ...

  4. [二次编码,数据类型补充以及各种坑]https://i.cnblogs.com/EditPosts.aspx?postid=11184330

    数据类型补充 str:不可变数据类型 1.capitalize首字母大写 name="song" n=name.capitalize() print(n) Song 2.title ...

  5. Mysql 时间相关

    -- 当前时间SELECT NOW(), SYSDATE(), CURRENT_TIMESTAMP(), LOCALTIME(), LOCALTIMESTAMP();SELECT CURDATE(), ...

  6. 【Java中级】(三)IO

    1. 流分为字节流和字符流 2. 字节流下面常用的又有数据流和对象流 3. 字符流下面常用的又有缓存流 文件对象 文件和文件夹都用File表示 //file path : 文件的绝对路径或相对路径Fi ...

  7. Java中使用 foreach 操作数组

    foreach 并不是 Java 中的关键字,是 for 语句的特殊简化版本,在遍历数组.集合时, foreach 更简单便捷.从英文字面意思理解 foreach 也就是" for 每一个& ...

  8. python课堂整理2

    一.字节和编码 1个字节是8位二进制 utf-8 表示一个中文要用3个字节 gbk 为中国汉字发明,2个字节可表示汉字 所以 utf-8 可以读gbk编码,而gbk去读utf-8 的内容会乱码 uni ...

  9. 自动化部署方案CICD

    自动化部署方案   由于来来也的时间不久,可能对现有的部署情况不是很了解,以下是个人对POC自动化部署的设计方案. 自动化部署优点 降低成本,提高生产力,高可用,更可靠,性能优化   与gitlab持 ...

  10. paddlepaddle实现猫狗分类

    目录 1.预备工作 1.1 数据集准备 1.2 数据预处理 2.训练 2.1 模型 2.2 定义训练 2.3 训练 3.预测 4.参考文献 声明:这是我的个人学习笔记,大佬可以点评,指导,不喜勿喷.实 ...