JVM虚拟机(一)介绍、JVM内存模型、JAVA内存模型,堆区、虚拟机栈、本地方法栈、方法区、常量池

目录

学习JVM有什么用、为什么要学JVM?

JVM是什么呢?

优点一:一次编写,到处运行。(Write Once, Run Anywhere,WORA)

优点二:自动内存管理,垃圾回收机制。

优点三:多线程支持

【Java程序运行机制】我们首先需要了解java如何运行起来的?

一、JVM和JMM的区别

(1)Java内存模型(JMM)

(2)JVM内存模型(JVM)

二、JVM运行时数据区

一、堆(heap)--- 存放对象实例(重要)

二、虚拟机栈(JVM Stack)--- (线程私有)

三、本地方法栈

四、方法区(Non-Heap)==> 永久代(1.8以前)、元空间(1.8以后)

五、常量池

(1)字符串常量池(String Pool)---堆区

(2)运行时常量池---方法区


学习JVM有什么用、为什么要学JVM?

  1. 深入理解Java语言:了解JVM的工作原理可以帮助你更深入地理解Java语言的运行机制,包括字节码的编译和执行过程;以及JVM的一些概念和技术,如垃圾收集、内存管理等。
  2. 改进代码质量:了解JVM的内存模型和并发模型可以帮助你编写更高质量的代码,避免死锁和竞态条件等问题。
  3. 性能优化:掌握JVM的知识能让你更有效地进行性能调优。你可以分析和理解垃圾收集器的行为,优化内存使用,减少延迟和提高吞吐量。
  4. 故障排查:当遇到内存泄漏、栈溢出、CPU飙高 或其他JVM相关的问题时,对JVM的了解可以帮助你快速定位和解决问题。可以更熟练地使用各种JVM工具,如jconsole、jstack、jmap、jstat等,这些工具对于监控和故障排查非常有用。
  5. 准备面试:对于高级Java开发者或架构师的职位,对JVM的理解通常是面试中的重要部分

JVM是什么呢?

JVM(Java Virtual Machine,Java虚拟机)是一个可以执行Java字节码的虚拟计算机。它是Java平台的核心组成部分


优点一:一次编写,到处运行。(Write Once, Run Anywhere,WORA)

Java源代码被编译成平台无关的字节码,可以在任何安装了JVM的平台上(windows/Linux/mac)运行。

优点二:自动内存管理,垃圾回收机制。

JVM负责自动内存管理,包括垃圾收集(Garbage Collection,GC),减轻了开发者管理内存的负担。

说到这里,一般会跟 C语言进行对比,C语言需要程序员自己去管理内存,如果程序员由于编码不当,很容易造成内存泄露的问题。而 Java 虚拟机的垃圾回收功能就大大减轻了程序员的负担,减少了程序员出错的机会。

优点三:多线程支持

JVM支持多线程编程,允许并发执行,提高了应用程序的效率。

【Java程序运行机制】我们首先需要了解java如何运行起来的?

Java文件是如何运行起来的:

Java文件运行起来的过程通常涉及以下几个步骤: 

1、编写代码:首先,我们编写Java源代码【后缀为.java

2、编译代码:使用JDK中的Java编译器(javac命令)将源代码编译成字节码【.class文件

3、运行程序:使用Java虚拟机(JVM)java命令运行生成的字节码文件。这将加载字节码并执行其中的代码。

4、类加载JVM首先加载字节码文件。类加载器(ClassLoader)首先加载.class文件,然后链接阶段会进行验证、准备和解析。最后,在初始化阶段,JVM会执行类构造函数<clinit>()方法。

5、执行主方法:JVM查找并执行main方法,这是程序的入口点。在执行期间,JVM管理多个运行时数据区,包括方法区、堆、栈、本地方法栈和程序计数器。

6、程序结束:当程序完成执行或遇到退出命令时,JVM将停止程序,并进行必要的清理工作。

7、垃圾回收JVM的垃圾收集器会自动回收不再使用的对象所占用的内存。

在整个过程中,Java代码的安全性由JVM的安全管理器来保证,确保代码不会执行任何违反安全策略的操作。

总结:

