Java内存区域与内存分配策略

news2024/9/22 4:17:11

java很聪明,它将手动改为自动,把内存的控制权交给了虚拟机,下面我们就来探究一下JVM是怎么进行自动内存管理的。

å¨è¿éæå¥å¾çæè¿°

手动内存管理分为两部分:给对象分配内存和回收分配给对象的内存。

一、运行时数据区域


线程公有

在运行时数据区中,方法区和堆是属于线程公有的,也就是两块区域是循环利用的,所以要对其进行垃圾回收。

线程私有

程序计数器、虚拟机栈、本地方法栈是属于线程私有的,与其线程“同生共死”,属于一次性的,不需要进行垃圾回收。

1、程序计数器

程序计数器中存放的是当前线程所执行的字节码的行号。jvm工作时,就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。

2、Java虚拟机栈

Java虚拟机栈是线程私有的,它的声明周期与线程相同。

虚拟机栈里面存储的是栈帧,栈帧里面存储的是局部变量表,操作数栈,动态链接,方法出口等信息。

  • 栈中的栈帧

每个方法从调用到执行的过程就是一个栈帧在虚拟机栈中入栈到出栈的过程。

  • 栈帧中的局部变量表

存放的是编译期可知的各种基本数据类型,对象引用类型。所以其所需要的内存空间在编译期间就能完成分配,在运行期间不会改变其大小。

在分配基本数据类型所占的空间时,除了64位的long和double类型的数据会占用2个局部变量空间,其余的数据类型只占用1个。

3、本地方法栈

本地方法栈和虚拟机栈的作用是相同的,只不过虚拟机栈执行的是java方法,本地方法栈执行的是Native方法。

java方法就是开发人员写的java代码,Native方法就是一个java调用非java代码的接口。

4、Java堆

如果说栈解决的是程序运行问题,即程序如何处理数据;则堆解决的是数据存储问题,即数据怎么放,放在哪。

此内存区域的唯一目的是存放对象实例,Java堆是垃圾收集器管理的主要区域。

特定:堆是虚拟机内存中最大的一块,大概占内存的三分之二,堆可处于物理上不连续的内存空间中,只要逻辑上是连续的即可。

5、方法区

方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的信息、常量、静态变量、即时编译器编译后的代码等数据。方法区也可以看作是Java堆的一部分。

这部分区域可以不选择垃圾回收,这区域的内存回收主要针对常量池的回收和对类型的卸载。

这部分可能会导致未完全回收而导致内存泄漏。

二、java8内存模型-永久代(PermGen)和元空间(Metaspace)


1、PermGen(永久代)

绝大部分程序员都见过"java.lang.OutOfMemoryError: PermGen space "这个异常。这里“PermGen space”其实指的就是方法区。不过方法区和“PermGen space”又有着本质的区别。前者是JVM规范,而后者是JVM规范的一种实现,并且只有HotSpot才有“PermGen space”,而对于其他类型的虚拟机,如 JRockit(Oracle)、J9(IBM) 并没有“PermGen space”。由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。最典型的场景就是,在jsp页面比较多的情况,容易出现永久代内存溢出。我们现在通过动态生成类来模拟“PermGen space”的内存溢出:

package com.paddx.test.memory;

import java.io.File;

import java.net.URL;

import java.net.URLClassLoader;

import java.util.ArrayList;

import java.util.List;

public class PermGenOomMock{

public static void main(String[] args) {

URL url = null;

List classLoaderList = new ArrayList();

try {

url = new File(“/tmp”).toURI().toURL();

URL[] urls = {url};

while (true){

ClassLoader loader = new URLClassLoader(urls);

classLoaderList.add(loader);

loader.loadClass(“com.paddx.test.memory.Test”);

}

} catch (Exception e) {

e.printStackTrace();

}

}

}

运行结果如下:

