【JVM快速入门篇】

本笔记内容为狂神说JVM快速入门篇部分

目录

一、JVM的位置 

二、JVM体系结构

三、类加载器(ClassLoader)

四、双亲委派机制

双亲委派机制的作用

五、沙箱安全机制

六、Native

​编辑Native Method Stack 本机方法栈

Native Ilnterface本地接口

七、PC寄存器

八、方法区Method Area

九、栈(stack)

十、三种JVM

十一、堆(Heap)

1、新生区、老年区

2、永久区 

十二、堆内调优 

十三、GC垃圾回收机制

GC常见面试题目 

算法 

1.引用计数法

2.复制算法

3.标记清除法

4.标记压缩法

总结 

十四、JMM 


面试:

请你谈谈你对JVM的理解? java8虚拟机和之前的变化更新?

什么是OOM,什么是栈溢出StackOverFlowError?怎么分析?

JVM的常用调优参数有哪些?

内存快照如何抓取,怎么分析Dump文件?

谈谈JVM中,类加载器你的认识?

1. JVM的位置

2. JVM的体系结构

3.类加载器

4.双亲委派机制

5.沙箱安全机制(了解)

6. Native(重点)

7. PC寄存器(了解)

8.方法区

9.栈

10.三种JVM

11.堆

12.新生区、老年区

13.永久区

14.堆内存调优

15. GC

        1.常用算法

16. JMM

17.总结

一、JVM的位置 


操作系统之上,java程序之下

是JRE的一部分,一个虚构出来的计算机 

二、JVM体系结构


Java虚拟机(JVM)是Java程序的运行环境,它负责将Java源代码编译成字节码文件,并在运行时将字节码文件解释成机器码并执行。 

JVM的体系结构主要由以下三部分组成:

1.类加载器(Class Loader):负责将字节码文件加载到JVM中。类加载器按照特定的顺序加载class文件,首先加载程序的入口类,然后递归加载该类所依赖的其他类。

2.运行时数据区(Runtime Data Area):JVM在运行时会为每个线程创建一个运行时数据区,用于存储线程的状态信息和变量值等。运行时数据区主要包括方法区、堆、虚拟机栈、本地方法栈和程序计数器等。

3.执行引擎(Execution Engine):JVM的执行引擎负责将字节码文件解释成机器码并执行。执行引擎有两种实现方式:解释执行和即时编译。解释执行是将字节码文件逐条解释成机器码并执行,而即时编译是将字节码文件编译成本地机器码,然后执行本地机器码。

总之,JVM的体系结构主要由类加载器、运行时数据区和执行引擎三部分组成。这些组件共同协作,使得Java程序能够在不同的平台上运行,并且具有良好的可移植性和安全性。 

三、类加载器(ClassLoader)


加载class文件 

JVM提供了三层类加载器,分别是:

1. 启动类加载器(Bootstrap ClassLoader):它是JVM内置的类加载器,负责加载Java的核心库,如rt.jar、resources.jar等,它是整个类加载器层次结构的根加载器,它不是Java类,因此无法在Java代码中直接获取到该类加载器。

2. 扩展类加载器(Extension ClassLoader):它负责加载Java的扩展库,默认加载JAVA_HOME/jre/lib/ext目录下的jar包或者-Djava.ext.dirs指定的目录中的jar包。

3. 应用程序类加载器(Application ClassLoader):它也称为系统类加载器,负责加载应用程序classpath目录下的类和jar包,是最常用的类加载器。该类加载器可以通过ClassLoader.getSystemClassLoader()方法获取到。

这三层类加载器构成了一个层次结构,每个类加载器都有自己的父类加载器,当需要加载一个类时,JVM会先委托父类加载器加载,如果父类加载器无法加载该类,则由子类加载器进行加载。这种委托机制保证了类的唯一性和安全性。

CustomClassLoader(用户自定义类加载器):java编写,用户自定义的类加载器,可加载指定路径的class文件 JAVA编写,用户自定义的类加载器,可加载指定路径的类文件。

四、双亲委派机制


当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。

双亲委派机制的作用

1、防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
2、保证核心.class不能被篡改。通过委托方式,不会去篡改核心.class,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。

案例: 

五、沙箱安全机制


沙箱安全机制是一种安全措施,用于在运行不可信代码时保护系统的安全。它通过限制代码的访问权限和资源使用来保护系统,防止恶意代码对系统造成损害。 

