Java内存区域(二)

简介: Java内存区域

上述的内容 也有可能随着JVM的迭代 也会发生很多的变化, 这里是按照主流的Java 8


案例

public class SimpleHeap {
    private int id;//属性、成员变量
    public SimpleHeap(int id) {
        this.id = id;
    }
    public void show() {
        System.out.println("My ID is " + id);
    }
    public static void main(String[] args) {
        SimpleHeap sl = new SimpleHeap(1);
        SimpleHeap s2 = new SimpleHeap(2);
        int[] arr = new int[10];
        Object[] arr1 = new Object[10];
    }
}

d0b1ffbfa995e73c871072b6051d9699_image-20230926194836745.png


堆空间划分的细节

现代垃圾收集器大部分都基于分代收集理论设计,堆空间细分为:


Java7 及之前堆内存逻辑上分为三部分:新生区+养老区+永久区


Young Generation Space 新生区 Young/New

又被划分为Eden区和Survivor区

Old generation space 养老区 Old/Tenure

Permanent Space 永久区 Perm

d430d28937aa4ea89861926443991071_image-20230926195018113.png


Java 8及之后堆内存逻辑上分为三部分:新生区+养老区+元空间


Young Generation Space 新生区,又被划分为Eden区和Survivor区

Old generation space 养老区

Meta Space 元空间 Meta

bb019ed624b820c0c7a8548b564dec9f_image-20230926195033076.png


约定:新生区 <–> 新生代 <–> 年轻代 、 养老区 <–> 老年区 <–> 老年代、 永久区 <–> 永久代


设置堆大小

Java堆区用于存储Java对象实例,那么堆的大小在JVM启动时就已经设定好了,可以通过选项”-Xms”和”-Xmx”来进行设置。


一旦堆区中的内存大小超过“-Xmx”所指定的最大内存时,将会抛出OutofMemoryError异常。


通常会将-Xms和-Xmx两个参数配置相同的值


-Xms用于表示堆区的起始内存,等价于**-XX:InitialHeapSize**

-Xmx则用于表示堆区的最大内存,等价于**-XX:MaxHeapSize**

原因:假设两个不一样,初始内存小,最大内存大。在运行期间如果堆内存不够用了,会一直扩容直到最大内存。如果内存够用且多了,也会不断的缩容释放。频繁的扩容和释放造成不必要的压力,避免在GC之后调整堆内存给服务器带来压力。


后续还需要对这方面内容进行扩展

方法区(Method Area)

这个又是和上面的Java堆区有着相似之处。


它是各个线程共享的内存区域,它用于存储已被虚拟机加载 的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。虽然《Java虚拟机规范》中把 方法区描述为堆的一个逻辑部分,但是它却有一个别名叫作“非堆”(Non-Heap),目的是与Java堆区 分开来。


栈、堆、方法区的交互关系

6f4d7c9b50be81c232e916aed3487ba8_image-20230926195829771.png


Person 类的 .class 信息存放在方法区中

person 变量存放在 Java 栈的局部变量表中

真正的 person 对象存放在 Java 堆中

在 person 对象中,有个指针指向方法区中的 person 类型数据,表明这个 person 对象是用方法区中的 Person 类 new 出来的


022900f9a40922e90f779a90224f1a6e_image-20230926195849265.png

相关细节

方法区主要存放的是 Class,而堆中主要存放的是实例化的对象


方法区(Method Area)与Java堆一样,是各个线程共享的内存区域。多个线程同时加载统一个类时,只能有一个线程能加载该类,其他线程只能等等待该线程加载完毕,然后直接使用该类,即类只能加载一次。


方法区在JVM启动的时候被创建,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的。


方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展。


方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误:


java.lang.OutofMemoryError:PermGen space或者java.lang.OutOfMemoryError:Metaspace


加载大量的第三方的jar包


Tomcat部署的工程过多(30~50个)

大量动态的生成反射类

关闭JVM就会释放这个区域的内存。


方法区的演变

在 JDK7 及以前,习惯上把方法区,称为永久代。JDK8开始,使用元空间取代了永久代。我们可以将方法区类比为Java中的接口,将永久代或元空间类比为Java中具体的实现类

本质上,方法区和永久代并不等价。仅是对Hotspot而言的可以看作等价。《Java虚拟机规范》对如何实现方法区,不做统一要求。例如:BEAJRockit / IBM J9 中不存在永久代的概念。

现在来看,当年使用永久代,不是好的idea。导致Java程序更容易OOm(超过-XX:MaxPermsize上限)

而到了JDK8,终于完全废弃了永久代的概念,改用与JRockit、J9一样在本地内存中实现的元空间(Metaspace)来代替

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代最大的区别在于:元空间不在虚拟机设置的内存中,而是使用本地内存。

永久代、元空间二者并不只是名字变了,内部结构也调整了

根据《Java虚拟机规范》的规定,如果方法区无法满足新的内存分配需求时,将抛出OOM异常

622c9c274fec5e27368b4815171b472a_image-20230926200208528.png


后续还需要补充


垃圾回收问题

《Java虚拟机规范》对方法区的约束是非常宽松的,除了和Java堆一样不需要连续的内存和可以选 择固定大小或者可扩展外,甚至还可以选择不实现垃圾收集。


相对而言,垃圾收集行为在这个区域的 确是比较少出现的,但并非数据进入了方法区就如永久代的名字一样“永久”存在了。这区域的内存回 收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收效果比较难令人满意,尤 其是类型的卸载,条件相当苛刻,但是这部分区域的回收有时又确实是必要的。


