Java运行时内存管理

news2024/12/23 5:43:57

一、前言

希望能在我们平时开发写代码的时候,能够知道当前写的这段代码,内存方面是如何分配的。
我们深知,一个Java程序员在很多时候根本不用操心内存的释放,而是依靠JVM去管理,以前写C++代码的时候,却要时刻记着new的空间要及时释放掉,不然程序很容易出现内存溢出的情况。因为,Java在这方面确实方便了许多,让我们有更多精力去考虑业务方面的实现。但是,这并不意味着我们就能肆无忌惮的使用内存,因为:

1.JVM并不会及时的去清理内存
2.我们无法通过代码去控制JVM去清理内存

这就要求我们平时在开发过程中,要了解JVM的垃圾回收机制,合理安排内存。那么怎么样才能合理安排内存呢?那么就需要我们了解JVM的内存分配机制,而后才能真正控制好,让程序运行在我们鼓掌之中。

二、JVM内存模型

平时我们对于Java内存都有一个比较粗略的概念,就是分堆和栈,但实际上还是复杂得多,以下给出完整内存模型:
在这里插入图片描述
相对应区域的内容为:
在这里插入图片描述

在这里插入图片描述

程序计数器PC

这一个区域我概括了以下几个要点:

1.这一区域不会出现OOM(Out Of Memory)错误的情况
2.属于线程私有,因为每一个线程都有自己的一个程序计数器,来表示当前线程执行的字节码行号
3.标识Java方法的字节码地址,而不是Native方法
4.处于CPU上,我们无法直接操作这块区域

虚拟机栈

这个区域也是我们平时口中说的堆栈的栈,关于这个块区域有如下要点:

1.属于线程私有,与线程的生命周期相同
2.每一个java方法被执行的时候,这个区域会生成一个栈帧
3.栈帧中存放的局部变量有8种基本数据类型,以及引用类型(对象的内存地址)
4.java方法的运行过程就是栈帧在虚拟机栈中入栈和出栈的过程
5.当线程请求的栈的深度超出了虚拟机栈允许的深度时,会抛出StackOverFlow的错误
6.当Java虚拟机动态扩展到无法申请足够内存时会抛出OutOfMemory的错误

本地方法栈

这个区域,属于线程私有,顾名思义,区别于虚拟机栈,这里是用来处理Native方法(Java本地方法)的,而虚拟机栈是处理Java方法的。对于Native方法,Object中就有不少的Native的方法,hashCode,wait等,这些方法的执行很多时候都是借助于操作系统。

这一区域也有可能抛出StackOverFlowError 和 OutOfMemoryError

Java堆

我们平时说得最多,关注得最多的一个区域,就是他了。我们后期进行的性能优化主要针对这部分内存,GC的主战场,这个地方存放的几乎所有的对象实例和数组数据。而在虚拟机栈中分配的只是引用,这些引用会指向堆中真正存储的对象。这里我大概进行了如下概括:

1.Java堆属于线程共享区域,所有的线程共享这一块内存区域
2.从内存回收角度,Java堆可被分为新生代和老年代,这样分能够更快的回收内存
3.从内存分配角度,Java堆可划分出线程私有的分配缓存区(Thread Local Allocation Buffer,TLAB),这样能够更快的分配内存
4.当Java虚拟机动态扩展到无法申请足够内存时会抛出OutOfMemory的错误

方法区

方法区主要存放的是已被虚拟机加载的类信息、常量、静态变量、编译器编译后的代码等数据。GC在该区域出现的比较少。概括如下:

1.方法区属于线程共享区域
2.习惯性叫他永久代
3.垃圾回收很少光顾这个区域,不过也是需要回收的,主要针对常量池回收,类型卸载
4.常量池用于存放编译期生成的各种字节码和符号引用,常量池具有一定的动态性, 里面可以存放编译期生成的常量
5.运行期间的常量也可以添加进入常量池中,比如string的intern()方法。

运行时常量池

