Java虚拟机详解02----JVM内存结构

简介:

主要内容如下:

  • JVM启动流程
  • JVM基本结构
  • 内存模型
  • 编译和解释运行的概念

 

一、JVM启动流程:

587f1e97-d4ed-4d7f-89c6-dabe944fed12

JVM启动时,是由java命令/javaw命令来启动的。

二、JVM基本结构:

JVM基本结构图:

16865f32-a87b-4fee-9aed-a793016cf2a7

《深入理解Java虚拟机(第二版)》中的描述是下面这个样子的:

96698196-0f6b-4ef6-9a20-85dd35d75f27

 

Java中的内存分配:

Java程序在运行时,需要在内存中的分配空间。为了提高运算效率,就对数据进行了不同空间的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。

具体划分为如下5个内存空间:(非常重要)

  • :存放局部变量
  • :存放所有new出来的东西
  • 方法区:被虚拟机加载的类信息、常量、静态常量等。
  • 程序计数器(和系统相关)
  • 本地方法栈

1、程序计数器:

每个线程拥有一个PC寄存器

在线程创建时创建

指向下一条指令的地址

执行本地方法时,PC的值为undefined

2、方法区: 

保存装载的类信息

  类型的常量池

  字段,方法信息

  方法字节码

通常和永久区(Perm)关联在一起

3、堆内存:

和程序开发密切相关

应用系统对象都保存在Java堆中

所有线程共享Java堆

对分代GC来说,堆也是分代的

GC管理的主要区域

现在的GC基本都采用分代收集算法,如果是分代的,那么堆也是分代的。如果堆是分代的,那堆空间应该是下面这个样子:

8d74ba35-9865-4434-8be5-73cec9e93295

上图是堆的基本结构,在之后的文章中再进行详解。

4、栈内存:

  • 线程私有,生命周期和线程相同
  • 栈由一系列帧组成(因此Java栈也叫做帧栈)
  • 帧保存一个方法的局部变量、操作数栈、常量池指针
  • 每一次方法调用创建一个帧,并压栈

解释:

Java虚拟机栈描述的是Java方法执行的内存模型每个方法被调用的时候都会创建一个栈帧,用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程就对应着一个栈帧在虚拟机中从入栈到出栈的过程

在Java虚拟机规范中,对这个区域规定了两种异常情况:

    (1)如果线程请求的栈深度太深,超出了虚拟机所允许的深度,就会出现StackOverFlowError(比如无限递归。因为每一层栈帧都占用一定空间,而 Xss 规定了栈的最大空间,超出这个值就会报错)

    (2)虚拟机栈可以动态扩展,如果扩展到无法申请足够的内存空间,会出现OOM

 

4.1  Java栈之局部变量表:包含参数和局部变量

    局部变量表存放了基本数据类型、对象引用和returnAddress类型(指向一条字节码指令的地址)。其中64位长度的long和double类型的数据会占用2个局部变量空间(slot),其余数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配。

例如,我写出下面这段代码:

复制代码
 1 package test03;
 2 
 3 /**
 4  * Created by smyhvae on 2015/8/15.
 5  */
 6 public class StackDemo {
 7     
 8     //静态方法
 9     public static int runStatic(int i, long l, float f, Object o, byte b) {
10         return 0;
11     }
12 
13     //实例方法
14     public int runInstance(char c, short s, boolean b) {
15         return 0;
16     }
17 
18 }
复制代码

 

上方代码中,静态方法有6个形参,实例方法有3个形参。其对应的局部变量表如下:

1272c229-ef70-4898-9ede-66828e6b84f7

上方表格中,静态方法和实例方法对应的局部变量表基本类似。但有以下区别:实例方法的表中,第一个位置存放的是当前对象的引用

 

4、2  Java栈之函数调用组成栈帧

方法每次被调用的时候都会创建一个栈帧例如下面这个方法

public static int runStatic(int i,long l,float  f,Object o ,byte b){
       return runStatic(i,l,f,o,b);
}

 

当它每次被调用的时候,都会创建一个帧,方法调用结束后,帧出栈。如下图所示:

1244af96-c081-4059-98de-7881ec4c792e

 

4.3  Java栈之操作数栈

Java没有寄存器,所有参数传递都是使用操作数栈

