JVM快速入门

发布于:2025-05-02 ⋅ 阅读:(18) ⋅ 点赞:(0)

目录

前言:

1.JVM的位置

2.JVM的体系结构

3.类加载器

类加载器中的一些方法和细节:

4.双亲委派机制

5.沙箱安全机制

概念

原理

Java 沙箱安全机制

应用场景

6.Native

7.方法区:

8.PC寄存器

9.栈

10.三种JVM

HotSpot VM

OpenJ9 VM

Zing VM

11.堆

新生区:

养老区:

永久区:

补充说明:

常见问题:

12、GC

13、GC算法

13.1、什么是垃圾?

1.标记清除算法

2.标记压缩(整理)算法

3.复制算法

4.引用计数器算法(不用了几乎)

18、JMM

19、总结

结尾:


前言:

在瞬息万变的软件开发领域,Java虚拟机(JVM)如同数字世界中的万能翻译官,默默支撑着全球数十亿设备的稳定运行。从金融交易系统到大数据平台,从移动应用到物联网设备,JVM以其"一次编写,到处运行"的独特魅力持续焕发着蓬勃生机。本文将为开发者打开JVM世界的大门,用庖丁解牛的方式剖析这个支撑Java生态26载的核心引擎。我们将从字节码的奇妙旅程开始,穿越类加载器的迷雾森林,探秘运行时数据区的内存迷宫,最终抵达即时编译器的性能之巅。无论您是刚接触Java的新手,还是希望深入理解底层机制的中级开发者,这次快速入门之旅都将为您装备关键的JVM知识图谱。

1.JVM的位置

JRE包含了JVM


2.JVM的体系结构

本地方法栈(Native Method Stack)这个单词要稍微记一下

我们所说的JVM调优就是在方法区和堆内进行的 


3.类加载器

作用:加载class文件

举例:new Student();

student的引用放在了java的栈里,具体的人是放在堆里的

类加载器中的一些方法和细节:


4.双亲委派机制

举个例子:

Java.lang.String中明明有main方法,但是他却找不到。

这个时候就是所谓的双亲委派机制,最初是为了安全设计的。

应用程序加载器(当前)——>扩展加载器(Extension)——>根类加载器(Bootstrap)

我们这里new了一个String,然后他会看一下当前类加载器上看有没有String ,然后他会向上请求,看一下扩展加载器里有没有String ,然后再往上去根加载器里面找有没有。最终在根加载器中找到了String类,并且运行,但是根加载类上的String是默认的那个不可变类的String,里头肯定是没有mian方法的,所以他会报错:找不到main方法。

如果说在根类没有找到String 类,他就会返回去找扩展加载器,去执行扩展加载器的string 方法,同样的,扩展加载器没有就直接去应用程序加载器上找。

总结:

优先去根加载器找类来加载,没有的话依次往下找


5.沙箱安全机制

沙箱安全机制是一种重要的安全防护手段,主要用于为程序提供一个隔离的运行环境,以此来限制程序的访问权限,防止其对系统造成损害。下面从概念、原理、Java 沙箱安全机制、应用场景等方面为你详细介绍:

概念

沙箱就如同一个虚拟的 “容器”,程序在这个容器内运行,其所能访问的资源和执行的操作都被严格限制。即使程序存在漏洞或者被恶意利用,由于沙箱的隔离作用,它也无法对沙箱外部的系统资源(如文件系统、网络等)进行非法访问或破坏。

原理

沙箱安全机制的核心原理是通过访问控制和资源隔离来实现的。具体来说:

  • 访问控制:沙箱会依据预先设定的规则,对程序的各种操作进行细致检查,只有符合规则的操作才被允许执行。例如,一个沙箱可以限制程序只能访问特定目录下的文件,当程序试图访问其他目录的文件时,沙箱会阻止该操作。
  • 资源隔离:沙箱会为程序分配独立的系统资源,如内存、CPU 等,使程序的运行不会对沙箱外部的系统资源产生影响。同时,不同程序在各自的沙箱中运行,彼此之间也不会相互干扰。