运行时常量池也是方法区的一部分,用于存放编译器生成的各种字面量和符号引用。单独拿出来说明一下,是因为我们平时使用String比价多,涉及到这一块的知识,但这一块区域不会抛出OutOfMemoryError

三、垃圾标记算法

垃圾收集器(Garbage Collection),通常被称作GC。GC主要做了两个工作:

  • 内存的划分和分配,
  • 对垃圾进行回收。

关于内存的划分和分配,目前Java虚拟机内存的划分是依赖于GC设计的,比如现在GC都是采用了分代收集算法来回收垃圾的,Java堆作为GC主要管理的区域,被细分为新生代和老年代,再细致一点新生代又可以划分为Eden空间、From Survivor空间、To Survivor空间等,这样划分是为了更快地进行内存分配和回收。空间划分后,GC就可以为新对象分配内存空间。关于对垃圾进行回收,被引用的对象是存活的对象,而不被引用的对象是死亡的对象(也就是垃圾),GC要区分出存活的对象和死亡的对象(也就是垃圾标记),并对垃圾进行回收。

在对垃圾进行回收前,GC要先标记出垃圾,那么如何标记呢?目前有两种垃圾标记算法:

  • 引用计数算法
  • 根搜索算法

1、引用计数算法:

​ 引用计数算法的基本思想就是每个对象都有一个引用计数器,当对象在某处被引用的时候,它的引用计数器就加1,引用失效时就减1。当引用计数器中的值变为0,则该对象就不能被使用,变成了垃圾。

​ 目前主流的Java虚拟机没有选择引用计数算法来为垃圾标记,主要原因是引用计数算法没有解决对象之间相互循环引用的问题。举个例子,在下面代码的注释1和注释2处,tom和mike相互引用,除此之外这两个对象无任何其他引用,实际上这两个对象已经死亡,应该作为垃圾被回收,但是由于这两个对象互相引用,引用计数就不会为0,如果Java虚拟机采用了引用计数算法,垃圾收集器就无法回收它们。

class Student {
    Student friend;
}
//
Student s1 = new Student();
Student s2 = new Student();
s1.friend = s2;// 1
s2.friend = s1;// 2

s1 = null;
s2 = null;

优点
引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。

缺点
无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.

2、根搜索算法

​ 这个算法的基本思想就是选定一些对象作为GC Roots,并组成根对象集合,然后以这些GCRoots的对象作为起始点,向下搜索,如果目标对象到GC Roots是连接着的,我们则称该目标对象是可达的,如果目标对象不可达则说明目标对象是可以被回收的对象,如图下图所示。

在这里插入图片描述

从上图可以看出,ObjF、ObjD和ObjE都是不可达的对象,其中ObjD和ObjE虽然互相引用,但是因为它们到GC Roots是不可达的,所以它们仍旧被判定为可回收的对象,这样根搜索算法就解决了引用计数算法无法解决的问题:已经死亡的对象因为相互引用而不能被回收。

四、JVM内存源码示例说明

package senduo.com.memory.allocate;

/**
 * *****************************************************************
 * * 文件作者:ouyangshengduo
 * * 创建时间:2017/8/11
 * * 文件描述:内存分配调用过程演示代码
 * * 修改历史:2017/8/11 9:39*************************************
 **/
public class MemoryAllocateDemo {
    public static void main(String[] args){ //JVM自动寻找main方法
        /**
         * 执行第一句代码,创建一个Test实例test,在栈中分配一块内存,存放一个指向堆区实例对象的指针
         */
        Test test = new Test();
        
        /**
         * 执行第二句代码,声明定义一个int型变量(8种基本数据类型),在栈区直接分配一块内存存储这个变量的值
         */
        int date = 9;
        
        /**
         * 执行第三句代码,创建一个BirthDate实例bd1,在栈中分配一块内存,存放一个指向堆区实例对象的指针
         */
        BirthDate bd1 = new BirthDate(13,6,1991);
        
        /**
         * 执行第四句代码,创建一个BirthDate实例bd2,在栈中分配一块内存,存放一个指向堆区实例对象的指针
         */
        BirthDate bd2 = new BirthDate(30,4,1991);
        
        /**
         * 执行第五句代码,方法test1入栈帧,执行完出栈
         */
        test.test1(date);
        
        /**
         * 执行第六句代码,方法test2入栈帧,执行完出栈
         */
        test.test2(bd1);
        
        /**
         * 执行第七句代码,方法test3入栈帧,执行完出栈
         */
        test.test3(bd2);

    }
}