本例中使用的 JDK 版本是 1.7,指定的 PermGen 区的大小为 8M。通过每次生成不同URLClassLoader对象来加载Test类,从而生成不同的类对象,这样就能看到我们熟悉的 "java.lang.OutOfMemoryError: PermGen space " 异常了。这里之所以采用 JDK 1.7,是因为在 JDK 1.8 中, HotSpot 已经没有 “PermGen space”这个区间了,取而代之是一个叫做 Metaspace(元空间) 的东西。下面我们就来看看 Metaspace 与 PermGen space 的区别。

2、Metaspace(元空间)

其实,移除永久代的工作从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。我们可以通过一段程序来比较 JDK 1.6 与 JDK 1.7及 JDK 1.8 的区别,以字符串常量为例:

package com.paddx.test.memory;

import java.util.ArrayList;

import java.util.List;

public class StringOomMock {

static String base = “string”;

public static void main(String[] args) {

List list = new ArrayList();

for (int i=0;i< Integer.MAX_VALUE;i++){

String str = base + base;

base = str;

list.add(str.intern());

}

}

}

这段程序以2的指数级不断的生成新的字符串,这样可以比较快速的消耗内存。我们通过 JDK 1.6、JDK 1.7 和 JDK 1.8 分别运行:

JDK 1.6 的运行结果:

è¿éåå¾çæè¿°

JDK 1.7的运行结果:

è¿éåå¾çæè¿°

JDK 1.8的运行结果:

è¿éåå¾çæè¿°

从上述结果可以看出,JDK 1.6下,会出现“PermGen Space”的内存溢出,而在 JDK 1.7和 JDK 1.8 中,会出现堆内存溢出,并且 JDK 1.8中 PermSize 和 MaxPermGen 已经无效。因此,可以大致验证 JDK 1.7 和 1.8 将字符串常量由永久代转移到堆中,并且 JDK 1.8 中已经不存在永久代的结论。现在我们看看元空间到底是一个什么东西?

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:

-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。

-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。

除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:

-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集

-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集

现在我们在 JDK 8下重新运行一下代码段 4,不过这次不再指定 PermSize 和 MaxPermSize。而是指定 MetaSpaceSize 和 MaxMetaSpaceSize的大小。输出结果如下:

è¿éåå¾çæè¿°

从输出结果,我们可以看出,这次不再出现永久代溢出,而是出现了元空间的溢出。

第三章 垃圾收集器与内存分配策略

====================

一、内存分配


这部分我们说一下对象在java堆中是如何分配、布局、访问以及内存分配的原则。

1、对象的创建

我们用new来创建对象,来看看系统运行到new时,虚拟机在干什么。此时的类就像一块肉,他要经过层层安检,才能到达人类的饭桌。

(1)查看在常量池中是否有对应的符号引用。【在方法区中进行】

(2)查看此类是否被加载、解析和初始化过。【在方法区中进行】

(3)领取新生对象的内存。有两种方式:指针碰撞和空闲列表。【在堆中进行】

(4)将分配到的内存空间初始化为零。

(5)对对象进行必要的设置,比如其实哪个类的实例,对象的哈希码之类的。这些信息存放在对象的对象头中。

(6)如果java代码对对象进行了赋值,则会走到第六步,执行方法。此方法的作用就是对对象进行初始化。

2、对象的内存布局

对象在内存中的存储布局分为三个部分:对象头+实例数据+对其补充

  • 对象头

对象头里面有两部分信息:

(1)运行时数据,包括哈希码、GC分代年龄、锁状态标志灯。

(2)类型指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

  • 实例数据

实例数据中存放的是代码中定义的各种类型的字段内容。

  • 对其填充

对齐填充起的是占位符的作用,不是必然存在的,其只要保证对象的大小是8字节的整数倍即可。

3、对象的访问定位

建立完对象后,我们就可以使用对象了。通过句柄和直接指针两种方式。

  • 句柄

句柄访问就是在java堆中划分出一块内存区域作为句柄池,句柄中包含了实例数据和类型数据各自具体的地址信息。

  • 直接指针

直接指针之所以“直接”,是因为它去除了句柄这个中介。所以在速度上比句柄快。