以前Sun公司的Bug列 表中,曾出现过的若干个严重的Bug就是由于低版本的HotSpot虚拟机对此区域未完全回收而导致内存 泄漏。


常见面试题

百度

三面:说一下JVM内存模型吧,有哪些区?分别干什么的?

蚂蚁金服:

Java8的内存分代改进

JVM内存分哪几个区,每个区的作用是什么?

一面:JVM内存分布/内存结构?栈和堆的区别?堆的结构?为什么两个survivor区?

二面:Eden和survior的比例分配

小米:

jvm内存分区,为什么要有新生代和老年代

字节跳动:

二面:Java的内存分区

二面:讲讲vm运行时数据库区

什么时候对象会进入老年代?

京东:

JVM的内存结构,Eden和Survivor比例。

JVM内存为什么要分成新生代,老年代,持久代。新生代中为什么要分为Eden和survivor。

天猫:

一面:Jvm内存模型以及分区,需要详细到每个区放什么。

一面:JVM的内存模型,Java8做了什么改

拼多多:

JVM内存分哪几个区,每个区的作用是什么?

美团:

java内存分配

jvm的永久代中会发生垃圾回收吗?

一面:jvm内存分区,为什么要有新生代和老年代?

运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。


Class文件中除了有类的版本、字 段、方法、接口等描述信息外,还有一项信息是常量池表,用于存放编译期生 成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。


运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量 一定只有编译期才能产生,也就是说,并非预置入Class文件中常量池的内容才能进入方法区运行时常 量池


运行期间也可以将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的 intern()方法。(动态性的体现)


既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存 时会抛出OutOfMemoryError异常。


HotSpot 虚拟机对象探究

一个对象创建的全过程

对象的内存布局

对象的访问定位

目录
相关文章
|
12月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
1261 3
|
存储 缓存 Java
Java数组全解析:一维、多维与内存模型
本文深入解析Java数组的内存布局与操作技巧,涵盖一维及多维数组的声明、初始化、内存模型,以及数组常见陷阱和性能优化。通过图文结合的方式帮助开发者彻底理解数组本质,并提供Arrays工具类的实用方法与面试高频问题解析,助你掌握数组核心知识,避免常见错误。
|
11月前
|
Java 大数据 Go
从混沌到秩序:Java共享内存模型如何通过显式约束驯服并发?
并发编程旨在混乱中建立秩序。本文对比Java共享内存模型与Golang消息传递模型,剖析显式同步与隐式因果的哲学差异,揭示happens-before等机制如何保障内存可见性与数据一致性,展现两大范式的深层分野。(238字)
375 4
|
存储 缓存 Java
【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理
JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm概念说明主内存所有线程共享的内存区域,存储原始变量(堆内存中的对象实例和静态变量)工作内存。
426 0
|
11月前
|
存储 缓存 Java
【深入浅出】揭秘Java内存模型(JMM):并发编程的基石
本文深入解析Java内存模型(JMM),揭示synchronized与volatile的底层原理,剖析主内存与工作内存、可见性、有序性等核心概念,助你理解并发编程三大难题及Happens-Before、内存屏障等解决方案,掌握多线程编程基石。
|
缓存 监控 Kubernetes
Java虚拟机内存溢出(Java Heap Space)问题处理方案
综上所述, 解决Java Heap Space溢出需从多角度综合施策; 包括但不限于配置调整、代码审查与优化以及系统设计层面改进; 同样也不能忽视运行期监控与预警设置之重要性; 及早发现潜在风险点并采取相应补救手段至关重要.
1121 17
|
Java 物联网 数据处理
Java Solon v3.2.0 史上最强性能优化版本发布 并发能力提升 700% 内存占用节省 50%
Java Solon v3.2.0 是一款性能卓越的后端开发框架,新版本并发性能提升700%,内存占用节省50%。本文将从核心特性(如事件驱动模型与内存优化)、技术方案示例(Web应用搭建与数据库集成)到实际应用案例(电商平台与物联网平台)全面解析其优势与使用方法。通过简单代码示例和真实场景展示,帮助开发者快速掌握并应用于项目中,大幅提升系统性能与资源利用率。
385 6
Java Solon v3.2.0 史上最强性能优化版本发布 并发能力提升 700% 内存占用节省 50%
|
监控 Kubernetes Java
最新技术栈驱动的 Java 绿色计算与性能优化实操指南涵盖内存优化与能效提升实战技巧
本文介绍了基于Java 24+技术栈的绿色计算与性能优化实操指南。主要内容包括:1)JVM调优,如分代ZGC配置和结构化并发优化;2)代码级优化,包括向量API加速数据处理和零拷贝I/O;3)容器化环境优化,如K8s资源匹配和节能模式配置;4)监控分析工具使用。通过实践表明,这些优化能显著提升性能(响应时间降低40-60%)同时降低资源消耗(内存减少30-50%,CPU降低20-40%)和能耗(服务器功耗减少15-35%)。建议采用渐进式优化策略。
703 2
|
缓存 监控 Cloud Native
Java Solon v3.2.0 高并发与低内存实战指南之解决方案优化
本文深入解析了Java Solon v3.2.0框架的实战应用,聚焦高并发与低内存消耗场景。通过响应式编程、云原生支持、内存优化等特性,结合API网关、数据库操作及分布式缓存实例,展示其在秒杀系统中的性能优势。文章还提供了Docker部署、监控方案及实际效果数据,助力开发者构建高效稳定的应用系统。代码示例详尽,适合希望提升系统性能的Java开发者参考。
700 4
Java Solon v3.2.0 高并发与低内存实战指南之解决方案优化