参考

  • Android 虚拟机、对象、变量的内存分配

  • Android性能调优篇之探索JVM内存分配

  • Android 从内存模型深究内存优化本质

  • 深入理解Java虚拟机——Java内存区域

  • android studio profiler 内存分析用法【官网】

  • 内存优化 · 基础论 · 初识Android内存优化

  • Android内存优化深入解析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/448735.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SAP 自定义生产订单状态

1、生产订单通常系统有一整套订单状态,做PP的各位同学都应该知道。 CRTD状态 REL已下达 CNF已报工 DLV已入库 TECO技术性完成 等等状态这里就不在罗列了,可以自行在生产订单中看到 2、这篇文章主要是在生产订单系统外,在自定义一套状态。这个…

Spring更简单的读取和存储Bean(基于注解)

目录 ①从Maven中央仓库获取spring-context和spring-beans的依赖,将依赖引入到pom.xml中 ②配置扫描路径 ③添加注解存储Bean对象(可以使用5大类注解和方法注解) 类注解(写在类上,作用于类上) Contro…

【致敬未来的攻城狮计划】— 连续打卡第十一天:FSP固件库开发点亮第一个灯。

系列文章目录 1.连续打卡第一天:提前对CPK_RA2E1是瑞萨RA系列开发板的初体验,了解一下 2.开发环境的选择和调试(从零开始,加油) 3.欲速则不达,今天是对RA2E1 基础知识的补充学习。 4.e2 studio 使用教程 5.…

leetcode刷题--辅助工具

idea插件 插件商店搜索leetcode,可以让你利用idea调试leetcode的题目 插件首先需要填写用户名密码登录,登录上就可以在idea搜题、做题、提交等 注意: 一些版本登录可能登录失败,解决方法是换leetcode地址为leetcode.cn。 有些可…

通过用户名密码认证保障 MQTT 接入安全

认证是一种安全措施,用于识别用户并验证他们是否有权访问系统或服务器。它能够保护系统免受未经授权的访问,确保只有经过验证的用户才能使用系统。 物联网连接万物,对试图访问基础设施的用户进行认证至关重要。未经授权的访问存在重大的安全…

数据保管库的数据质量错误

数据保管库的数据质量错误 在过去的几年里,数据仓库发生了巨大的变化,但这并不意味着支撑健全数据架构的基本原理需要被抛在窗外。事实上,随着GDPR等数据法规的日益严格以及对优化技术成本的重新重视,我们现在看到了“Data Vault…

设计模式之备忘录模式(C++)

作者:翟天保Steven 版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处 一、备忘录模式是什么? 备忘录模式是一种行为型的软件设计模式,在不破坏封装的前提下,获取一个…

OpenCV实例(八)行人跟踪

OpenCV实例(八)行人跟踪 1.目标跟踪概述2.基于背景差分检测运动物体2.1 实现基本背景差分器2.2 使用MOG背景差分器2.3 使用卡尔曼滤波器寻找运动趋势 3.跟踪行人 作者:Xiou 1.目标跟踪概述 目标跟踪是对摄像头视频中的移动目标进行定位的过…

数据结构与算法基础-学习-20-查找之散列表(HASH TABLE)

