Java高级——内存分配机制

2023-10-27

概述

对象都是在堆上分配,但实际上也有可能经过即时编译后被拆散为标量类型并间接地在栈上分配

分代设计下,新生对象通常会分配在新生代中,少数
情况下(例如对象大小超过一定阈值)也可能会直接分配在老年代

《Java虚拟机规范》并未规定新对象的创建和存储细节,取决于垃圾收集器及参数的设定

以下如无特别说明,均使用HotSpot虚拟机的Serial+Serial Old,JDK8环境

对象优先在Eden分配

设置堆20M,新生代Eden 8M,2个Survivor 1M,老年代10M

-Xms20M -Xmx20M -Xmn10M  -XX:SurvivorRatio=8  
-XX:+PrintGCDetails -XX:+UseSerialGC

如下程序分配三个2M,一个3M对象

public class Test {

	private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
        allocation4 = new byte[3 * _1MB];
    }
	
}

分配allocation4时,Eden已用了6MB,剩余空间不够分配allocation4的3MB发生一次GC

在这里插入图片描述

如上图,三个2MB对象无法放入Survivor(只有1M),只能将其放到老年代,并在eden分配allocation4的3MB

大对象直接进入老年代

大对象指需要大量连续内存空间的Java对象,如很长的字符串、元素数量多的数组

需避免大对象,因为会导致内存还有空间就提前触发gc,且复制时占用大量内存

-XX:PretenureSizeThreshold 可设置大于该值的对象直接在老年代分配,避免其在Eden和Survivor来回复制占用内存,其只能对Serial和ParNew有效

-Xms20M -Xmx20M -Xmn10M  -XX:SurvivorRatio=8 -XX:+PrintGCDetails
-XX:PretenureSizeThreshold=3145728 -XX:+UseSerialGC

如上设置为3MB,如下分配一个4MB数组

public class Test {

	private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        byte[] allocation;
        
        allocation = new byte[4 * _1MB];
    }
	
}

对象会直接分配到老年代

在这里插入图片描述

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

虚拟机为每个对象定义了一个年龄计数器,存储在对象头

Eden的对象经过一次Minor GC后仍存活并能被Survivor容纳的话,就会被移动到Survivor

对象在Survivor中每熬过一次Minor GC,年龄就增加1岁,当它的年龄达到15(-XX:MaxTenuringThreshold),就会被晋升到老年代中

-XX:TargetSurvivorRatio设置超过多少Survivor比例,会重新计算Threshold,为避免其影响设置的高一点为80

-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 
-XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+UseSerialGC -XX:TargetSurvivorRatio=80

如下allocation1需要256KB,Survivor可以容纳

public class Test {

	private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        byte[] allocation1,allocation2,allocation3;
        
        allocation1 = new byte[_1MB / 4];
        
        allocation2 = new byte[4 * _1MB];
        allocation3 = new byte[4 * _1MB];	//第一次GC
        allocation3 = null;
        allocation3 = new byte[4 * _1MB];	//第二次GC
    }
	
}

当-XX:MaxTenuringThreshold=1时

  • allocation2在第一次GC直接进入老年代(Survivor放不下),即5337K->786K
  • allocation1先进入Survivor然后在第二次GC进入老年代,新生代被收集变为0
  • 截图4178为allocation3+其他,4881为allocation1+allocation2+其他

在这里插入图片描述

当-XX:MaxTenuringThreshold=15时,可看到第二次GC后,allocation1仍留在新生代的Survivor

在这里插入图片描述

动态对象年龄判定

并非年龄达到-XX:MaxTenuringThreshold才能晋升老年代

若Survivor中相同年龄所有对象大小的总和大于Survivor的一半,年龄大于或等于该年龄的对象就可直接进入老年代

-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 
-XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+UseSerialGC -XX:MaxTenuringThreshold=15

如下设置晋升年龄为15

public class Test {

	private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        byte[] allocation1,allocation2,allocation3,allocation4;
        
        allocation1 = new byte[_1MB / 4];  
        allocation2 = new byte[_1MB / 4];
        allocation3 = new byte[4 * _1MB];	
        allocation4 = new byte[4 * _1MB];   //第一次GC
        allocation4 = null;
        allocation4 = new byte[4 * _1MB];   //第二次GC
    }
	
}

