【JavaSE】JVM

文章目录

定义

JVM是java虚拟机,体现了java 的运行环境;

整体的一个流程是将java编译成为Class类,加载到类加载器Class Loader,之后在加载到JVM中

流程如下:

类加载器

类加载器

  1. 虚拟机自在的加载器
  2. 启动类(根)加载器
  3. 扩展类加载器
  4. 应用程序(系统)加载器

.getClassLoader:查看加载器

java 复制代码
DemoJVM01 demoJVM01 = new DemoJVM01();
DemoJVM01 demoJVM02 = new DemoJVM01();
DemoJVM01 demoJVM03 = new DemoJVM01();


System.out.println(demoJVM03.hashCode());
System.out.println(demoJVM02.hashCode());
System.out.println(demoJVM01.hashCode());

System.out.println("---end");

Class<? extends DemoJVM01> aClass = demoJVM01.getClass();
System.out.println(" Class Loader:"+aClass.getClassLoader());
System.out.println(" Class Loader`s Parent:"+aClass.getClassLoader().getParent());
System.out.println(" Class Loader`s Parent`s Parent:"+aClass.getClassLoader().getParent().getParent()); // rt.jar
plain 复制代码
 Class Loader:sun.misc.Launcher$AppClassLoader@18b4aac2
 Class Loader`s Parent:sun.misc.Launcher$ExtClassLoader@3d04a311
 Class Loader`s Parent`s Parent:null

双亲委派机制

如果在jar包中如果有一模一样名字的方法的情况,新定义的内容不会生效,这称之为双亲委派机制

app >> ext >> rt (Boot)

双亲委派机制:

  1. 类加载器收到类加载的请求;
  2. 将这个请求向上委托 给父类加载器去完成,一直向上委托,直至启动类加载器;
  3. 启动加载器检查 是否能够 加载当前这个类,能加载就结束,使用当前的加载器,否则 抛出异常,通知子加载器进行加载;
  4. 重复步骤3 直至 Class Not Found;

沙箱安全机制

沙箱机制 (sandbox),将java代码限定到虚拟机(JVM)之中,并且严格限制代码对本地系统的资源访问。通过这样的措施来保证代码的有效隔离。

native关键字

private native void start0();,start()调用了start0()方法,使用了native关键字,使用调用了C语言的库,java的功能已经实现不了了。

JNI的作用:扩展Java的使用,融合不同的语言 给Java使用;

  • C+±- → Java
  • C++++ → C#

使用Java驱动本地东西或者调用计算机的时候native 会出现,但大多时候都是使用C++去实现;

Native Method Stack

PC寄存器

程序计数器:Program Counter Register

每一个线程都有一个程序计数器,是线程所私有的,就是一个指针,指向方法区的方法字节码(用于存储指向一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间;

方法区

Method Area, 方法区 是被所有线程共享,所有字段和方法字节码,以及一些特殊的方法:接口,构造函数;

简单的说,所有定义的方法的信息都会保存在该区域,此区间属于共享区间;

静态变量、常量、类信息(构造方法,接口定义)、运行时的常量池存在方法区中,但是 实例变量存在堆内存中, 和方法区无关;

static、final、Class、常量池;

队列

类似于管道流程,

线程的执行就是队列,新进先出(FIFO)原则,

学习思路

程序=数据结构+算法;

程序=框架+业务逻辑~:淘汰!

类似于桶的样态;先进后出(FILO)原则,

main方法会放在最底下,执行的方法在上面执行之后会将执行过的弹出;

如果空间设置不对,会出先栈溢出异常;递归自调会出现这种状态;

递归自调用如果没有终止递归的方法就会出现栈溢出;

存放的是进行引用的地方;

功能、特点

栈内存,主管程序的运行,生命周期和线程同步;

线程结束,占内存就会释放,对于栈来说,不存在垃圾回收问题;当线程结束,站就会结束;

栈中保存着 8大基本类型+对象引用+实例的方法;

栈的运行原理:通过栈帧实现;

栈满了 会出先StackOverflowError 栈溢出错误,

栈+堆+方法区的关系:

HotSpot和堆

HotSpot

查看版本 java -version

plain 复制代码
C:\Users\杨>java -version
openjdk version "17.0.16" 2025-07-15
OpenJDK Runtime Environment Temurin-17.0.16+8 (build 17.0.16+8)
OpenJDK 64-Bit Server VM Temurin-17.0.16+8 (build 17.0.16+8, mixed mode, sharing)

C:\Users\杨>

已经没有HotSpot了;

堆(Heap),真正放置实例内容的地方 就是堆;

类加载器 读取了类的文件后,_常量,类,方法 ,变量 _等都会保存到堆中;

堆内存细分为三个区域

  • 新生区:young
  • 养老区:old
  • 永久区:Perm

GC(垃圾回收)有轻量级和重量级两种GC机制:

  • 轻量级的GC在新生区,在堆容量特别大的情况下会 进行重量级GC;
  • GC的垃圾回收,主要在伊甸园区和养老区;
  • 如果内存特别大的时候,会出现OOM(OutOfMemoryError),堆内存溢出;
  • jdk1.8之后,永久存储区的名字 >> (源空间)

OOM的出现的场景,一直往新生区的加对象 直至加到上限,就会出现这个问题;

动态的不断生成反射类,一直加载直到内存满都会出现OOM;

新生区、永久区、堆内存调优

jdk1.8之后 元空间 和 JVM 分离了;

新生区

存储新创建的对象,大部分对象生命周期较短,通过垃圾回收(GC)快速清理无用对象。

分为伊甸园(Eden区)、幸存者0区(Survivor0)和幸存者1区(Survivor1)。

对象在Eden区创建,经历GC后存活的对象被移至幸存区,多次存活后晋升至老年代(Old Generation)。 ‌

养老区(Old)

在新生区的对象 经过伊甸园区,之后到幸存0区,幸存1区,当新生区全都满了之后,回去执行一次重GC,之后还能存在的情况才会到达养老区;

直至最后到达养老区满了之后会到达顶峰。在增加会出先OOM;

99%的对象到不了永久区,都是临时对象,大部分对象都是临时对象;

永久区(元空间)

永久区 所占用区域是常驻内存中,用来存放jdk携带的Class对象,interface元数据,存储的是Java运行的一些环境;

这个区域不会存在GC,在我们关闭虚拟机的时候,会去释放这个区域的内存;

永久区历程

  • jdk1.6之前:名字称之为永久代,常量池是在方法区之中;
  • jdk1.7:永久代慢慢退化了,将常量池移动到堆中;
  • jdk1.8:彻底去永久代,常量池到元空间中,持久代已经变成元空间了;

存储类元数据(如类定义、常量池)、静态变量等,属于方法区的替代实现。

什么情况永久区会出问题:

  • 启动类启动时加载了大量的外部jar;
  • tomcat在启动时部署了太多的应用;
  • 在程序执行时,编写了大量的反射类,不断的被加载,直到内存满,出现OOM;

方法区

方法区是与所有的内存区共享的,但是存在在堆之中,又称之为非堆

方法区这个存在在元空间之中,在逻辑上 我们理解是有的,但是物理上是不占用的;

堆内存调优

查看堆内存

java 复制代码
package Jbm;

public class DemoJVM01 {

    public static void main(String[] args) {
        // 获取JVM内存信息
        long maxMemory = Runtime.getRuntime().maxMemory();
        // 获取JVM初始化总内存
        long totalMemory = Runtime.getRuntime().totalMemory();


        System.out.println("maxMemory"+maxMemory+"Byte\t"+(maxMemory/1024/1024)+"MB");
        System.out.println("---");
        System.out.println("totalMemory"+totalMemory+"Byte\t"+(totalMemory/1024/1024)+"MB");
        System.out.println("---end");
    }
}
plain 复制代码
maxMemory3736076288Byte	3563MB
---
totalMemory253231104Byte	241MB
---end

默认分配的总内存是电脑内存的1/4,初始化的内存是1/64

可以手动配置堆内存扩大;

问题及解决方式

:::info

有碰到过OOM(堆内存溢出)吗?

  1. 配置内存,扩大内存看下显示是否恢复;
  2. 分析内存,查看代码的那个地方出现了问题;

:::

JPofiler 分析OOM

出现OOM怎么办,如何排除?

  • 使用内存分析工具,看第几行代码出错:MAT,Jpofiler;
  • debug,查看代码出错的地方;

MAT、Jprofiler的作用:

  • 分析Dump内存文件,快速定位内存泄漏;
  • 获得堆中的数据;
  • 获得最大的对象;

GC垃圾回收

概念

JVM在进行GC时,并不是对三个区域统一回收,大部分时候,回收都是新生代

  • 新生代;
  • 幸存区:0,1;
  • 老年区

GC的两种:

  • 轻GC :普通GC
  • 重GC:全局GC

GC的题目

  1. JVM的内存模型和分区~ 详细到每个区放什么?
  2. 堆里面的 分区有哪些?伊甸园区 幸存区 form to,老年区,说说他们的特点!
  3. GC算法有哪些?标记清除法,标记压缩法没复制算法,引用计数器,具体怎么用的?
  4. 轻GC和重GC分别是什么时候发生?

引用计数法

将内存中的地址进行标注,标注如果又被引用的继续保留,如果有的地址被引用为0,则进行垃圾回收

复制算法

使用幸存区 0区和1区进行GC;

幸存区的form和to的判断标准:【谁空谁是to】;

流程:

  1. 每次GC之后,都会将伊甸园区的对象移动到幸存区;
  2. 在伊甸园区清空from区,变成to区,
  3. from区和to区来回切换,将两个区互相复制,期间为保持一个干净的空间,from会不断清空切换为to;
  4. 当GC次数到达一定的次数之后,会将对象复制到养老区;

可以使用 -XX: -XX:MaxTenuringThreshoid=9999 调整GC的任期时间,JVM调优的方式;

伊甸园区和幸存from区有内容,移动到To区,之后To是满的,变成From区;

From区将所有的东西copy到to区后,From变成了To;

  • 好处:没有内存碎片
  • 坏处:浪费了内存空间,多了一一般的空间被使用;
  • 在对象存活度较低的情况下,使用复制算法最佳!

标记清除算法

对 用过的对象做个标记;

扫描所有的对象,对对象进行标记;

之后清除 所有的没有标记的对象,进行清除;

  • 优点:不需要额外的空间;
  • 缺点:两次扫描,严重浪费时间,会产生内存碎片;

标记压缩算法

使用压缩,对内存空间再次扫描,向一段 移动存活的对象。

多了一层移动的成本

标记清除算法+标记压缩算法=标记清除压缩算法。

总结

  • 内存效率:复制算法 > 标记清除算法 > 标记压缩算法(时间复杂度)
  • 内存整齐度:复制算法=标记压缩算法 > 标记清除算法
  • 内存利用率:标记压缩算法=标记清除算法 > 复制算法

最优的算法?

只有最合适的算法,没有最好的算法; ---> 分代收集算法

年轻代:

  • 存活率底
  • 复制算法√

老年代:

  • 区域大,存活率高
  • 标记清除(内存碎片不是很多)+标记压缩算法

JMM

什么是JMM?

JMM(Java Mermory Model)java内存模型,是java的缓存一致性协议,用于定义读写的规则。

JMM定义了线程的工作内存和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,期间的每一个线程都是有个自己的本地内存(Local Memory)。

在JVM中只有一个主内存,线程执行后 会copy出来自己的一个内存区域,这是自己的工作区域;

之后数据会变得不一样,为了解决 数据不一致,共享内存的内容可见性的问题,出现了volatile关键字;

volatile关键字

volatile是Java中保证多线程可见性和禁止指令重排序的关键字‌,主要用于解决共享变量的内存同步问题。

JMM是一种抽象的观念;

相关推荐
lang201509283 小时前
掌握MyBatis Java API:高效操作数据库
java·数据库·mybatis
晨晖23 小时前
将聚合工程的ssm项目部署到本地tomcat
java·tomcat
Z_z在努力4 小时前
【杂类】理解 @Repository 和 Mapper 的关系
java·tomcat·mybatis
paopaokaka_luck4 小时前
基于SpringBoot+Vue的少儿编程培训机构管理系(WebSocket及时通讯、协同过滤算法、Echarts图形化分析)
java·vue.js·spring boot·后端·spring
雨过天晴而后无语4 小时前
Windchill中MVC选中事件级联另一MVC内容
java·javascript·html·mvc
胖咕噜的稞达鸭4 小时前
C++中的父继子承:继承方式实现栈及同名隐藏和函数重载的本质区别, 派生类的4个默认成员函数
java·c语言·开发语言·数据结构·c++·redis·算法
陈小桔4 小时前
Springboot之常用注解
java·spring boot·后端
code小毛孩5 小时前
如何简单的并且又能大幅度降低任务队列的锁粒度、提高吞吐量?
java·jvm·数据库
你不是我我6 小时前
【Java开发日记】请介绍类加载过程,什么是双亲委派模型?
java·开发语言