Java 沙箱安全机制

Java 是最早引入沙箱安全机制的编程语言之一,其沙箱安全机制主要由以下几个部分组成:

  • 类加载器(ClassLoader):负责将 Java 类文件加载到 Java 虚拟机中。不同的类加载器具有不同的加载路径和权限,通过这种方式可以对类的来源进行控制,确保只有受信任的类才能被加载到系统中。
  • 字节码校验器(Bytecode Verifier):在类被加载时,字节码校验器会对类的字节码进行严格检查,确保字节码符合 Java 语言的规范和安全要求。例如,检查字节码是否会导致栈溢出、是否会访问非法的内存地址等。
  • 安全管理器(SecurityManager):是 Java 沙箱安全机制的核心组件,它会根据预先定义的安全策略,对程序的各种操作(如文件访问、网络连接等)进行实时监控和控制。当程序试图执行一个受限制的操作时,安全管理器会根据安全策略决定是否允许该操作执行。
  • 访问控制器(AccessController):用于在运行时动态地检查和控制程序对系统资源的访问权限。它会结合当前的安全上下文和安全策略,对每个资源访问请求进行评估,只有在权限允许的情况下才会允许访问。

应用场景

  • 浏览器插件:当你在浏览器中运行一些插件(如 Java Applet)时,浏览器会为这些插件创建一个沙箱环境,限制它们对本地系统的访问权限,防止插件中的恶意代码对用户的计算机造成损害。
  • 云服务:云服务提供商可以使用沙箱技术为不同的用户或应用程序提供隔离的运行环境,确保每个用户的应用程序只能访问自己的资源,不会对其他用户的应用程序产生影响。
  • 软件开发测试:在软件开发和测试过程中,可以使用沙箱来模拟不同的运行环境,对软件进行安全测试,发现和修复潜在的安全漏洞。

6.Native

说明:凡是带了 Native 关键字的,说明java的作用范围达不到了,会去调用底层c语言的库。

首先:他会先进入本地方法栈