沙箱安全机制通常包括以下几个方面:

1.代码执行环境隔离:将不可信代码运行在一个独立的、受控制的环境中,与系统其它部分隔离开来。

2.权限控制:对不可信代码的访问权限进行限制,只允许其访问必要的资源。

3.资源限制:限制不可信代码的资源使用,防止其占用系统资源导致系统崩溃或服务不可用。

4.安全检查:对不可信代码进行安全检查,检查其是否存在安全漏洞或恶意行为。

Java中的沙箱安全机制是通过安全管理器(SecurityManager)来实现的。安全管理器会对Java程序访问系统资源的行为进行控制,例如文件、网络、系统属性等。通过在Java程序中设置安全策略(Policy),可以控制Java程序能够访问哪些资源,从而实现沙箱安全机制。

六、Native


Native Method Stack 本机方法栈

它的具体做法是Native Method Stack中登记native方法,在( Execution Engine )执行引擎执行的时候加载NativeLibraies[本地库]。

Native Ilnterface本地接口

本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C、C++程序,Java在诞生的时候是C、C++横行的时候,想要立足,必须有调用C、C++的程序,于是就在内存中专门开辟了块区域处理标记为native的代码,它的具体做法是在Native Method Stack 中登记native方法,在( Execution Engine )执行引擎执行的时候加载Native Libraies。 

目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机或者Java系统管理生产设备,在企业级应用中已经比较少见。因为现在的异构领域间通信很发达,比如可以使用Socket通信,也可以使用WebService等等,不多做介绍! 

七、PC寄存器


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

八、方法区Method Area


方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间

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

九、栈(stack)


栈:先进后出
队列:先进先出( FIFO : First Input First Output )

栈:栈内存,主管程序的运行,生命周期和线程同步
线程结束,栈内存也就是释放,对于栈来说,不存在垃圾回收问题

—旦线程结束,栈就Over! 

栈内存中:
8大基本类型+对象引用+实例的方法

栈+堆+方法区:交互关系 

 

栈运行原理:栈帧
栈满了:StackOverflowError  

栈帧图解栈底部子帧指向上一个栈的方法上一个栈的父帧指向栈底部方法 

十、三种JVM


  • Sun公司HotSpot Java HotspotTM64-Bit Server VM (build 25.181-b13,mixed mode)
  • BEAJRockit
  • lBM J9VM

我们学习都是: Hotspot


十一、堆(Heap)


Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的。
类加载器读取了类文件后,一般会把什么东西放到堆中?
类、方法、常量、变量,保存我们所有引用类型的真实对象

堆内存中还要细分为三个区域:

  • 新生区(伊甸园区)Young/New
  • 养老区old
  • 永久区Perm (JDK1.8中已经被移除,被方法区的元空间取代)

堆内存详细划分

GC垃圾回收,主要是在伊甸园区和养老区

假设内存满了,OOM,堆内存不够! java.lang.OutOfMemoryError:Java heap space

永久存储区里存放的都是Java自带的例如lang包中的类如果不存在这些,Java就跑不起来了

在JDK8以后,永久存储区改了个名字(元空间)   元空间逻辑上存在 物理上不存在

1、新生区、老年区


新生区

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

  • 伊甸园满了就触发轻GC,经过轻GC存活下来的就到了幸存者区
  • 幸存者区满之后意味着新生区也满了,则触发重GC,经过重GC之后存活下来的就到了养老区。
  • 重GC过后若能存放新对象,则创建成功,若失败,则出现OOM错误。

常见的OOM错误有三种:

​ java.lang.OutOfMemoryError: Java heap space——堆内存溢出

​ java.lang.OutOfMemoryError: PermGen space——元空间溢出

​ java.lang.StackOverflowError——栈溢出,一般是由于死循环或递归引起这些可以通过JVM调优进行改进。

真理:经过研究,百分之99的对象都是临时对象,大多在新生区都被消灭了

2、永久区 


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

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

元空间:逻辑上存在,物理上不存在(因为存储在本地磁盘内)所以最后并不算在JVM虚拟机内存中 