在HotSpot虚拟机中,使用的是这种方式。

说完了对象在java堆中是如何分配,布局和访问的,接下来我们说说内存分配的原则。

4、内存分配的原则

å¨è¿éæå¥å¾çæè¿°

堆大致分为新生代,老年代,永久代。对象的内存分配主要分配在新生代的Eden区,少数情况下会直接分配到老年代中。分配的规则不是100%固定的,取决于垃圾收集器组合和参数设置等。下面有几条分配原则可供参考。

  1. 对象优先在Eden分配
  1. 大对象直接进入老年代
  1. 长期存活的对象将进入老年代
  1. 动态对象年龄判定
  1. 空间分配担保

二、垃圾回收机制


英文名儿是GC(Garbage Collection)。

å¨è¿éæå¥å¾çæè¿°

1、哪些内存需要回收?

堆和方法区中的内存需要回收,其它的不用回收。

因为只有堆和方法区是线程共享的,其余的是与线程“同生共死”的,线程结束,内存自然就跟着回收了,所以不用管它们。

2、什么时候回收?

(1)在堆里面:

当对象“死了”的时候就要对其进行内存回收了。啥叫对象死了?就是没有地方引用它了,它无用了。那怎么判断它是否死了呢?有两种方法。

  • 引用计数算法

给对象添加一个引用计数器,每当有一个地方引用它时,计数器的值就+1,当引用失效时,计数器的值就-1,当计数器的值为0时,代表此对象已不被引用,也就是“可以死了”。

但这有一个弊端,就是循环引用的问题。就像下图,堆里的两个对象即使无用了也没办法对其进行回收,因为它们互相引用着,计数器的值至少为1。

å¨è¿éæå¥å¾çæè¿°

  • 可达性分析

所有生成的对象都是一个成为“GC Roots”的根的子树。从GC Roots开始向下搜索,搜索所经过的路径成为引用链。当一个对象到GC Roots没有任何引用链可以到达时,就称这个对象是不可达的,也就是可以被GC回收了。这个是java中采用较多的方式。

就像下图中的堆中未被引用的对象,就可以对其进行回收了。

å¨è¿éæå¥å¾çæè¿°

怎么判断一个对象是否还存在着引用?java中的引用分为4种:

  • 强引用:Object object = new Object(),只要强引用存在,GC永远不会回收掉被引用的对象。

  • 软引用:描述一些还有用但非必需的对象,当系统即将发生内存溢出时,就会将其进行回收。

  • 弱引用:只要进行GC,就会对其进行回收。

  • 虚引用:这是最弱的一种引用关系,无法通过虚引用来取得一个对象实例。它的作用是:能在这个对象被收集器回收时收到一个系统通知。

(2)在方法区里面:

我们知道,方法区里存储的是已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。所以我们在方法区里面进行垃圾回收,回收的是一些废弃的常量和无用的类。

  • 怎么判断一个常量是否被废弃了?
    /img-blog.csdnimg.cn/20190619110132365.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FzYmJs,size_16,color_FFFFFF,t_70)

怎么判断一个对象是否还存在着引用?java中的引用分为4种:

  • 强引用:Object object = new Object(),只要强引用存在,GC永远不会回收掉被引用的对象。

  • 软引用:描述一些还有用但非必需的对象,当系统即将发生内存溢出时,就会将其进行回收。

  • 弱引用:只要进行GC,就会对其进行回收。

  • 虚引用:这是最弱的一种引用关系,无法通过虚引用来取得一个对象实例。它的作用是:能在这个对象被收集器回收时收到一个系统通知。

(2)在方法区里面:

我们知道,方法区里存储的是已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。所以我们在方法区里面进行垃圾回收,回收的是一些废弃的常量和无用的类。

  • 怎么判断一个常量是否被废弃了?

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

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

相关文章

基础入门 - SpringBoot 底层注解