然后:他会调用本地方法接口(JNT

JNT的作用:扩展java的使用,融合不同的编程语言为java提供服务。

JVM在内存区域中专门开辟了一块标记区域:本地方法栈(Native Method Stack)

作用:登记native方法,注意是登记,并不执行

最终执行的时候,加载本地库的方法经过本地接口(JNT)


7.方法区:

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


8.PC寄存器

每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区的方法字节码(用来存储指向像一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。


9.栈

栈内存,主管程序的运行,生命周期和线程同步,线程结束时,栈内存也释放,所以对于栈来说,不存在垃圾回收机制

一旦线程结束,栈就Over

一旦栈满:栈就会内存溢出,会报error

10.三种JVM

HotSpot VM

  • 简介:是目前最广泛使用的 JVM,由 Sun Microsystems(后被 Oracle 收购)开发。它在 JDK 1.3 之后成为默认的 JVM。
  • 特点
    • 自适应优化:具有即时编译(Just-In-Time Compilation,JIT)功能,能在运行时分析代码的执行频率,将频繁执行的代码编译为本地机器码,提高执行效率。同时,它还能根据应用的运行情况动态调整堆内存的大小,以适应不同的内存需求。
    • 垃圾回收算法多样:采用了多种垃圾回收算法,如标记 - 清除、复制、标记 - 压缩等,根据不同的堆内存区域(新生代和老年代)选择合适的算法,以优化垃圾回收的性能,减少停顿时间。
    • 成熟稳定:经过多年的发展和大量实践的检验,具有高度的稳定性和可靠性,在各种不同的操作系统和硬件平台上都有良好的表现。

OpenJ9 VM

  • 简介:最初是由 IBM 开发的 JVM,后来捐赠给了开源社区。它以其高效的内存管理和低延迟特性而受到关注。
  • 特点
    • 内存管理高效:采用了一种称为 “OMR(Open Memory Recycling)” 的内存管理技术,能够更精细地管理堆内存,减少内存碎片的产生,提高内存的利用率。这使得在内存受限的环境中,OpenJ9 VM 能够表现出更好的性能。
    • 可扩展性和可定制性:具有良好的可扩展性,允许开发人员根据具体的应用需求对 JVM 进行定制。例如,可以通过配置参数来调整垃圾回收的策略、线程管理方式等,以优化应用在特定场景下的性能。
    • 与 OpenJDK 集成良好:作为 OpenJDK 项目的一部分,与 OpenJDK 的其他组件紧密集成,能够充分利用 OpenJDK 的开源生态系统和社区支持,方便开发人员进行开发和维护。

Zing VM

  • 简介:由 Azul Systems 公司开发,是一款商业化的 JVM,旨在为大规模企业级应用提供高性能和低延迟的运行环境。
  • 特点
    • 超大堆内存支持:能够支持非常大的堆内存,可轻松管理数十 GB 甚至上百 GB 的堆空间,这对于处理大规模数据和高并发的企业级应用非常有优势。例如,在一些大数据处理和金融交易系统中,能够避免频繁的垃圾回收导致的停顿,保证系统的稳定运行。
    • 低延迟垃圾回收:采用了独特的垃圾回收算法,如 “C4(Concurrent Continuously Compacting Collector)” 垃圾回收器,实现了几乎无停顿的垃圾回收,大大降低了应用的响应延迟,提高了系统的实时性。
    • 性能优化技术:运用了多种先进的性能优化技术,如代码缓存管理、锁优化等,进一步提高了应用的执行效率。同时,Zing VM 还提供了丰富的监控和管理工具,方便运维人员对 JVM 的运行状态进行实时监控和调优。

11.堆

一个JVM只有一个堆内存,堆内存的大小是可以调节的。

堆区内部:

新生区:

  • 类诞生和成长的地方,甚至死亡;
  • 伊甸园,所有的对象都是在伊甸园区new 出来的
  • 幸存者(0 1区)

养老区:

真理:99%的对象都是临时对象,在新生区就被回收了,所以一般的对象到不了养老区。

永久区:

这个区域常驻内存的,用来存放JDK自身携带的Class对象。Interface元数据,存储的是java运行时的一些环境或者类信息,这个区域不存在垃圾回收,关闭虚拟就会释放这个区域的内存。

一个启动类,加载了大量的第三方jar包。Tomcat部署了太多的应用,大量的动态生成反射类。不断地被加载。直到内存满了,就会出现OOM;

  • jdk1.6以前:永久代常量池是在方法区
  • jdk1.7 :永久代,但是慢慢的退化了,去永久化,常量池在堆中
  • jdk1.8 :  无永久化,常量池在元空间

补充说明:

  • jdk8以后,元空间其实不在堆区,而是在本地内存,逻辑上他在堆区,但是物理地址上不在。
  • 所以堆区的内存:新生区+养老区

常见问题:

在一个项目中,突然出现了OOM故障,那么该如何排查,研究为什么出错?

思路:

  • 要能看到代码第几行出错,我们就更好的排查,内存快照分析工具:MAT,Jprofiler
  • 一行一行的Debug

MAT,Jprofiler的作用:

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

Jprofiler下载网址 https://www.ej-technologies.com/download/jprofiler/files

Jprofiler9.2激活码

L-Larry_Lau@163.com#23874-hrwpdp1sh1wrn#06201


在IDEA中下载Jprofiler插件并重启,在工具栏会有图标如下:

安装好我们的Jprofiler工具!

测试: 我们故意造成内存溢出

public class JprofilerTest {

    // 1m
    byte[] arr = new byte[1*1024*1024];

    public static void main(String[] args) {

        ArrayList list = new ArrayList<String>();

        // -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

        while (true){
            list.add(new JprofilerTest());
        }

    }
}

抛出异常

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.tian.jvm.JprofilerTest.<init>(JprofilerTest.java:16)
	at com.tian.jvm.JprofilerTest.main(JprofilerTest.java:25)

之后我们需要借用Jprofiler来dump内存错误!

同上 我们在VM options中输入 指令 : -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

意思就是如果出现了内存溢出错误 dump出来!

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid9740.hprof ...
Heap dump file created [7683930 bytes in 0.034 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.tian.jvm.JprofilerTest.<init>(JprofilerTest.java:16)
	at com.tian.jvm.JprofilerTest.main(JprofilerTest.java:25)

注意 Dumping heap to java_pid9740.hprof …

表明已经dump下来了,那我们就需要找到这个文件在哪,于此同时,我们在idea的左边文件栏回生产一个文件夹

我们右键我们的类找到我们dump的文件

向上找!找到生成的hprof文件即我们dump下的文件

 

由于我们已经安装了Jprofiler Ap,我们直接双击,运行!

我们可以看到 list对象分配了90%的内存,太离谱了简直,当然,这是我们的测试!

Jprofiler提示的错误位置!

这么一来,我们就很方便的找到了哪里,是什么原因造成的OOM!方便解决

12、GC

GC在垃圾回收时,不是对这三个区域统一回收。大部分都是,回收新生代

新生代
幸存区(from to)
老年区


13、GC算法

13.1、什么是垃圾?

垃圾是指在运行程序中没有任何指针指向的对象,这个对象就是回收的垃圾!

如果不对其进行垃圾回收,那么内存中会一直保存这些垃圾对象,知道应用程序结束,被保留的对象无法用其空间,甚至会造成内存溢出!

1.标记清除算法

我们来脑补一下,见名知意,我们没用一次对象,就给他进行一次标记!然后清除!

好处:不需要额外的空间!
坏处:两次扫描严重浪费时间! 会产生内存碎片


2.标记压缩(整理)算法

标记压缩算法是来优化标记清除算法的!、

  • 好处:没有了内存碎片!
  • 坏处:多了移动成本!


3.复制算法

  • 好处: 没有内存碎片!
  • 坏处:浪费内存空间 ! 因为他要保证to是空的! 极端情况下(对象不会被回收!)
  • 最佳使用场景:对象存活度较低! —> 新生区


4.引用计数器算法(不用了几乎)

18、JMM

java内存模型 (Java Memory Model )

jmm是类似于MSI等 缓存一致性协议,用于定义数据读写的规则!

JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。

解决共享对象可见性: voliate关键字

深入了解请看 https://blog.csdn.net/jinxinxin1314/article/details/106700414?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161744592816780265434767%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=161744592816780265434767&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-106700414.first_rank_v2_pc_rank_v29&utm_term=JMM

19、总结

内存效率(时间复杂度):复制算法 > 标记清除算法 > 标记压缩算法

内存整齐度: 复制算法 = 标记压缩算法 > 标记清除算法

内存利用率: 标记压缩算法 = 标记清除算法 > 复制算法

没有最好的算法,只有适合的算法! 分代收集算法

年轻代: 存活率低 使用复制算法

老年代: 存活率高 使用标记清除(内存碎片不是很多)+ 标记压缩混合(内存碎片多了之后压缩一次)

结尾:

当我们完成这次JVM探秘之旅,回望这个由200万行C++代码构筑的精密系统,会发现技术之美往往存在于抽象与实现的完美平衡。从.class文件的字节码舞蹈到垃圾回收器的时空博弈,从方法区的类型圣殿到本地方法栈的跨语言桥梁,JVM的每个设计决策都在诉说着软件工程的智慧结晶。希望这篇入门指南能成为您深入JVM世界的罗盘,当您下次面对内存溢出告警或性能瓶颈时,不再是面对黑盒的迷茫,而是带着系统级的认知从容应对。技术的精进永无止境,JVM仍在持续进化:从ZGC的革命性低延迟,到Valhalla项目的值类型革新,这个绿色咖啡杯图标背后的世界永远充满惊喜。让我们保持好奇,继续前行,在Java生态的星辰大海中书写新的篇章。


网站公告

今日签到

点亮在社区的每一天
去签到