其实运行一个Java程序都是通过启动一个JVM虚拟机,在虚拟机里面运行XXX.class文件

C:\Program Files\Java\jdk1.8.0_333\bin\java

一、JVM和JMM的区别

(1)Java内存模型(JMM)

Java内存模型规定所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本的拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程之间也无法访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

Java 内存模型(下文简称 JMM)就是在底层处理器内存模型的基础上,定义自己的多线程语义。它明 确指定了一组排序规则,来保证线程间的可见性。

java 提供了几种语言结构,包括 volatile, final 和 synchronized, 它们旨在帮助程序员向编译器描述程序 的并发要求,其中:

  • volatile - 保证可见性和有序性

  • synchronized - 保证可见性和有序性; 通过管程(Monitor)保证一组动作的原子性

  • final - 通过禁止在构造函数初始化和给 final 字段赋值这两个动作的重排序,保证可见性(如果 this 引用逃逸就不好说可见性了)

(2)JVM内存模型(JVM)

Jvm:(java Virtual Machine)JVM是Java虚拟机,Java程序需要运行在虚拟机上,不同的平台有自己的虚拟机,因此Java语言可以实现跨平台。

JVM主要组成部分:JVM包括两个子系统和两个组件

  • 类加载子系统

  • 执行引擎

  • 运行时数据区

  • 本地接口

二、JVM运行时数据区

一、堆(heap)--- 存放对象实例(重要)

Java 堆是 Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

  • 【堆的物理空间是不连续的所以分配的内存是在运行期确认的,因此大小不固定】

  • 存放内容:堆存放的是对象的实例和数组。

大部分Java的内存溢出都属于堆溢出。原因是因为大量对象占据了堆空间,这些对象都持有强引用导致无法回收。

例子:

启动参数设置-Xmx10m,限制最大的内存为10M

public static void main(String[] args){
​
   List<String> list = new ArrayList<>();
   while(true){
       list.add("111");
   }
} 

二、虚拟机栈(JVM Stack)--- (线程私有)

每个线程运行时所需要的内存,称为虚拟机栈,它的特点就是先进后出。

【栈的物理空间是连续的,所以分配的内存大小要在编译期就确认,大小是固定的】

栈存放的内容:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。

  • 每个线程运行的时候都会创建虚拟机栈,所以栈内存也是线程安全的。

  • 每个栈由多个栈帧(frame)组成,对应着每次方法调用时所需要的数据,或者说占用的内存

JVM栈主要负责以下几个方面:

  1. 方法调用:每当Java程序调用一个方法时,JVM栈就会为这次方法调用创建一个栈帧(Stack Frame),用于存储局部变量、操作数栈、动态链接信息和方法出口等。

  2. 局部变量存储:每个栈帧都包含一个局部变量表,用于存储方法中的局部变量,包括基本数据类型、对象引用以及returnAddress类型(指向方法调用结束后的执行点)。

  3. 操作数栈:每个栈帧还包含一个操作数栈,用于存储方法执行过程中的中间结果,以及参与计算的操作数。

  4. 同步:JVM栈还与Java同步机制有关。当一个同步方法或同步块被调用时,JVM会创建一个锁记录(Lock Record),并将其压入当前线程的JVM栈中。

  5. 异常处理:JVM栈还参与异常处理。当异常发生时,JVM会创建一个异常对象,并在JVM栈中查找匹配的异常处理器。

  6. 线程私有每个线程都有自己的JVM栈,这意味着JVM栈是线程私有的内存区域,用于支持多线程环境下的并发执行。

  7. 内存管理:JVM栈中的栈帧随着方法调用的结束而自动被销毁,其内存由JVM自动管理,不需要程序员手动释放。

Java 虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError:

  • StackOverFlowError:当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError异常。(典型的场景有:递归调用和死循环)

  • OutOfMemoryError:若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了(如果JVM栈的内存空间不足以创建新的栈帧),此时抛出OutOfMemoryError异常。

由于每一个线程的开启都要占用系统内存,因此当线程数量太多时,也有可能导致OOM。由于线程的栈空间也是在堆外分配的,因此和直接内存非常相似,如果想让系统支持更多的线程,那么应该使用一个较小的堆空间。

三、本地方法栈