jvm方法区和元空间的关系?

  • JVM方法区和元空间都是Java虚拟机中用于存储类信息的区域,但是它们的实现和作用略有不同。
  • 方法区是JVM规范中定义的一个内存区域,用于存储已加载的类信息、常量、静态变量、即时编译器编译后的代码等(元空间也类似)。它是线程共享的,是JVM内存中的一部分。在JDK8之前,方法区是使用永久代实现的,但是永久代的大小固定,不能动态调整,容易导致内存溢出。而在JDK8及以后版本中,方法区被替换为了元空间(Metaspace),元空间是使用本地内存实现的,可以动态调整大小,避免了永久代的问题。
  • 总之,方法区和元空间都是Java虚拟机中用于存储类信息的区域,但是元空间是JDK8及以后版本中替代永久代的新实现,它使用本地内存实现,可以动态调整大小,同时还提供了一些新的特性。 

十二、堆内调优 


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

  • 能够看到代码第几行出错:内存快照分析工具,MAT, Jprofiler
  • Dubug,一行行分析代码!

MAT, Jprofiler作用

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

MAT是eclipse集成使用,这里不多介绍

Jprofile的使用

  1. 在idea中下载jprofile插件
  2. 联网下载jprofile客户端
  3. 在idea中vM参数中写参数-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
  4. 运行程序后在jprofile客户端中打开找到错误告诉哪个位置报错

命令参数详解

  • -Xms 设置初始化内存分配大小/164
  • -Xmx 设置最大分配内存,默以1/4
  • -XX:+PrintGCDetails //打印Gc垃圾回收信息
  • -XX:+HeapDumpOnOutOfMemoryError //oom DUMP

十三、GC垃圾回收机制


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

  • 新生代
  • 幸存区(form, to)
  • 老年区

GC两种类:轻GC(普通的GC),重GC(全局GC)

GC常见面试题目 

JVM的内存模型和分区,详细到每个区放什么?

堆里面的分区有哪些?Eden,from,to,老年区 说说他们的特点

在Java堆中,主要分为新生代和老年代两个部分。其中,新生代又可以分为Eden区、Survivor区1和Survivor区2 

  1. Eden区:Eden区是新生代中最大的一块区域,用于存放新创建的对象。当Eden区满时,会触发Minor GC,将Eden区和Survivor区中的存活对象复制到另一个Survivor区中。Eden区的特点是对象生命周期短,大多数对象都是在Eden区被创建和销毁的,因此Eden区的回收比较频繁。

  2. Survivor区1和Survivor区2:Survivor区是新生代中的一个较小的区域,用于存放Eden区和另一个Survivor区中的存活对象。当Survivor区满时,会触发Minor GC,将Survivor区中的存活对象复制到另一个Survivor区中。Survivor区的特点是存活对象的生命周期较长,因此Survivor区的回收比较少。

  3. From区和To区:From区和To区是Survivor区的两个部分,它们在不同的时间扮演不同的角色。当一次Minor GC完成后,From区中存活的对象会被复制到To区中,同时From区和To区的角色会互换。From区和To区的特点是它们的大小固定,不会随着对象的数量增加而增加。

  4. 老年区:老年区是Java堆中的一块较大的区域,用于存放长生命周期的对象。当对象在新生代中经过多次复制后还存活,就会被晋升到老年区中。老年区的特点是对象生命周期长,因此老年区的回收比较少。

GC的算法有哪些?

  • 标记清除
  • 标记压缩
  • 复制算法
  • 引用计数器

轻GC和重GC分别在什么时候发生? 

轻GC和重GC是指JVM进行垃圾回收时的两种不同方式。

轻GC通常发生在新生代,也就是Eden区和Survivor区中。当Eden区或Survivor区中的对象达到一定程度时,会触发轻GC。轻GC的过程比较快速,只会扫描并回收新生代中的无用对象,存活的对象会被复制到Survivor区或老年代中。轻GC通常不会影响应用程序的执行,因为它只会回收新生代中的对象,而新生代中的对象通常比较少。

重GC通常发生在老年代,也就是存活时间较长的对象所在的区域。当老年代中的对象达到一定程度时,会触发重GC。重GC的过程比较耗时,需要扫描整个堆内存,并回收无用对象。重GC会暂停应用程序的执行,因为在重GC的过程中,应用程序无法访问堆内存。因此,重GC的发生频率应该尽量降低,以减少对应用程序的影响。 

算法 

1.引用计数法