目录 1、SpringBoot特点 1.1、依赖管理 1.2、自动配置 2、容器功能 2.1、组件添加 1、Configuration Spring Boot 在底层 Configuration 的两个配置 2、Import 3、Conditional 2.2、原生配置文件引入 1、ImportResource 2.3、配置绑定 1、ConfigurationProperties …

前端开发踩坑笔记(2022-11)

文章目录1、Mac上SourceTree更新已删除的远端分支和tag2、echarts x轴文字显示不全&#xff08;解决方案&#xff09;3、如何渲染多行多列的表格&#xff08;非固定的行数和列数&#xff09;4、umy-ui标题过长或内容过长时的处理5、dateRange的时间选择只能选择一个周6、如何将…

Servlet API 详解

目录 一、HttpServlet ① init() 方法 ② service() 方法 ③ destroy() 方法 ④ doGet()方法 ⑤ doPost()方法 ⑥ doPut/deDelete/doOptions 常见面试题&#xff1a; 请你谈谈Servlet的生命周期 二、Http请求&#xff1a;HttpServletRequest 1. 获取请求行信息 2. 获…

一个redux使用案例模板

目录 redux 纯函数和高阶函数&#xff1a; redux 开发工具使用 react-redux redux 1. 结构&#xff1a; count--index.jsx import React, { Component } from react import store from ../../redux/store import { acDecrement,acIncrement,acAsyncIncrement } from ../..…

不会向上管理的人,做不好项目经理和PMO【附具体行动清单】

在职场中&#xff0c;向上管理基本是最重要的一件事儿&#xff0c;升职涨薪奖金都离不开向上管理&#xff01;当你的向上管理做得好&#xff0c;机会都会迎面扑来。 你是不是也遇到过被领导批评时&#xff0c;感到非常委屈或愤怒&#xff0c;情绪经常被领导左右&#xff0c;那…

Three.js一学就会系列:02 画线

系列文章目录 Three.js一学就会系列&#xff1a;01 第一个3D网站 文章目录系列文章目录[Three.js一学就会系列&#xff1a;01 第一个3D网站](https://blog.csdn.net/u012551928/article/details/128205373)前言一、省略部分二、使用方法创建一个场景创建一个透视摄像机将渲染器…

详解CSS层叠上下文(解析z-index不生效的原因)

为什么会有层叠上下文 在CSS2.1规范中&#xff0c;每个盒模型的位置是三维的&#xff0c;分别是平面画布上的X轴&#xff0c;Y轴以及表示层叠的Z轴。一般情况下&#xff0c;元素在页面上沿X轴Y轴平铺&#xff0c;我们察觉不到它们在Z轴上的层叠关系。而一旦元素发生堆叠&#x…

查询网站的谷歌PR权重复杂吗?查询谷歌PR权重最简单的方法

查询网站的谷歌PR权重复杂吗&#xff1f;用对方法一点也不复杂哦! 查询谷歌PR权重最简单的方法——用网站批量查询工具。 网站批量查询工具根据网站的域名可以查询到网站的权重值、网站信息、域名信息、域名备案情况、域名是否安全&#xff0c;来作为网站数据分析的参考。 具体…

C语言基础7:结构体类型、声明、成员类型、定义、初始化、成员访问、传参

文章目录C语言基础7&#xff1a;结构体类型、声明、成员类型、定义、初始化、成员访问、传参1. 结构体类型的声明1.1 结构体的基础知识1.2 结构体的声明1.3 结构体成员的类型1.4 结构体变量的定义和初始化2. 结构体成员访问4. 结构体传参C语言基础7&#xff1a;结构体类型、声明…

SAP S4HANA MM模块后台配置详解

目录 1. 常规设置 1.1 定义国家 1.2.计量单位配置 1.3.货币设置 1.4.维护日历 1.4.1 概念及功能说明 1.4.2 业务示例 1.4.3 配置步骤 2. 企业结构 2.1 定义和分配公司 2.2 设定评估级别、定义/分配工厂 2.2.1. 概念及功能说明 2.2.1. 业务示例 2.2.2. 配置步…

java 八股文

