Java虚拟机详解(六)------内存分配
我们说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虚拟机详解(六)------内存分配的更多相关文章
- Java虚拟机详解02----JVM内存结构
主要内容如下: JVM启动流程 JVM基本结构 内存模型 编译和解释运行的概念 一.JVM启动流程: JVM启动时,是由java命令/javaw命令来启动的. 二.JVM基本结构: JVM基本结构图: ...
- Java虚拟机详解----JVM内存结构
http://www.cnblogs.com/smyhvae/p/4748392.htm 主要内容如下: JVM启动流程 JVM基本结构 内存模型 编译和解释运行的概念 一.JVM启动流程: JVM启 ...
- Java虚拟机详解----JVM常见问题总结
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...
- Java虚拟机垃圾收集器与内存分配策略
Java虚拟机垃圾收集器与内存分配策略 概述 那些内存须要回收,什么时候回收.怎样回收是GC须要完毕的3件事情. 程序计数器.虚拟机栈与本地方法栈这三个区域都是线程私有的,内存的分配与回收都具有确定性 ...
- Java虚拟机详解(二)------运行时内存结构
首先通过一张图了解 Java程序的执行流程: 我们编写好的Java源代码程序,通过Java编译器javac编译成Java虚拟机识别的class文件(字节码文件),然后由 JVM 中的类加载器加载编译生 ...
- JVM完整详解:内存分配+运行原理+回收算法+GC参数等
不管是BAT面试,还是工作实践中的JVM调优以及参数设置,或者内存溢出检测等,都需要涉及到Java虚拟机的内存模型.内存分配,以及回收算法机制等,这些都是必考.必会技能. JVM内存模型 JVM内存模 ...
- Java虚拟机详解04----GC算法和种类【重要】
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...
- Java虚拟机详解04----GC算法和种类
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...
- Java 虚拟机详解
深入理解JVM 1 Java技术与Java虚拟机 说起Java,人们首先想到的是Java编程语言,然而事实上,Java是一种技术,它由四方面组成: Java编程语言.Java类文件格式.Java虚 ...
随机推荐
- cola-ui的使用
[toc] > 官方:[http://www.cola-ui.com](http://www.cola-ui.com) > > 教程位置:[http://www.cola-ui.co ...
- 使用MyBatis在控制台动态打印执行的sql语句
使用MyBatis进行数据库操作的时候,sql语句都是写在相应的mapper文件中,参数也是使用占位符取值表示的,mapper文件中看不到实时执行的完整sql,有时候sql语句错误或者参数类型不对的时 ...
- 开源框架Autofac使用入门
目录导航 1.Autofac是什么 1.1什么是DI和IOC 1.2DI和IOC在项目中起到什么作用 2.Autofac如何使用 2.1下载 2.2代码Demo 2.3Demo分析 3总结 1.Aut ...
- python的ORM技巧记录
# -*- coding:utf-8 -*- from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, In ...
- python介绍、安装及相关语法、python运维、编译与解释
1.python介绍 Python(英国发音:/ˈpaɪθən/ 美国发音:/ˈpaɪθɑːn/)是一种广泛使用的解释型.高级编程.通用型编程语言,由吉多.范罗苏姆创造,第一版发布于1991年.可以视 ...
- bulk更新mongodb的脚本
bulk批处理mongodb,比普通的js脚本来的更快一些. 官方网址:https://docs.mongodb.com/manual/reference/method/Bulk/ bulk支持的方法 ...
- python课堂整理3---字符串魔法
字符串魔法 1.首字母大写功能 test = "alex" v = test.capitalize() print(v) 2.所有变小写(casefold更厉害,可以将很多未知的其 ...
- solr配置分词器
一.solr4.10 + mmseg4j-2.2.0分词器 1.solr的安装部署:http://www.cnblogs.com/honger/p/5876289.html,注意不同的版本安装方式可能 ...
- 用python绘制漂亮的图形
先看效果,没有用任何绘图工具,只是运行了一段python代码. 代码如下: _ = ( 255, lambda V ,B,c :c and Y(V*V+B,B, c -1)if(abs(V)<6 ...
- Nginx配置安装(Mac)
我用到的安装工具是:homebrew 真的很方便! 步骤1: 打开终端,输入 brew info nginx结果:我们可以看到,nginx在本地还未安装(Not installed),nginx的来源 ...