JVM内存分配机制详解

2023-05-16

目录

1. 对象创建流程(TODO)

1.1 jvm分配内存 

1.2 设置对象头 

1.2.1 对象头实例

1.2.2 指针压缩

2.JVM对象内存分配 

2.1 逃逸分析和标量替换

 2.1.1 逃逸分析和标量替换实战

 2.2 eden区分配内存过程

2.3 大对象分配进入老年代

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

2.5 动态年龄进入老年代

2.6 老年代空间分配担保机制

2.5 计算生产模拟系统JVM参数设置

3.对象内存回收机制

3.1 finalize方法判断对象是否存活 

 3.2 如何判断一个类是一个无用的类


1. 对象创建流程(TODO)

A a = new A()过程 分为:

1. 类加载检查,如果加载过进行下一步,没有加载过使用类加载器加载类

2. 分配内存

3. 初始化

4. 设置对象头

5. 执行init方法

1.1 jvm分配内存 

jvm分配内存方式:

        1.指针碰撞:将一块内存分配为已使用和未使用,中间分配使用指针分隔,分配内存就是将指针移动到与对象大小相同的内存块处。

        2.空闲列表:一般内存中可能有不相邻的内存块,虚拟机会维护一个列表,标记哪些是使用过的内存和未使用的内存。分配内存的时候会更新这个列表。

jvm解决分配内存并发问题的方法:

        1.CAS+失败重试的方式:cas就是判断是否是否相同,如果相同更新的方式,没有更新成功,还有失败重试机制。

        2.TLAB:将分配内存的动作按照线程划分为在不同空间,每个空间预先分配一段内存。默认使用-XX:+/-UseTLAB参数设定是否开启TLAB,默认jdk1.8开启,使用-XX:TLABSIZE指定TLAB大小

1.2 设置对象头 

 对象在内存中分为对象头,实例数据,对齐填充。

对象头:

        1.mark Word

        2.Klass Pointer指针(堆中对象指向方法区的类信息)

        3.数组长度

1.2.1 对象头实例

打印对象头实战: 

1.引入pom.xml


<!--        引入jol包-->
        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.10</version>
        </dependency>
  
import org.openjdk.jol.info.ClassLayout;

public class Math {

    public static void main(String[] args) {
        //SIZE 前8位markword 中间4位jclass Pointer指针指向方法区类信息 最后4位对齐填充8的整数位
        //总共16字节
        ClassLayout classLayout = ClassLayout.parseInstance(new Object());
        System.out.println(classLayout.toPrintable());

        System.out.println("----------------------");

        //SIZE 前8位markword 中间4位jclass Pointer指针指向方法区类信息 4位数组才有 8位数组大小,1个int占4个字节
        //总共24字节
        ClassLayout classLayout1 = ClassLayout.parseInstance(new int[]{1,2});
        System.out.println(classLayout1.toPrintable());

        System.out.println("----------------------");

        //SIZE 前8位markword 中间4位jclass Pointer指针指向方法区类信息 4位int占4个字节
        //1位 byte 3位对齐整数4填充位 4位string对象占4位 4位Object对象占4位 4位填充位
        //总共32字节
        ClassLayout classLayout2 = ClassLayout.parseInstance(new A());
        System.out.println(classLayout2.toPrintable());

    }
}

class A{
    int a; //占4个字节
    String name; //占4个字节
    byte c; //占1个字节,默认会对齐填充4的整数位,补齐后面3位(alignment/padding gap)
    Object o ;//占4个字节
}

 hostspot源码对象内存分配如下:

 

1.2.2 指针压缩

jdk1.6以后默认开启指针压缩,主要对对象头Kclass Pointer指针进行压缩,以得到压缩内存的目的,帮助减少内存压力。

-XX:-UseCompressedOops(默认开启)

-XX:-UseCompressedOops(关闭指针压缩)

-XX:+UseCompressedClassPointers(默认只开启压缩对象头里的KClass Pointer指针)

为什么要进行指针压缩?