java 八股文 java篇 java 面向对象有哪些特征 封装 多态和继承 arrayList 和 LinkedList 的区别 数据结构不同&#xff0c;一个是数组一个是链表 arrayList 适合 随机访问 读多&#xff0c;插入和删除少 LinkedList 适合插入 和删除 多&#xff0c;按次序遍历的情况 再…

数据结构实验-折半插入排序-双向冒泡排序

目录 分析&#xff1a; 折半插入排序 双向冒泡排序 折半插入排序 思想&#xff1a; 代码 运行结果 双向冒泡排序 代码 运行结果 分析&#xff1a; 折半插入排序 折半插入排序&#xff0c;折半插入排序是在直接插入的改进&#xff0c;通过折半查找得到插入位置&#xf…

java自定义类加载器来加载本地class文件,用demo来解析类加载的双亲委派机制、沙箱机制、打破双亲委派机制

1、首先将class文件放入指定本地目录下 2、编写自定义类加载器demo代码来加载class文件 /*** author WuSong* version 1.0* date 2022/12/7 12:07* description*/ public class MyClassLoaderTest {/*** 1&#xff1a;继承ClassLoader类* 2&#xff1a;重写findClass方法*/sta…

2023最新扫码连wifi-扫码挪车-聚合CPS返利多合一小程序源码

2023最新扫码连wifi-扫码挪车-聚合CPS返利多合一系统 系统特点: 目前已接入的 CPS 渠道: 充值:话费充值、电费充值、影视会员充值、会员卡券充值 本地团购:联联周边游 电商平台:京东、拼多多、唯品会、淘宝、抖音美团:外卖、闪购、酒店、到店、优选饿了么:外卖、商超 出行服务:…

高压放大器在压电驱动器的机翼除冰方法研究中的应用

实验名称&#xff1a;高压放大器基于压电驱动器的机翼除冰方法研究 研究方向&#xff1a;压电效应、多普勒激光测振 实验原理&#xff1a;多普勒激光测振仪是基于多普勒激光测振原理工作的&#xff0c;当四边固支的矩形板通过驱动器激振起来时&#xff0c;通过激光扫描铝板上的…

知识图谱-KGE-语义匹配-双线性模型(打分函数用到了双线性函数)-2012:LFM(Latent Factor Model)

【paper】 A latent factor model for highly multi-relational data 【简介】 这篇文章是法国的研究团队发表在 NIPS 2012 上的文章&#xff0c;还挂了 Antoine Bordes 的名字。文章提出了 LFM&#xff08;Latent Factor Model&#xff09;&#xff0c;主要贡献有两点&#x…

机床测头应用一:仿形加工功能,降低废品率

机床测头是一种可安装在大多数数控机床上&#xff0c;并在加工循环中自动对工件的尺寸及位置进行测量的装置&#xff0c;使用合适的测量程序&#xff0c;还可以根据测量结果实现自动刀路补偿&#xff0c;可以保证“第一件和第一百件尺寸一致”&#xff0c;是批量生产中不可缺少…

PLC程序实例三:ModBusRTU客户端编程实例与测试方法

一、需求描述 1、设备作为ModBusRTU服务端时&#xff0c;需要给出对应的测试方法&#xff0c;即 PLC 作为主站&#xff0c;设备作为从站使用&#xff08;本文编写的是PLC主站程序&#xff09; 2、业务与上一篇文章ModBusTCP网络触发业务逻辑一致&#xff0c;描述如下&#xf…

SpringCloud学习笔记 - Nacos服务注册中心 - Nacos Discovery

1. Nacos简介 Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称&#xff0c;一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集&#xff0c;帮助您…

Git实战 | 让工作更高效,搞定Git的分支管理

上一篇讲到Git的分支管理实操&#xff0c;在线合并和本地合并都进行了实操。毕竟&#xff1a;光说不练是假把式。而只练不整理&#xff0c;只能是傻把式了。分支管理到底如何进行管理呢&#xff1f; 先以GitLab上的一张经典的图打头&#xff0c;作为一个总体概览&#xff0c;也…