jvm-狂神课程

一、JVM

JVM就是Java虚拟机,Java虚拟机就是JVM

1. JVM位置

6d6c0eefe96d780c0e37be2427ea91a0

  • 1、Java程序(跑的环境是在jvm(虚拟机)跑的,也可以说是在jre上跑的)java运行是需要在特定的环境的也就是这个jre这种。

  • 2、jvm(也就是jrejre包括了jvm):jvm是用c写的

  • 3、操作系统(也是个软件

  • 4、硬件体系(Intel,sapc)

2. JVM体系结构

2.1. jvm结构图

image

image

  • 1、java编译 - 命令javac

  • 2、编译生成Class File

  • 3、类装载器(类加载器Class Loader

  • 4、运行时数据区:(类加载完成后进入这个运行时数据区:Runtime Data Are)运行时异常是不可捕获的。这是在类加载器后的产物!运行时数据区里面就有(方法区(Method Area),java栈(stack),本地方法栈(Native Method Stack),堆(heap) ,程序计数器(pc寄存器))

    • 本地方法栈: 本地方法栈也是线程私有的数据区,本地方法栈存储的区域主要是 Java 中使用 native 关键字修饰的方法所存储的区域。
    • 程序计数器:程序计数器也是线程私有的数据区,这部分区域用于存储线程的指令地址,用于判断线程的分支、循环、跳转、异常、线程切换和恢复等功能,这些都通过程序计数器来完成。
    • 方法区:方法区是各个线程共享的内存区域,它用于存储虚拟机加载的 类信息、常量、静态变量、即时编译器编译后的代码等数据。
    • :堆是线程共享的数据区,堆是 JVM 中最大的一块存储区域,所有的对象实例都会分配在堆上。JDK 1.7后,字符串常量池从永久代中剥离出来,存放在堆中。
    • 运行时常量池:运行时常量池又被称为 Runtime Constant Pool,这块区域是方法区的一部分,它的名字非常有意思,通常被称为 非堆。它并不要求常量一定只有在编译期才能产生,也就是并非编译期间将常量放在常量池中,运行期间也可以将新的常量放入常量池中,String 的 intern 方法就是一个典型的例子。
  • 5、本地方法接口(native)(和本地方法库相连),同时这一层还有执行引擎

2.2. jvm垃圾回收

垃圾回收,指的的堆内存的垃圾回收,垃圾回收机制简称GC

程序在运行过程中,会产生大量的内存垃圾(一些没有引用指向的内存对象都属于内存垃圾,因为这些对象已经无法访问,程序用不了它们了,对程序而言它们已经死亡),为了确保程序运行时的性能,java虚拟机在程序运行的过程中不断地进行自动的垃圾回收(GC)。

垃圾收集系统是Java的核心,也是不可少的,Java有一套自己进行垃圾清理的机制,开发人员无需手工清理

9d4f28349422c9714bbcb35604bc08ff

为什么java栈,本地方法栈,程序计数器是不会有垃圾回收的?

因为他们是栈,最终是要出栈的,若是上面的是一个垃圾阻塞了,那他就无法出栈了,Jvm调优也就是垃圾回收,调的就是方法区和堆,99%是调堆。

手动执行GC

System.gc(); // 手动回收垃圾

finalize方法作用

  • 1、finalize()方法是在每次执行GC操作之前时会调用的方法,可以用它做必要的清理工作。

  • 2、它是在Object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。

public class Test {
	public static void main(String[] args) {
		Test test = new Test();
		test = null;
		System.gc(); // 手动回收垃圾
	}

	@Override
	protected void finalize() throws Throwable {
		// gc回收垃圾之前调用
		System.out.println("gc回收垃圾之前调用的方法");
	}
}

2.3. jvm调优

505a192a9f4b519eec155d0cfd56b0ca

二、类加载器

类加载器: 负责把class文件加载到内存中

类加载机制

Java 虚拟机负责把描述类的数据从 Class 文件加载到系统内存中,并对类的数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这个过程被称之为 Java 的类加载机制。

1. 类加载的过程

一个类从被加载到虚拟机内存开始,到卸载出内存为止,一共会经历下面这些过程。

798bd696514621e2b5ce1605840e61f4

类加载机制一共有五个步骤,分别是加载、链接、初始化、使用和卸载阶段,这五个阶段的顺序是确定的。

其中链接阶段会细分成三个阶段,分别是验证、准备、解析阶段,这三个阶段的顺序是不确定的,这三个阶段通常交互进行。解析阶段通常会在初始化之后再开始,这是为了支持 Java 语言的运行时绑定特性(也被称为动态绑定)。

1.1.加载

1、获取class文件加载成二进制字节流
2、把该文件的编码结构-->运行时的内存结构
3、在内存中生成该类的一个Class对象

1.2.链接

验证
	确保 Class 文件的字节流中的内容符合《Java虚拟机规范》中的要求
准备
	为类中的变量分配内存并设置其初始值
解析 
	相当于翻译的过程,Java虚拟机将常量池内的符号引用替换为直接引用的

1.3.初始化

类加载过程的最后一个步骤,在之前的阶段中,都是由 Java 虚拟机占主导作用,但是到了这一步,却把主动权移交给应用程序。

1.4.使用

初始化之后的代码由 JVM 来动态调用执行

1.5.卸载

当代表一个类的 Class 对象不再被引用,那么 Class 对象的生命周期就结束了,对应的在方法区中的数据也会被卸载。
    
JVM 自带的类加载器装载的类,是不会卸载的,由用户自定义的类加载器加载的类是可以卸载的。

2.类加载器的分类

  • 虚拟机自带 的加载器

  • 启动类加载器 - null,获取不到,底层c++编写,向上委托到这里

  • 扩展类加载器 - ExtClassLoder

  • 应用程序类加载器 - AppClassLoder

3.双亲委派机制

  • 1、类加载器接收到一个加载请求时,他会委派给他的父加载器,实际上是去他父加载器的缓存中去查找是否有该类,如果有就加载返回,如果没有则继续委派给父类加载,直到顶层类加载器。
  • 2、如果顶层类加载器也没有加载该类,则会依次向下查找子加载器的加载路径,如果有就加载返回,如果都没有,则会抛出异常。

image

4.沙箱安全机制

如果我们要编写一个和核心类库全限定命一模一样的类,JDK为了保证核心代码的一个安全 阻止你的代码和全限定名相同 
    
优点:保证原生JDK的安全,保证核心源代码 防止API被篡改,避免重复加载类

5.Native方法区

5.1. native

凡是使用了native关键字的,说明Java的作用范围已经达不到了,它会去调用底层的C语言的库。

  1. 进入本地方法栈。
  2. 调用本地方法接口。

5.2. 方法区

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

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

5.3. PC寄存器

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

6.java栈(虚拟机栈)

6.1. 栈的作用

栈是运行时的单位 程序如何运行 如何处理数据

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

6.2.栈帧

a.概念 
	每个线程都有自己的栈,栈中的数据以栈帧的格式存在,每个方法对应一个栈帧 
    
b.存储内容 
	局部变量表
		每个方法的局部变量,数组结构,存放的形参 
		没有线程安全问题 因为数据是线程私有的
	操作数栈
		根据指令进行入栈 出栈 
    
c.原理
	先进后出

6.3.栈存储的东西

8大基本类型、对象引用,实例的方法

6.4.五道面试题

1.举例栈溢出的情况
  • 1、函数中采用了很大的结构体,或者数组;
  • 2、有很深的函数调用,或者递归调用
  • 3、访问了非法的地址

通过-Xss1m调整栈空间

2.调整栈大小,就能保证不出现溢出吗?

不能

3.分配的栈内存越大越好吗?

不是

4.垃圾回收是否会涉及到虚拟机栈?

不会

5.方法中定义的局部变量是否线程安全?

是的

7.堆

堆内存的大小是可以调节的

7.1. 三种JVM

  1. Sun公司的HotSpot。(java -version查看)
  2. BEA的JRockit
  3. IBM的J9VM

7.2.堆的概述

一个JVM实例对应一个进程实例,一个JVM实例有一个运行时数据区(Runtime)

一个Runtime就有一个独立的方法区和堆

一个进程有多个线程,多个线程共享一个方法区和堆空间

一个线程拥有自己独立的程序计数器/本地方法栈/虚拟机栈

为了解决多个线程访问出现线程不安全问题–>TLAB(线程私有空间)

垃圾回收只会在堆(方法区)当中进行回收

7.3.堆内存中细分

image

主要区别在于jdk8以前是新生区、养老区、永久区。jdk8即以后使用元空间代替了永久区。

227ee50e966adf60e3b7e2088d9679dc

1.新生区

新生区又叫做伊甸园区,包括:伊甸园区、幸存0区、幸存1区。

新生区:老年区=1:2

新生区=eden:from:to 【谁空谁是to】

创建对象在eden

2.永久区

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

什么情况下,在永久区就崩了?

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

什么是老年区和元空间??
方法区是一种规范,不同的虚拟机厂商可以基于规范做出不同的实现,老年区和元空间就是出于不同jdk版本的实现。
方法区就像是一个接口,老年区与元空间分别是两个不同的实现类。
只不过老年区是这个接口最初的实现类,后来这个接口一直进行变更,直到最后彻底废弃这个实现类,由新实现类—元空间进行替代。

jdk1.8之前:

694f8dbf7bb31c9833c915c137bc8285

jdk1.8以及之后:在堆内存中,逻辑上存在,物理上不存在(元空间使用的是本地内存)

796ca080b420c447170d30c6b7dc7351

4.常量池

在jdk1.7之前,运行时常量池+字符串常量池是存放在方法区中,HotSpot VM对方法区的实现称为永久代。

38f66c4b577cb78d826bea4f50a92e2f

在jdk1.7中,字符串常量池从方法区移到堆中,运行时常量池保留在方法区中。

881a3ef67d3040347e9c1c249237d6d0

jdk1.8之后,HotSpot移除永久代,使用元空间代替;此时字符串常量池保留在堆中,运行时常量池保留在方法区中,只是实现不一样了,JVM内存变成了直接内存。

e9892b982ef64b9edc2f013d8a068090

8.GC垃圾回收

8.1. 垃圾回收的区域

主要都是在方法区和堆中,且99%都是在堆中

8.2.引用计数法

每个对象在创建的时候,就给这个对象绑定一个计数器。每当有一个引用指向该对象时,计数器加一;每当有一个指向它的引用被删除时,计数器减一。这样,当没有引用指向该对象时,计数器为0就代表该对象死亡,这时就应该对这个对象进行垃圾回收操作。

优点:引用计数算法的实现简单,判定效率高,但建议不要使用

缺点:术无法解决对象之间的循环引用问题

24e10a4b449aa453a938d0c25786e921

8.3.复制算法

该算法将内存平均分成两部分,然后每次只使用其中的一部分,当这部分内存满的时候,将内存中所有存活的对象复制到另一个内存中,然后将之前的内存清空,只使用这部分内存,循环下去。

这个算法与标记-整理算法的区别在于,该算法不是在同一个区域复制,而是将所有存活的对象复制到另一个区域内。

优点:在存活对象不多的情况下,性能高,能解决内存碎片和java垃圾回收算法之-标记清除 中导致的引用更新问题

缺点:会造成一部分的内存浪费。不过可以根据实际情况,将内存块大小比例适当调整;如果存活对象的数量比较大,复制算法的性能会变得很差

6c63c44718aba8c7ef44f536ec3d8145

7c45a21d5b193943ac2d08080e284e5a

8.4.标记清除算法

为每个对象存储一个标记位,记录对象的状态(活着或是死亡)。
分为两个阶段,一个是标记阶段,这个阶段内,为每个对象更新标记位,检查对象是否死亡;第二个阶段是清除阶段,该阶段对死亡的对象进行清除,执行 GC 操作。

优点:解决循环引用的问题、必要时才回收(内存不足时)

缺点:1、回收时,应用需要挂起,也就是stop the world;2、标记和清除的效率不高,尤其是要扫描的对象比较多的时候;3、会造成内存碎片(会导致明明有内存空间,但是由于不连续,申请稍微大一些的对象无法做到)

b00a1294d10389dd6af9de3fb6a56bdf

8.5.标记压缩算法

在标记阶段,该算法也将所有对象标记为存活和死亡两种状态;不同的是,在第二个阶段,该算法并没有直接对死亡的对象进行清理,而是将所有存活的对象整理一下,放到另一处空间,然后把剩下的所有对象全部清除。

优点:解决标记清除算法出现的内存碎片问题

缺点:压缩阶段,由于移动了可用对象,需要去更新引用

4b4022234c317444b03af09e036e5b54

8.6.GC算法总结

  • 内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)
  • 内存整齐度:复制算法=标记压缩算法>标记清除算法
  • 内存利用率:标记压缩算法=标记清除算法>复制算法
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小钱要努力

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值