在早期32位操作系统中,内存最多可以占用2的32次方数据,如果使用64位系统,内存可以有2的64次方数据,在进行指针压缩以后将超过2的32次方数据进行压缩可以放入内存,能存储更多数据,在拿到CPU寄存器寻址时会进行解码操作还原数据本来内存大小。

在内存小于4G时,默认不开启指针压缩。

如果内存大于32G,默认使用8字节进行寻址,压缩指针会失效,因为64位操作系统使用32位,内存会多出1.5倍,会导致占用较大带宽,GC承受较大压力,所以互联网一般不采用超过内存32G的服务器。

2.JVM对象内存分配 

2.1 逃逸分析和标量替换

逃逸分析:一个对象在方法参数列表里面,在方法返回值里面返回,就代表这个对象逃逸了。如果只能在某个方法内使用,代表对象没有逃出方法区,jdk7以后默认开启逃逸分析,我们可以使用-XX:-DoEscapeAnalysis关闭逃逸分析。非逃逸的对象在方法区分配内存的时候默认分配内存如果不超过桟帧大小可以放入桟空间中,大大减少了堆内存的压力。

标量替换:在对象确定不能逃逸以后,并且对象还可以分解成成员变量时,jvm就不会创建对象,而是将对象的成员变量由方法使用的成员变量替换,方法使用的成员变量由寄存器或者桟帧中分配内存,这样可以存在不连续的内存空间上。jdk7以后默认开启,可以使用-XX:+EliminateAllocations空间,+号代表开启,-号代表关闭。 

public class EscapeAnalysis {

    /**
     * A对象逃逸出getA方法
     * @return
     */
    public A getA(){
        A a = new A();
        a.setName("逃逸分析");
        return a;
    }

    /**
     * A对象未逃逸出getB方法
     * @return
     */
    public void getB(){
        A a = new A();
        a.setName("逃逸分析");
    }
}

class A{
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

 2.1.1 逃逸分析和标量替换实战

/**
 * 1.默认会开启逃逸分析和标量替换
 * -Xmx15M -Xms15M -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
 * 2.关闭逃逸分析
 * -Xmx15M -Xms15M -XX:-DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
 * 3.关闭标量替换
 * -Xmx15M -Xms15M -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations
 */
public class Test {

    /**
     * A对象未逃逸出getB方法
     * @return
     */
    public static void getB(){
        A a = new A();
        a.setName("逃逸分析");
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for(int i = 0;i < 100000000;i++){
            getB();
        }
        long end = System.currentTimeMillis();
        System.out.println("占用时间:"+(end - start)/1000+"秒");
    }
}

class A{
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

 2.2 eden区分配内存过程

经过测试得知eden区默认63.5M内存,在57.6M时占满Eden区内存,不知道中间为什么会有一部分空间没有占用仍然显示100%的情况。在eden区占满以后往S0区移动,大对象会往老年代放入,后续的对象继续往eden区放入。

/**
 * eden区分配内存
 * -XX:+PrintGCDetails
 */
public class GCTest {

    public static void main(String[] args) {
        byte[] obj1,obj2,obj3,obj4,obj5;
        obj1 = new byte[59000*1024];//eden区默认63.5M内存,在57.6M时占满Eden区内存

        obj2 = new byte[8000*1024];//进行mimor gc将大对象往老年代移动一部分,S0区移动一部分

        obj3 = new byte[8000*1024];//下面三个对象继续往Eden区放入
        obj4 = new byte[8000*1024];
        obj5 = new byte[8000*1024];
    }

}

2.3 大对象分配进入老年代

在使用serial或ParNew垃圾回收器时可以设置,下面使用serial垃圾回收器


-XX:PretenureSizeThreshold=1000000 -XX:+UseSerialGC -XX:+PrintGCDetails参数来让大对象直接进入老年代。  
/**
 * eden区分配内存
 * -XX:+PrintGCDetails 打印GC详细内容
 * -XX:PretenureSizeThreshold=1000000 单位字节 -XX:+UseSerialGC --在使用serial年轻代垃圾回收器时可以设置大对象参数
 * -XX:PretenureSizeThreshold=1000000 -XX:+UseSerialGC -XX:+PrintGCDetails
 * --大对象会直接进入老年代
 */
public class GCTest {