可看到第一次GC后还有1023K,年龄为1但没进入Survivor,而是进入老年代

在这里插入图片描述

其实就是参数-XX:TargetSurvivorRatio,将其设为100,可看到仍进入Survivor

在这里插入图片描述

空间分配担保

在Minor GC之前

  • 检查老年代最大可用连续空间是否大于新生代所有对象总空间
  • 若不成立,查看-XX:HandlePromotionFailure是否允许分配担保
  • 若允许,检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,若大于则进行一次有风险的Minor GC
  • 若小于或-XX:HandlePromotionFailure设置不允许或担保失败则Full GC

分配担保指的是当出现大量对象在Minor GC后存活,把Survivor无法容纳的对象直接送入老年代

-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 
-XX:+PrintGCDetails -XX:+UseSerialGC

在JDK6u23运行代码

public class Test {

	private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        byte[] allocation1,allocation2,allocation3,allocation4,allocation5,allocation6,allocation7;
        
        allocation1 = new byte[2 * _1MB];  
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];	//第一次gc
        allocation1 = null;
        allocation4 = new byte[2 * _1MB];
        allocation5 = new byte[2 * _1MB];
        allocation6 = new byte[2 * _1MB]; //第二次gc
        allocation4 = null;
        allocation5 = null;
        allocation6 = null;
        allocation7 = new byte[2 * _1MB];
    }
	
}

当-XX:+HandlePromotionFailure时

  • 第一次gc,allocation 1回收,23直接进入老年代(7585K->159K表示新生代暂无数据,6487K->4255K表示整堆还有23),4分配新生代
  • 第二次gc,allocation 456想进入老年代,老年代空间空间不够且不允许担保,Full GC回收,回收456,7分配新生代
  • 故最后2211K表示7在新生代,4255K表示23仍在老年代

在这里插入图片描述

当-XX:+HandlePromotionFailure时

  • 第一次gc,allocation 1回收,23直接进入老年代(7585K->362K表示新生代暂无数据,6487K->4255K表示整堆还有23),4分配新生代
  • 第二次gc,allocation 456想进入老年代,老年代空间空间不够但允许担保,Mirror GC回收,回收456,7分配新生代

在这里插入图片描述

在JDK 6 Update 24之后,该参数无效,只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就Minor GC否则Full GC

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java高级——内存分配机制 的相关文章

