【Java基础】jvm 堆、栈、方法区 & java 内存模型

简介: 在进入主题前,我们先了解一些相关的知识,方面后面对运行时数据区进行分类。进程中,有很多数据是多线程之间共享的,线程在执行时,会先从主存中读取数据,然后复制一份到高速缓存中,当计算完后,再刷新到主存中。

一、 概览

在进入主题前,我们先了解一些相关的知识,方面后面对运行时数据区进行分类。

进程中,有很多数据是多线程之间共享的,线程在执行时,会先从主存中读取数据,然后复制一份到高速缓存中,当计算完后,再刷新到主存中。

我们只要找到独属于线程的资源,那么其他的资源都是线程共享的,线程运行的本质就是函数的执行,函数运行时的信息保存在栈帧中,栈帧中保存了函数的返回值、调用其它函数的参数、java方法、局部变量、操作数栈、动态链接、方法出口、寄存器信息等。除了这个栈帧里面的信息,其他信息都是共享的,据此,我们先画个图,运行时数据区(Runtime Data Area)大致可以分成以下几个区域
在这里插入图片描述

Java运行时数据区(Runtime Data Area)是指在Java程序执行期间,Java虚拟机所管理的诸多内存区域(分别用于存储不同的数据),如上图所示,包含了以下几个部分:

  1. 堆区
  2. 栈区
  3. 方法区
  4. 程序计数器

二、堆区

该区域是一个共享区,主要用于存储对象实例、数组.

Java堆是虚拟机管理的内存中最大的一块区域,jvm只有一个堆区,在虚拟机启动时创建,被所有线程共享,堆是gc管理堆主要区域。

Jvm堆分类

jvm堆一般分为三部分: 新生代,老年代,永久代(元空间)

永久代java8已经被元空间取代。

2.1 新生代

用来存放新生的对象,占据堆的1/3空间

如果新创建的对象占用内存很大,则直接分配到老年代。(当老年代也满了装不下的时候,就会抛出OOM异常。)

2.2 老年代

老年代的对象比较稳定,所以MajorGC不会频繁执行。

在进行MajorGC前一般都先进行了一次MinorGC, 使得有新生代的对象晋身入老年代,导致空间不够用时才触发。 当无法找到足够大的连续空间分配给新创建的较大对象时 也会提前触发一次MajorGC进行垃圾回收腾出空间。

2.3 永久代(元空间)

指内存的永久保存区域,主要存放Class和Meta(元数据)的信息。

Class在被加载的时候被放入永久区域。类的元数据放入 native memory, 字符串池和类的静态变量放入java堆中。

永久代(元空间)和和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的Class的增多而胀满,最终抛出OOM异常。

在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。

元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。

这样可以加载多少类的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来控制。

三、栈

虚拟机栈:java方法、局部变量、操作数栈、动态链接、方法出口等。

本地方法栈:native方法

栈帧

栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。简言之,栈帧就是利用EBP(栈帧指针,请注意不是ESP)寄存器访问局部变量、参数、函数返回地址等的手段。

关于本地方法栈和Java栈,在Java虚拟机规范中定义了两种异常。

线程的请求栈的深度大于虚拟机所允许的深度,将抛出StackOverflowError异常
虚拟机在扩展栈时无法申请到足够的内存时,将抛出OutOfMemoryError异常
每次函数调用都会生成对应的栈帧,从而占用一定的内存

四、方法区

线程共享的内存区域,主要用于存储类型信息、常量、静态变量、即时编译代码等

五、程序计数器

CPU在执行程序时,需要有一个地方存放下一条要被取走指令的位置,这是一个寄存器,cpu中只有一个程序计数器。
虚拟机字节码指令的地址或undefined

每个线程都有私有(独立)的程序计数器。

线程中的程序计数器可以理解为一段内存,用来保存当前线程执行到的位置,因为系统采用时间片轮转的方法,所以一个线程不可能一直占用CPU,只能执行规定时间,进行线程切换,这里就需要有一个私有的线程计数器,也就是本地计数器,来保存当前线程的执行到的位置,等到下一次再从这个位置继续执行。

六、Java内存模型(Java Memory Model,JMM)

在多线程场景下,CPU会出现缓存一致性问题,处理器重新排序问题,

为了解决这个问题,制定了计算机内存模型。(原子性、可见性、有序性)
即是Java语言对这个操作规范的遵循,

JMM规定了所有的变量都存储在主存中,每个线程都有自己的工作区,线程将使用到的变量从主存中复制一份到自己的工作区,线程对变量的所有操作(读取、赋值等)都必须在工作区,不同的线程也无法直接访问对方工作区,线程之间的消息传递都需要通过主存来完成。可以把这里主存类比成计算机内存模型中的主存,工作区类比成计算机内存模型中的高速缓存。

相关文章
|
4天前
|
存储
栈内存
栈内存归属于单个线程,也就是每创建一个线程都会分配一块栈内存,而栈中存储的东西只有本线程可见,属于线程私有。 栈的生命周期与线程一致,一旦线程结束,栈内存也就被回收。 栈中存放的内容主要包括:8大基本类型 + 对象的引用 + 实例的方法
8 1
|
22天前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
1月前
|
存储 算法 Java
深入理解Java虚拟机(JVM)及其优化策略
【10月更文挑战第10天】深入理解Java虚拟机(JVM)及其优化策略
41 1
|
1月前
|
安全 Java API
🌟探索Java宇宙:深入理解Java技术体系与JVM的奥秘
本文深入探讨了Java技术体系的全貌,从Java语言的概述到其优点,再到Java技术体系的构成,以及JVM的角色。旨在帮助Java开发者全面了解Java生态,提升对Java技术的认知,从而在编程实践中更好地发挥Java的优势。关键词:Java, JVM, 技术体系, 编程语言, 跨平台, 内存管理。
34 2
|
4月前
|
Java
Java面试题:Java内存模型与并发编程知识点,解释Java中“happens-before”的关系,分析Java中的内存一致性效应(Memory Consistency Effects)及其重要性
Java面试题:Java内存模型与并发编程知识点,解释Java中“happens-before”的关系,分析Java中的内存一致性效应(Memory Consistency Effects)及其重要性
28 0
|
6月前
|
SQL 安全 Java
java单例——Java 内存模型之从 JMM 角度分析 DCL
java单例——Java 内存模型之从 JMM 角度分析 DCL
70 0
|
存储 算法 Java
【Android 内存优化】Java 内存模型 ( Java 虚拟机内存模型 | 线程私有区 | 共享数据区 | 内存回收算法 | 引用计数 | 可达性分析 )
【Android 内存优化】Java 内存模型 ( Java 虚拟机内存模型 | 线程私有区 | 共享数据区 | 内存回收算法 | 引用计数 | 可达性分析 )
255 0
|
存储 缓存 Java
Java高级之内存模型分析
博客出自:http://blog.csdn.net/liuxian13183,转载注明出处! All Rights Reserved ! 下文是博主感悟,请带着怀疑性的态度阅读! 需要了解基本变量所占内存大小,请移步:读书笔记-类结构的认识 Java存储空间有这么几块-来源于Java编程思想 寄存器:位于处理器内部,不受外层代码控制,由处理器自行分配-C/C++可以建议分配方式,使用句柄(包含引用类型和引用地址)来操作数据。
1057 0
|
8天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
4天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
22 9