    public static void main(String[] args) {
        byte[] obj1;
        obj1 = new byte[8000*1024];//直接将大对象放入老年代
    }
}

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

正常年轻代中对象的年龄要达到15次就要进入老年代,但是我们可以通过设置参数来让这个年龄提前进入老年代。不同的垃圾回收器年龄可能不同,CMS垃圾回收器默认6岁。

-XX:MaxTenuringThreshold设置回收年龄

2.5 动态年龄进入老年代

在eden占满以后,发生Minor GC,会把对象从eden挪动到s0区,如果对象超过s0区的一半,会直接进入老年代。公式:年龄>=1的对象内存总和大于s0区的50%就会进入老年代。

2.6 老年代空间分配担保机制

 简单说:就是每次再进入minor GC之前会判断是否要进行一次Full GC,这样就可以减少Minor GC,通过设置-XX:-HandlePromotionFailure来实现。

2.5 计算生产模拟系统JVM参数设置

如下计算后的JVM内存参数建议如下:

java -Xms3072M -Xmx3072M -Xss1M -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=512M -jar jar包

如果要设置eden,s0,s1区比例,需要设置-XX:+UseAdaptiveSizePolicy(默认开启),参数会默认开启,动态扩容年轻代比例。如果要关闭使用-XX:+UseAdaptiveSizePolicy参数

3.对象内存回收机制

程序计数器法:jdk默认不使用,当一个对象被其他对象引用程序技术器就会加1,如果对象=null没有引用时减1,但是可能会出现A引用B,B引用A导致对象无法回收的情况。

GC Roots引用计数法:从GC Roots往下引用到其他对象形成一条引用链,当下面没有引用时对象被标记为垃圾,当下一次垃圾回收时会被垃圾回收器回收。常用的GC Roots:本地方法桟中引用的对象,方法区中的常量,静态变量等。

java的4大引用:强,虚,软,弱。

强引用:new 对象。这样的对象即使发生异常也不会被强制回收。

弱引用:new SoftReference<User>new User();应用场景:当一个页面需要回退时会返回到上一个页面,当发生GC时会回收掉这个对象。

虚引用和软引用几乎不用,忽略。

3.1 finalize方法判断对象是否存活 

1.第一次判断是否覆盖了finalize方法没有直接将没有与GC Roots关联的对象直接回收。

2.使用finalize方法可以进行对象的自救,比如将对象赋值给引用链上的某一个类变量或常量。

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * 测试使用finallize方法进行对象的自救
 */
public class User {

    private int id ;
    private String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    protected void finalize() throws Throwable {
        //对象自救方法
//      OOMTest.list.add(this);
        System.out.println("id为:"+id+"的对象即将被回收!");
    }
}

class OOMTest{
    public static List<User> list = new ArrayList<User>();

    public static void main(String[] args) {
        List<User> strings = new ArrayList<User>();
        int i = 0,j = 0;
        while(true){
            strings.add(new User(i++, UUID.randomUUID().toString()));
            new User(j--,UUID.randomUUID().toString());
        }
    }
}

 3.2 如何判断一个类是一个无用的类

1.java中堆和堆指向方法区的指针都被回收

2.类的类加载器被回收掉,应用场景:tomcat中的大量自定义加载器比如webappClassLoader和jspClassLoader都应该被回收掉。

3.java中不存在引用的对象,如果存在还是会通过反射生成相应的类。

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

JVM内存分配机制详解 的相关文章

  • RabbitMq学习笔记(五)—— Topic

    ReceiveTopicOne 匹配规则 span class hljs number 1 span span class hljs comment 声明交换器和队列 span channel exchangeDeclare EXCHANG
  • ViewBinding - Jetpack 视图绑定委托封装及使用示例