例如下面这段代码:

    public static int add(int a,int b){
        int c=0;
        c=a+b;
        return c;
    }

 

压栈的步骤如下:

  0:   iconst_0 // 0压栈

  1:   istore_2 // 弹出int,存放于局部变量2

  2:   iload_0  // 把局部变量0压栈

  3:   iload_1 // 局部变量1压栈

  4:   iadd      //弹出2个变量,求和,结果压栈

  5:   istore_2 //弹出结果,放于局部变量2

  6:   iload_2  //局部变量2压栈

  7:   ireturn   //返回

如果计算100+98的值,那么操作数栈的变化如下图所示:

35721707-fff7-4516-86a7-dda30320dc06

 

 

4.4  Java栈之栈上分配:

小对象(一般几十个bytes),在没有逃逸的情况下,可以直接分配在栈上

直接分配在栈上,可以自动回收,减轻GC压力

大对象或者逃逸对象无法栈上分配

栈、堆、方法区交互:

7efdd9ad-03a3-41e8-9635-36d5506a23f9f29029fe-8cef-47ea-b091-e3f19d6a9801

 

 

三、内存模型:

每一个线程有一个工作内存。工作内存和主存独立。工作内存存放主存中变量的值的拷贝。

7883501b-7e5c-4064-af0d-438a118acbf6

当数据从主内存复制到工作存储时,必须出现两个动作:第一,由主内存执行的读(read)操作;第二,由工作内存执行的相应的load操作;当数据从工作内存拷贝到主内存时,也出现两个操作:第一个,由工作内存执行的存储(store)操作;第二,由主内存执行的相应的写(write)操作。

每一个操作都是原子的,即执行期间不会被中断

对于普通变量,一个线程中更新的值,不能马上反应在其他变量中。如果需要在其他线程中立即可见,需要使用volatile关键字作为标识。

d481032e-ed23-46c2-9c5c-5f34e3193ef9

1、可见性:

  一个线程修改了变量,其他线程可以立即知道

保证可见性的方法:

volatile

synchronized (unlock之前,写变量值回主存)

final(一旦初始化完成,其他线程就可见)

2、有序性:

  在本线程内,操作都是有序的

  在线程外观察,操作都是无序的。(指令重排 或 主内存同步延时)

3、指令重排:

142d481b-f4a3-4c23-b56d-cf3ec5b63a2e

指令重排:破坏了线程间的有序性:

154748eb-cc14-48ee-b66f-cdcc26068cd1

 

 

指令重排:保证有序性的方法:

e0bcca41-a6e1-4a5c-b723-e0d721875acc

指令重排的基本原则:

程序顺序原则:一个线程内保证语义的串行性

volatile规则:volatile变量的写,先发生于读

锁规则:解锁(unlock)必然发生在随后的加锁(lock)前

传递性:A先于B,B先于C 那么A必然先于C

线程的start方法先于它的每一个动作

线程的所有操作先于线程的终结(Thread.join())

线程的中断(interrupt())先于被中断线程的代码

对象的构造函数执行结束先于finalize()方法

 

四、解释运行和编译运行的概念:

解释运行:

解释执行以解释方式运行字节码

解释执行的意思是:读一句执行一句

编译运行(JIT):

将字节码编译成机器码

直接执行机器码

运行时编译

编译后性能有数量级的提升

编译运行的性能优于解释运行。

 