和虚拟机栈所发挥的作用非常相似,区别是:

  • 本地方法栈则为虚拟机使用到的 Native 方法服务。

  • 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务

本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。方法执行完毕后相应的栈帧也会出栈并释放内存空间

本地方法栈报错:

也会出现 StackOverFlowError 和 OutOfMemoryError 两种错误。

本地方法区都有什么里面都有什么?

native方法

四、方法区(Non-Heap)==> 永久代(1.8以前)、元空间(1.8以后)

(线程共享)

它用于存储已被虚拟机加载的:类信息、常量、静态变量、方法字节码、即时编译器编译后的代码等数据。

方法区的两个实现类:

  • JDK6、JDK7 时,方法区 就是 PermGen(永久代)。

  • JDK8 时,方法区就是 Metaspace(元空间)

下面是关于方法区的介绍:

方法区(Method Area) 是各个线程共享的内存区域(跟我们之前讲过的堆空间是一样的)。

元空间默认空间大小是21M,如果空间不足会触发 Full GC,然后扩容。

主要存储类的信息、运行时常量池。

方法区是在虚拟机启动的时候创建,关闭虚拟机时释放元空间的内存。

如果方法区域中的内存无法满足分配请求,则会抛出 OutOfMemoryError: Metaspace。

方法区的实现运行时常量池字符串常量池
Jdk6PermGen space(永久代)永久代---在堆区永久代--在堆区
Jdk7PermGen space(永久代)永久代-方法区在堆区
Jdk8Metaspace(元空间)元空间-方法区在堆区

在 jdk1.8中元空间替代了永久代,原方法区被分成两部分:

方法区的Class文件信息,Class常量池和运行时常量池的三者关系

  • 加载的类信息

  • 运行时常量池:加载的类信息被保存在元数据区中,运行时常量池保存在堆中;

五、常量池

每个class一份,存在于字节码文件中。常量池中有字面量(数量值、字符串值)和符号引用(类符号引用、字段符号引用、方法符号引用),虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型

运行时和字符串常量池的版本变化? 存在哪里?

  • 在JDK1.7之前:运行时常量池--- 包含字符串常量池存放在方法区(永久代)

  • 在JDK1.7:字符串常量池被从永久代拿到了中, 运行时常量池还在方法区, 也就是hotspot中的永久代。

  • 在JDK1.8:hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)

(1)字符串常量池(String Pool)---堆区

字符串常量池又称为:字符串池,全局字符串池,英文也叫String Pool。 在工作中,String类是我们使用频率非常高的一种对象类型。JVM为了提升性能和减少内存开销,避免字符串的重复创建,其维护了一块特殊的内存空间,这就是我们今天要讨论的核心:字符串常量池。字符串常量池由String类私有的维护。

字符串常量池

每个JVM中只有一份,存在于堆区。全局字符串池里的内容是在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中(string pool中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的)。 在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个哈希表,里面存的是驻留字符串(用双引号括起来的引用而不是驻留字符串实例本身),也就是说在堆中的某些字符串实例被这个StringTable引用之后就等同被赋予了”驻留字符串”的身份。

(2)运行时常量池---方法区

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

运行时常量池是当Class文件被加载到内存后,Java虚拟机会将Class文件常量池里的内容转移到运行时常量池里(运行时常量池也是每个类都有一个)。运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中

运行时常量池

每个class一份,存在于方法区中(元空间)。当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,经过解析(resolve)之后,也就是把符号引用替换为直接引用,解析的过程会去查询全局字符串池,也就是下面的StringTable,以保证运行时常量池所引用的字符串与全局字符串池中所引用的是一致的。

总结:

JVM之 方法区、永久代(PermGen space)、元空间(Metaspace)三者的区别猎人在吃肉的博客-CSDN博客方法区和元空间区别

通过上面分析,大家应该清楚了 JDK8 中永久代向元空间的转换。不过大家应该都有一个疑问,就是为什么要做这个转换?所以,最后给大家总结以下几点原因:

1)字符串存在永久代中,容易出现性能问题和内存溢出。

2)类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。

3)永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

4)Oracle 可能会将HotSpot 与 JRockit 合二为一。

整理完毕,下一节继续!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值