    通过视图绑定功能 xff0c 您可以更轻松地编写可与视图交互的代码 在模块中启用视图绑定之后 xff0c 系统会为该模块中的每个 XML 布局文件生成一个绑定类 绑定类的实例包含对在相应布局中具有 ID 的所有视图的直接引用 在大多数情况下
  • LiveData 的生命周期 - viewLifecycleOwner / this

    在给定所有者的生命周期内将给定的观察者添加到观察者列表中 事件在主线程上调度 如果 LiveData 已经有数据集 xff0c 它将被传递给观察者 方法 xff1a observe LifecycleOwner Observer 使用示例
  • 设计模式——生产者消费者模式

    1 基本概括 2 主要介绍 2 1 概念 生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题 生产者和消费者彼此之间不直接通讯 xff0c 而通过阻塞队列来进行通讯 xff0c 所以生产者生产完数据之后不用等待消费者处理 xff
  • Arch linux安装deepin-wine和微信(wechat)

    一 保证有国内的镜像源 xff0c 不然下载很慢 xff0c 没有国内包的镜像源 xff0c 则软件列表会少很多国内的软件 如果没有国内镜像源 xff0c 请修改镜像源 sudo vim etc pacman d mirrorlist s
  • KVM虚拟化(一)

    一 KVM虚拟化架构 1 主流虚拟机架构 图中对比了几种主流虚拟化技术架构 xff1a ESXi Xen与KVM xff0c 其主要差别在与各组件 xff08 CPU 内存 磁盘与网络IO xff09 的虚拟化与调度管理实现组件有所不同 在
  • docker Centos 7 安装 xfce4 桌面 + x11vnc + novnc

    docker Centos 7 安装 xfce4 桌面 43 x11vnc 43 novnc 启动容器环境变量xfce4 桌面安装 启动 Xvfb安装 启动 xfce4 桌面 x11vnc安装 x11vnc启动 x11vnc novnc 上
  • linux C++ 环境下的ActiveMQ学习

    ActiveMQ 1 概述 ActiveMQ 是Apache出品 xff0c 最流行的 功能强大的即时通讯和集成模式的开源服务器 ActiveMQ 是一个完全支持JMS1 1和J2EE 1 4规范的 JMS Provider实现 提供客户端
  • LUbuntu16.10安装及自动使用root登录(Ubuntu类似)

    LUbuntu是一款新的轻量级Ubuntu发行版 xff0c 结合LXDE使得LUbuntu安装 运行速度极快 xff0c 硬件资源要求很低 xff0c 支持X86和ARM架构处理器 可以在http lubuntu net 根据自己电脑的系
  • 【源码分享】-wpf界面源代码分享

    好久以前做过一段时间的界面编程 C 43 43 C Wpf的都做过一点 xff0c 见 个人博客中UI类目 所以资料里面关于界面编程的代码会多一些 xff0c 有时间就整理发出来共享学习 13年的时候用WPF写过一个简单的小程序作爬虫来获取
  • 十大 Python GUI 框架比较

    图形用户界面 GUI 是任何 Web 应用程序中最重要的部分之一 有人会说最重要的部分 xff0c 因为今天的老练用户很快就会放弃可用性差的应用程序 Python 应用程序也是如此 新手 Python 程序员在掌握了 Python 编程的基
  • 如何用ps将图片修改成指定大小

    这篇文章主要介绍如何用ps将图片修改成指定大小 xff1a 一 按原像素比例 修改图片的尺寸 1 先来看看它原来的尺寸 xff0c 如图所示 500X481px 2 首先用ps将图片文件打开 xff0c 使用ps菜单命令 xff1a lt
  • docker 安装rabbitMQ(最详细)

    docker 安装rabbitMQ 1 rabbitMQ介绍 RabbitMQ是一套开源 xff08 MPL xff09 的消息队列服务软件 xff0c 是由 LShift 提供的一个 Advanced Message Queuing Pr
  • 阿里云-ECS云服务器跨地域部署k8s集群