GC算法是指在JVM中用于垃圾回收的算法。引用计数法是一种GC算法,它的基本思想是通过计数器来记录每个对象被引用的次数。当对象被引用时,计数器加1;当引用失效时,计数器减1。当计数器的值为0时,说明该对象已经无法被访问,可以被回收

不是很常用

2.复制算法

复制算法是一种GC算法,它的基本思想是将堆内存分成两个部分,每次只使用其中一个部分,当这个部分的内存用完了,就将还存活的对象复制到另外一个部分中,然后清空原来的部分,这样就完成了垃圾回收。复制算法的优点是实现简单、高效,可以有效地避免内存碎片的问题,但是它也有缺点,即需要两倍的内存空间,且无法处理大对象的情况。因此,在实际应用中,复制算法通常用于新生代的垃圾回收。 

好处:没有内存碎片

坏处:浪费了内存空间,多了一半空间永远是空的

复制算法最佳使用场景:对象存活度较低的时候

3.标记清除法

标记清除法是一种GC算法,它的基本思想是在垃圾回收时,首先标记所有仍然被引用的对象,然后清除所有未被标记的对象。具体实现时,可以从根对象开始,遍历整个对象图,将所有可达对象标记为“已标记”,然后遍历整个堆,将未被标记的对象清除。标记清除算法的优点是可以处理任意类型的对象,且不需要额外的内存空间。但是它也有缺点,即无法处理内存碎片的问题,可能会导致内存空间的浪费。因此,在实际应用中,标记清除算法通常用于老年代的垃圾回收。  

优点:不需要额外空间

缺点:两次扫描,严重浪费时间,会产生内存碎片

4.标记压缩法

标记压缩法是一种GC算法,它的基本思想是在垃圾回收时,首先标记所有仍然被引用的对象,然后将它们压缩到堆的一端,清除堆尾部分的所有对象。具体实现时,可以从根对象开始,遍历整个对象图,将所有可达对象标记为“已标记”,然后将所有已标记对象依次复制到堆的一端,并记录它们在新堆中的地址,最后清除堆的尾部。标记压缩算法的优点是可以解决内存碎片的问题,且不需要额外的内存空间。但是它也有缺点,即需要在垃圾回收时进行复制和压缩操作,可能会影响程序的性能。因此,在实际应用中,标记压缩算法通常用于新生代和老年代的垃圾回收。

优点:不会产生内存碎片

缺点:需要三次扫描

总结 

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

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

老年代:区域大,存活率高,使用标记清除+标记压缩混合实现

十四、JMM 


JMM(Java Memory Model)是Java内存模型的缩写,是Java程序中处理多线程并发访问共享内存的规范。JMM规定了在何时,如何以及何种方式访问共享内存,以确保多线程程序的正确性。

JMM规定了一组规则和语义,以确保多线程程序中的内存访问操作的可见性、有序性和原子性。其中,可见性指的是一个线程对共享变量的修改对另一个线程是可见的;有序性指的是程序中的指令执行顺序必须符合一定的规则;原子性指的是一个操作必须是不可分割的整体,要么全部执行成功,要么全部执行失败。

JMM规范包含了一些基本概念,如主内存、工作内存、内存屏障等,以及一些操作,如读操作、写操作、volatile变量的读写操作等。JMM规范的正确理解和应用对于编写正确的多线程程序非常重要。

它是干嘛的?​​​​​​​

缓存一致性协议,用于定义数据读写的规则

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

解决共享对象可见性这个问题: volatile(当一个变量被volatile修饰时,它的值会被强制刷新到主内存中,其他线程在读取这个变量时会从主内存中读取最新的值,从而保证了可见性。)

JMM和JVM的区别在于:

  1. JMM是Java程序中多线程并发访问共享内存的规范,而JVM是Java程序的运行环境。

  2. JMM定义了Java程序中多线程并发访问共享内存的行为,包括内存模型、内存屏障、变量可见性、原子性、有序性等,而JVM实现了JMM规范中定义的Java内存模型和内存屏障等机制。

  3. JMM是Java程序的理论基础,JVM是Java程序的具体实现。在编写Java程序时,我们需要遵循JMM规范,而在运行Java程序时,我们需要使用JVM来执行程序。

总之,JMM和JVM是密切相关的,但是它们是不同的概念。JMM规范定义了Java程序中多线程并发访问共享内存的行为,而JVM实现了JMM规范中定义的Java内存模型和内存屏障等机制。

结束!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值