相关文章
|
16天前
|
算法 安全 Java
Java内存管理:深入理解垃圾收集器
在Java的世界里,内存管理是一块基石,它支撑着应用程序的稳定运行。本文将带你走进Java的垃圾收集器(GC),探索它是如何默默守护着我们的内存安全。我们将从垃圾收集的基本概念出发,逐步深入到不同垃圾收集器的工作机制,并通过实例分析它们在实际应用中的表现。文章不仅旨在提升你对Java内存管理的认识,更希望你能通过这些知识优化你的代码,让程序运行更加高效。
34 3
|
7天前
|
存储 缓存 监控
【Java面试题汇总】JVM篇(2023版)
JVM内存模型、双亲委派模型、类加载机制、内存溢出、垃圾回收机制、内存泄漏、垃圾回收流程、垃圾回收器、G1、CMS、JVM调优
【Java面试题汇总】JVM篇(2023版)
|
6天前
|
监控 算法 Java
Java中的内存管理:理解垃圾回收机制的深度剖析
在Java编程语言中,内存管理是一个核心概念。本文将深入探讨Java的垃圾回收(GC)机制,解析其工作原理、重要性以及优化方法。通过本文,您不仅会了解到基础的GC知识,还将掌握如何在实际开发中高效利用这一机制。
|
6天前
|
存储 监控 算法
Java中的内存管理与垃圾回收机制解析
本文深入探讨了Java编程语言中的内存管理策略和垃圾回收机制。首先介绍了Java内存模型的基本概念,包括堆、栈以及方法区的划分和各自的功能。进一步详细阐述了垃圾回收的基本原理、常见算法(如标记-清除、复制、标记-整理等),以及如何通过JVM参数调优垃圾回收器的性能。此外,还讨论了Java 9引入的接口变化对垃圾回收的影响,以及如何通过Shenandoah等现代垃圾回收器提升应用性能。最后,提供了一些编写高效Java代码的实践建议,帮助开发者更好地理解和管理Java应用的内存使用。
|
14天前
|
安全 Java API
【性能与安全的双重飞跃】JDK 22外部函数与内存API:JNI的继任者,引领Java新潮流!
【9月更文挑战第7天】JDK 22外部函数与内存API的发布,标志着Java在性能与安全性方面实现了双重飞跃。作为JNI的继任者,这一新特性不仅简化了Java与本地代码的交互过程,还提升了程序的性能和安全性。我们有理由相信,在外部函数与内存API的引领下,Java将开启一个全新的编程时代,为开发者们带来更加高效、更加安全的编程体验。让我们共同期待Java在未来的辉煌成就!
43 11
|
15天前
|
安全 Java API
【本地与Java无缝对接】JDK 22外部函数和内存API:JNI终结者,性能与安全双提升!
【9月更文挑战第6天】JDK 22的外部函数和内存API无疑是Java编程语言发展史上的一个重要里程碑。它不仅解决了JNI的诸多局限和挑战,还为Java与本地代码的互操作提供了更加高效、安全和简洁的解决方案。随着FFM API的逐渐成熟和完善,我们有理由相信,Java将在更多领域展现出其强大的生命力和竞争力。让我们共同期待Java编程新纪元的到来!
38 11
|
12天前
|
监控 Java 大数据
【Java内存管理新突破】JDK 22:细粒度内存管理API,精准控制每一块内存!
【9月更文挑战第9天】虽然目前JDK 22的确切内容尚未公布,但我们可以根据Java语言的发展趋势和社区的需求,预测细粒度内存管理API可能成为未来Java内存管理领域的新突破。这套API将为开发者提供前所未有的内存控制能力,助力Java应用在更多领域发挥更大作用。我们期待JDK 22的发布,期待Java语言在内存管理领域的持续创新和发展。
|
8天前
|
存储 缓存 算法
Java中的内存管理:理解垃圾回收机制
本文将深入探讨Java中的内存管理,特别是垃圾回收机制。我们将从基本的内存分配开始,逐步解析垃圾回收的原理和过程,以及它对Java应用程序性能的影响。通过实例演示,我们会展示如何在Java中有效地管理和优化内存使用。最后,我们将讨论一些常见的内存泄漏问题及其解决方案。
|
18天前
|
安全 前端开发 Java
浅析JVM invokedynamic指令与Java Lambda语法的深度融合
在Java的演进历程中,Lambda表达式无疑是Java 8引入的一项革命性特性,它极大地简化了函数式编程在Java中的应用,使得代码更加简洁、易于阅读和维护。而这一切的背后,JVM的invokedynamic指令功不可没。本文将深入探讨invokedynamic指令的工作原理及其与Java Lambda语法的紧密联系,带您领略这一技术背后的奥秘。
13 1
|
2天前
|
监控 算法 Java
Java中的内存管理:理解Garbage Collection机制
本文将深入探讨Java编程语言中的内存管理,特别是垃圾回收(Garbage Collection, GC)机制。我们将从基础概念开始,逐步解析垃圾回收的工作原理、不同类型的垃圾回收器以及它们在实际项目中的应用。通过实际案例,读者将能更好地理解Java应用的性能调优技巧及最佳实践。
10 0