    阿里云 ECS云服务器跨地域部署k8s集群 一 背景介绍二 环境准备2 1 ECS云服务资源清单2 2 K8s软件列表 三 阿里云ECS服务器网络问题3 1 问题阐述3 2 解决方案 四 服务节点调整 xff08 master xff0c
  • 代码命名规范是一种责任也是一种精神(工匠精神)

    代码命名规范之美 规范概述命名规范管理类命名BootstrapProcessorManagerHolderFactoryProviderRegistrarEngineServiceTask 传播类命名ContextPropagator 回调
  • Kubernetes集群搭建(高可用)附全自动脚本

    高可用Kubernetes集群搭建 一 前言介绍 1 1 重要概念 二 系统架构 2 1 架构基本需求 2 2 架构图 三 环境准备 3 1 云服务或虚拟机清单 3 2 网络配置 针对使用vmware虚拟机用户需要操作 3 3 虚拟机网络设
  • k8s 集群部署ingress-nginx

    k8s 集群部署ingress nginx 环境准备 helm方式安装ingress nginx master边缘节点 创建命名空间和部署ingress 第一个ingress 例子 查看ingress controller配置 其实就是对应
  • k8s 集群部署traefik

    k8s 集群部署traefik k8s 集群部署traefik 环境准备 下载traefik helm方式安装traefik master边缘节点 执行安装 查看安装结果 安装traefik dashboard dashboard安装和查看
  • Linux系统(Centos)安装tomcat和部署Web项目

    文章结构 1 准备工作 2 在Linux下安装Tomcat8 0 3 Linux中配置tomcat的服务器启动和关闭和配置tomcat的开机启动 4 给tomcat配置用户名和密码登录 5 使用IDEA打包Maven托管的WEB项目 6 将
  • 如何判断蓝牙设备类型

    我们在开发Android的蓝牙应用时 xff0c 可能需要知道扫描到的蓝牙设备是什么类型 xff0c 然后过滤掉不符合要求的设备 xff0c 只保留符合要求的设备 xff0c 例如我们在车载系统上开发蓝牙电话应用时 xff0c 我们希望只显

随机推荐

  • Aspose.Words for Java 体验

    公司中要做一些导出word的工作 xff0c 经别人推荐 xff0c 使用了Aspose Words for Java xff0c 感觉很好用 xff0c 美中不足的地方就是 xff0c 它是收费软件 原理吗 xff1f 比较常规 xff0
  • 汽车制造行业OEM Tier1 Tier2指代什么?

    OEM OEM是Original Equipment Manufacturer的缩写 xff0c 通常指设备厂商 主机厂 整车厂 例如 xff1a 宝马 奔驰 奥迪 大众 丰田 国内汽车主机厂排名前十分别是上汽集团 东风汽车 北京汽车 长安
  • Android应用系统签名方法

    1 应用配置sharedUserId 在AndroidManifest xml文件根节点中加入属性 xff1a android sharedUserId 61 34 android uid system 34 2 找到系统签名文件 plat
  • Android 读取CPU/GPU运行参数(MTK平台)

    一 使用场景 Android运行一段世时间后 xff0c 系统出现卡顿 二 分析 amp 定位问题 系统卡顿 xff0c 同时又发现芯片温度很高 xff0c 怀疑是温度过高导致CPU降频 xff0c 因此我们要将一段时间内CPU的运行信息打
  • Android签名 (二) 制作签名文件

    你可能想知道 通过这篇博客可以解决哪些问题以及学到什么 xff1a 1 公司开发一个新的app xff0c 如何创建一个应用签名 xff1f 2 为了安全性 xff0c Android系统不想使用Google给的原生签名 xff0c 如何定
  • Android签名 (一) 查看签名信息

    你可能想知道 通过这篇博客可以解决哪些问题以及学到什么 xff1a 1 如果我们有一个应用 xff0c 如何查看应用的签名信息 xff1f 2 如果我们有签名原始文件 xff0c 如何查看签名文件中的签名信息 xff1f 这篇博客介绍了如何
  • L8 U3 职业生涯