目录 目录 一、基本思想 二、术语 1、散列方法 2、散列函数 3、散列表 4、冲突 5、同义词 三、如何减少哈希冲突 四、构造散列函数需考虑的情况 五、散列函数的构造方法 1、直接定址法 2、除留余数法 六、如何处理哈希冲突 1、开地址法 2、拉链法 七、散列表查…

【微服务笔记16】微服务组件之Gateway服务网关基础环境搭建、高可用网关环境搭建

这篇文章,主要介绍微服务组件之Gateway服务网关基础环境搭建、高可用网关环境搭建。 目录 一、Gateway服务网关 1.1、什么是Gateway 1.2、Gateway基础环境搭建 (1)基础环境介绍 (2)引入依赖 (3&#…

快速上手Navicat~

众所周知, Navicat是一款轻量级的用于MySQL连接和管理的工具,非常好用,使用起来方便快捷,简洁。下面我会简单的讲一下其安装以及使用的方法。并且会附带相关的永久安装教程。 简介 一般我们在开发过程中是离不开数据库的&#xf…

【Unity VR开发】结合VRTK4.0:添加对象追随器

语录: 我已经准备好了足够挡雨的伞,可是却迟迟没有等到雨的到来,这样的尴尬只是我漫长人生中的小插曲罢了。 前言: 对象追随器的目的是让一个或多个游戏对象跟随场景中的另一个对象,而无需将游戏对象嵌套在彼此之下。 …

『pyqt5 从0基础开始项目实战』13. 打包生成exe(保姆级图文)

目录 项目源码打包exe打开闪退需要db文件夹总结 欢迎关注 『pyqt5 从0基础开始项目实战』 专栏,持续更新中 欢迎关注 『pyqt5 从0基础开始项目实战』 专栏,持续更新中 项目源码 请查阅专栏上文获取源码 ## 安装库包 python pip install pyinstaller ![…

Stable Diffusion的原理

CSDN-markdown语法之怎样使用LaTeX语法编写数学公式 参考视频:【diffusion】扩散模型详解!原理代码! 用一颗桃树为你讲清楚 知识点:AI绘图原理 Diffusion扩散模型 Windows深度学习环境搭建:Windows深度学习环境搭建 …

FFmpeg开发笔记(三)FFmpeg的可执行程序介绍

外界对于FFmpeg主要有两种使用途径,一种是在命令行运行FFmpeg的可执行程序,该方式适合没什么特殊要求的普通场景;另一种是通过代码调用FFmpeg的动态链接库,由于开发者可以在C代码中编排个性化的逻辑,因此该方式适合厂商…

一篇文章介绍分布式事务

1、事务的基本概念 事务 事务指的就是一个操作单元,在这个操作单元中的所有操作最终要保持一致的行为,要么所有操作都成功,要么所有的操作都被撤销。简单地说,事务提供一种“要么什么都不做,要么做全套”机制。 本地…

【越早知道越好】的道理——能够大大提升效率的【快捷键】

文章目录 1️⃣虚拟桌面第一步:打开任务视图第二步:创建桌面第三步:桌面切换第四步:桌面删除 2️⃣窗口切换3️⃣桌面分屏如何分屏 前言🧑‍🎤:作为程序员👨‍💻&#xf…

scratch足球射门练习 中国电子学会图形化编程 少儿编程 scratch编程等级考试一级真题和答案解析2023年3月

目录 scratch足球射门练习 一、题目要求 1、准备工作 2、功能实现 二、案例分析

基于Java+SpringBoot+Vue前后端分离仓库管理系统设计实现

基于JavaSpringBootVue前后端分离仓库管理系统设计实现 博主介绍:5年java开发经验,专注Java开发、定制、远程、指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式…

RocketMQ 5.0 时代,6 张图带你理解 Proxy!

大家好,我是君哥。今天来聊一聊 RocketMQ 5.0 中的 Proxy。 RocketMQ 5.0 为了更好地拥抱云原生,引入了无状态的 Proxy 模块,新的架构图如下: 引入 Proxy 模块后,Proxy 承担了协议适配、权限管理、消息管理等计算功能…