我们说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. Spring中@value以及属性注入的学习

    1.简单的Java配置 配置文件(jdbc.properties) jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://1 ...

  2. 企业如何从“API优先”的策略中获益

    在过去的几年里,全球API经济在以难以置信的速度进行快速地增长.物联网.人工智能.自动驾驶等等众多令人充满期待的技术正蓬勃发展,这也证明了API对于如今整个技术圈子的重要性,也预示着在不久的将来它还将 ...

  3. [转载]nginx负载均衡+keepalived三主(多主)配置

    nginx负载均衡+keepalived三主(多主)配置 1.实验环境,实现目标三台主机分别配置nginx负载均衡对后端多台主机做转发,同时配置keepalived实现HA,保证任意主机出现故障时其他 ...

  4. 洛谷P3324 [SDOI2015]星际战争 题解

    题目链接: https://www.luogu.org/problemnew/show/P3324 分析: 因为本题的时间点较多,不能枚举,但发现有单调性,于是二分答案,二分使用的时间TTT 每个攻击 ...

  5. 个人永久性免费-Excel催化剂功能第60波-数据有效性验证增强版,补足Excel天生不足

    Excel在数据处理.数据分析上已经是公认的最好用的软件之一,其易用性和强大性也吸引无数的初中高级用户每天都在使用Excel.但这些优点的同时,也带出了一些问题,正因为其不同于一般的专业软件,需要专业 ...

  6. HDU-1576 A/B 基础数论+解题报告

    HDU-1576 A/B 基础数论+解题报告 题意 求(A/B)%9973,但由于A很大,我们只给出n(n=A%9973) (我们给定的A必能被B整除,且gcd(B,9973) = 1). 输入 数据 ...

  7. restapi(2)- generic restful CRUD:通用的restful风格数据库表维护工具

    研究关于restapi的初衷是想搞一套通用的平台数据表维护http工具.前面谈过身份验证和使用权限.文件的上传下载,这次来到具体的数据库表维护.我们在这篇示范里设计一套通用的对平台每一个数据表的标准维 ...

  8. 哥们,B/S了解吗?——啥玩意,我是敲代码的

    了解B/S和C/S 前言:......“学好长时间编程了,JavaSE学完了,前端也简单学了”.....“那你学这么多,讲讲B/S吧”......“B/S?这是个啥玩意?没听过”......“靠,牛逼 ...

  9. Python在office开发中的应用

    Python with Excel 有几个很好的Python模块能够方便地操作Excel的数据,包括读与写,不要求本地安装Excel.例如pandas, openpyxl, xlrd, xlutils ...

  10. Kafka学习(三)-------- Kafka核心之Cosumer

    了解了什么是kafka( https://www.cnblogs.com/tree1123/p/11226880.html)以后 学习核心api之消费者,kafka的消费者经过几次版本变化,特别容易混 ...