    Module 1 谈论你最近的工作 1 描述工作 描述工作的形容词 让我们来看一下可以用来描述工作 xff0c 项目和客户的形容词 用enjoyable 和 engaging来描述你觉得有意思的工作或者项目 For me sales is
  • L8 U4 商务旅行

    Module 1 计划商务旅行 1 处理信息 You mean with Lindstrom 你是说和林特罗姆 xff1f Yes with Lindstrom 是的 xff0c 和林特罗姆 Exactly 完全正确 You mean yo
  • L8 U5 产品和创新

    Module 1 产品特点 1 产品特点 询问产品特性 在询问商店中的产品时 xff0c 您可能首先想询问其质量水平 top of the range 高档的middle of the range 中档的good value for mon
  • Android打包jar的两种方法

    方法一 xff1a 使用Android Studio打包 方法二 xff1a 使用Android Build System打包 一 Android Studio打包 1 新建Android Library xff0c 取名为opensdk
  • LCS 下载插件

    难度简单2收藏分享切换为英文接收动态反馈 小扣打算给自己的 VS code 安装使用插件 xff0c 初始状态下带宽每分钟可以完成 1 个插件的下载 假定每分钟选择以下两种策略之一 使用当前带宽下载插件将带宽加倍 xff08 下载插件数量随
  • 调用webservice异常总结

    发布和调用Webservice很简单 xff0c 但小问题依然不断 xff0c 特总结如下 xff1a 一 Java调用 net的webService产生 服务器未能识别 HTTP 标头 SOAPAction 的值 错误 解决方案 xff1
  • 去除Evaluation Warning : The document was created with Spire.PDF for Java.

    去除Evaluation Warning The document was created with Spire PDF for Java 最近项目中有一个需求需要把PDF文件添加页码 xff0c 最终在网上找到了用Spire去添加的方法
  • 第二章 简单网页的爬取与Xpath、Json使用 2021-09-09

    爬虫系列总目录 本章节介绍爬虫中使用的基础库用于选择 xff0c 过滤页面信息 包括requests xff0c bs4 xff0c xpath xff0c 正则表达式re xff0c json 等内容 xff0c 能够实现对简单页面的获取
  • 解决crontab定时任务多次执行

    今天使用linux crontab定时任务时 xff0c 可能由于配置不妥 xff0c 任务多执行一次 xff0c 如下是我程序的日志记录 xff08 执行了两次 xff0c 我设置的是每分钟执行1次 xff09 解决如下 重启cronta
  • ruoyi对接CAS统一身份认证

    暂定逻辑如下 xff1a 搭建CAS服务器端 xff1a 项目地址 xff1a https gitee com weigang wu cas server webapp git 项目里有二开的说明文档 xff0c 如 xff1a 按照自定义
  • Linux 系统启动过程

    简介 Linux 系统启动过程大致分为5个阶段 xff1a 1 内核的引导 2 运行 init 3 系统初始化 4 建立终端 5 用户登录系统 内核引导 计算机通电后 xff0c 首先会进行 BIOS 开机自检 xff0c 然后根据BIOS
  • Ubuntu 修改$PS1 自定义命令提示符

    文章更新于 xff1a 2020 03 25 文章目录 一 自定义命令提示符1 可修改的是那部分 xff1f 2 修改 PS1 变量3 PS1 变量格式4 如何修改背景颜色5 修改字体 二 Enjoy xff01 一 自定义命令提示符 1
  • C#byte类型

    byte类型的范围是0 255 转换为二进制是00000000 11111111 C 中对byte类型的处理还是很特殊的 下面用几行简单的代码来说明问题 byte x 61 1 byte y 61 2 byte z 61 x 43 y er
  • JVM内存分配机制详解

    目录 1 对象创建流程 TODO 1 1 jvm分配内存 1 2 设置对象头 1 2 1 对象头实例 1 2 2 指针压缩 2 JVM对象内存分配 2 1 逃逸分析和标量替换 2 1 1 逃逸分析和标量替换实战 2 2 eden区分配内存过