随机推荐

  • c语言循环结构程序设计实验报告,c语言循环结构程序设计实验报告

    c语言循环结构程序设计实验报告 云南大学数学与统计学实验教学中心实验报告课程名称 程序设计和算法语言 学期 2012 2013 学年下学期 成绩 指导教师 学生姓名 学生学号实验名称 循环结构程序设计实验编号 四 实验日期 实验学时 3学院
  • 在Pycharm中使用HTMLTestRunner不能生成测试报告

    遇到一个问题 在做自动化测试时 使用的编辑工具是Pycharm 语言是python3 selenium3 代码运行没有问题 但是就是执行完毕后没有在对应目录生成测试报告 因为之前使用的是python2 7 selenium2 程序运行是没有
  • 13.网络爬虫—多进程详讲(实战演示)

    网络爬虫 多进程详讲 一 进程的概念 二 创建多进程 三 进程池 四 线程池 五 多进程和多线程的区别 六 实战演示 北京新发地线程池实战 前言 个人简介 以山河作礼 Python领域新星创作者 CSDN实力新星认证 第一篇文章 1 认识网
  • 2020-09-15

    win10截图 win shift s
  • C++类结构规范定义

    后期私有类肯定还会有很多 为了自己和读者方便阅读 在后面的设计中将严格按照制定格式来定义类的变量和函数 pragma once class CClassxxx public CClassBase DECLARE DYNAMIC CClass
  • Flink SQL中时态表

    前言 Flink 1 12正式发布后 带来了很多新的特性 本文重点学习和总结一下Flink 1 11和 Flink1 12中时态表的使用和自己的一个小总结 文章如有问题 请大家留言交流讨论 我会及时改正 本文主要将在Flink1 12中新的
  • 仿今日头条最强顶部导航指示器,支持6种模式

    项目中经常会用到类似今日头条中顶部的导航指示器 我也经常用一个类似的库PagerSlidingTabStrip 但是有时并不能小伙伴们的所有需求 所以我在这个类的基础上就所有能用到的情况做了一个简单的封装 大家知道做一个功能比较简单 但是封
  • PageHelper 分页排序使用记录

    PageHelper 分页使用 PageHelper startPage pageNum pageSize orderBy 其中最后一个参数是数据库字段名称 按传入的字段进行排序 场景 如果有接口参数中有排序字段 则按参数中的排序字段来排序
  • 用MATLAB修改图像大小

    J1 imread frame 1 png 将图片读进工作区 J2 im2double J1 将默认的uinit数据类型保存为double类型并进行单位化 f imresize J2 2 将J2图片尺寸变为原来的二倍 figure imsh
  • 机器学习——逻辑回归 (Logistic Regression)

    引言 当谈到分类问题时 机器学习中最常用的算法之一就是逻辑回归 Logistic Regression 逻辑回归是一种广义线性模型 用于预测二分类问题 它的优势在于简单易懂 计算效率高 并且通常具有不错的性能 在本文中 我们将介绍逻辑回归的
  • 软件测试面试题:什么是上下文切换?

    1 什么是上下文切换 上下文切换是指CPU从一个任务或线程切换到另一个任务或线程时 保存当前任务的上下文信息并加载新任务的上下文信息的过程 上下文信息包括寄存器状态 程序计数器 堆栈指针等 它们共同组成了一个任务或线程的运行状态 同时 我也
  • 基于深度学习的人脸检测与识别系统设计(python)

    代码在github上 https github com Bluenessdrops face recognition 以上 后续有空了再详细写写过程 有问题请留言
  • vivo手机android耗电快怎么解决,vivo手机耗电严重怎么办 如何解决手机耗电严重的问题...

    相信大家对vivo手机都是不陌生的 他也是我们国产智能手机中的一个品牌 使用的用户也非常的多 并且机型也是一代比一代更好 那么大家平时在使用vivo手机的时候可能都会觉得它的电耗太快了 每天没用多久就需要充电了 所以就有网友问到小编有没有解
  • python通讯录课程设计

    最近自学了python 想到之前学c 的通讯录课程设计 就试着用来检验python的学习成果 import os file name contact txt def menu print 欢迎使用通讯簿 print 菜单 print 1 新
  • 【Chips】跨时钟域的亚稳态处理、为什么要打两拍不是打一拍、为什么打两拍能有效?

    Title 跨时钟域的亚稳态处理 为什么要打两拍不是打一拍 为什么打两拍能有效 前言 个人颜色习惯 黑色加粗 突出显示 红色 重要 洋红色 产生的疑问 question 蓝色 个人思考 或 针对问题的Solution 1 个人疑惑 在学习
  • Spring中原型prototype的准确使用

    实际问题 项目中 报表导出涉及到了在同一个类的两个不同方法中 都有相同的查询数据库的操作 一个方法是用于获取内容 一个是用于获取条数的 大概类似于这样 code class language java hljs has numbering
  • CVPR 2022

    论文 https arxiv org abs 2112 10003 代码 https github com timojl clipseg 语雀文档 https www yuque com lart papers ma3gkwbb5ud1ew
  • 苹果手机如何打开开发者模式

    下载爱思助手 数据线连接苹果手机 点击虚拟定位 修改虚拟定位 打开开发者模式 6 根据提示前往 iPhone 设置 隐私与安全性 可发现 开发者模式 现在已经显示出来 请打开开关并重启设备 7 设备完成重启后 屏幕上会出现询问是否打开 开发
  • cpm(派系过滤算法)实现社区发现

    注意 1 派系过滤CPM方法 clique percolation method 用于发现重叠社区 派系 clique 是任意两点都相连的顶点的集合 即完全子图 2 所有彼此连通的k 派系构成的集合就是一个k 派系社区 其中一个k 派系与另
  • Java高级——内存分配机制

    内存分配 概述 对象优先在Eden分配 大对象直接进入老年代 长期存活的对象将进入老年代 动态对象年龄判定 空间分配担保 概述 对象都是在堆上分配 但实际上也有可能经过即时编译后被拆散为标量类型并间接地在栈上分配 分代设计下 新生对象通常会