JVM笔记

随手点赞, 手有余香😀

0.java程序的执行过程

在这里插入图片描述

1.JVM的位置

在这里插入图片描述

2.JVM的体系结构

a.简图

b.详细图

在这里插入图片描述引用都在栈中, 具体对象在堆中

3. 类加载器和双亲委派机制

JVM 提供了3种类加载器

  • 启动类加载器:负责加载 Java_HOME/lib 目录中的类库
  • 扩展类加载器:负责加载 Java_HOME/lib/ext 目录中的类库
  • 应用程序类加载器:负责加载用户路径(classpath)上的类库

既然有这么多加载器,加载一个类时使用的是哪个类加载器?如果不同类加载器有同名类,以哪个为准,如何避免类名冲突?双亲委派机制

双亲委派机制

是指一个类在收到类加载请求后不会尝试自己加载这个类, 而是把该类加载请求向上委派给其父类加载器去完成,
其父类加载器在接收到该类加载请求后又会将其委派给自己的父类, 以此类推,这样所有的类加载请求都被向上委派到启动类加载器中。
若父类加载器在接收到类加载请求后发现自己也无法加载该类 (通常原因是该类的Class文件在父类的类加载路径中不存在),
则父类会将该信息反馈给子类并向下委派子类加载器加载该类, 直到该类被成功加载. 若找不到该类,则JVM会抛出ClassNotFound异常。

4.java历史-沙箱安全机制

Java安全模型的核心就是Java沙箱(sandbox) ,

沙箱是一个限制程序运行的环境。沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。  
沙箱主要限制系统资源访问,那系统资源包括什么? CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。  
所有的Java程序运行都可以指定沙箱,可以定制安全策略。

演变过程

jdk1.0在Java中将执行程序分成本地代码和远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码则不可.

jdk1.1用户可以授权部分远程代码.

jdk1.6之后,引入了域(Domain)的概念。虚拟机吧所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互。而各个域应用部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域(Protected Domain),对应不一样的权限(Permission)。存在于不同域中的类文件就具有了当前域的全部权限

组成沙箱的基本组件:

a.字节码校验器(bytecode verifier) :确保Java类文件遵循Java语言规范。这样可以帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类。

b.类裝载器(class loader) :其中类装载器在3个方面对Java沙箱起作用

  • 它防止恶意代码去干涉善意的代码;

  • 它守护了被信任的类库边界;

  • 它将代码归入保护域,确定了代码可以进行哪些操作。

5.Native和方法区

private native void start0();
  • 凡是带了native关键字的,说明java的作用范围达不到了,回去调用底层c语言的库!
  • 会进入本地方法栈,然后去调用本地方法接口将native方法引入执行

本地方法栈(Native Method Stack)

内存区域中专门开辟了一块标记区域: Native Method Stack,负责登记native方法,在执行引擎( Execution Engine )执行的时候通过本地方法接口(JNI)加载本地方法库中的方法

本地方法接口(JNI)

本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序, Java在诞生的时候是C/C++横行的时候,想要立足,必须有调用C、C++的程序,然后在内存区域中专门开辟了一块标记区域: Native Method Stack,负责登记native方法,在执行引擎( Execution Engine )执行的时候通过本地方法接口(JNI)加载本地方法库中的方法

程序计数器: Program Counter Register

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

作用:记录要执行的代码位置,防止线程切换重新执行
字节码执行引擎修改程序计数器的值

方法区(Method Area)

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

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

8.深入理解栈

队列:先进先出(FIFO:First Input First Output)
栈:后进先出,每个线程都有自己的栈,程序计数器

栈内存主管程序的运行,生命周期和线程同步,线程结束,栈内存也就是释放。

所以对于栈来说,不存在垃圾回收问题,一旦线程结束,栈就结束.

栈内存中运行:8大基本类型+对象引用+实例的方法形参和局部变量

栈中有实例方法的形参和局部变量,实例方法代码则存放在方法区

基本数据类型:byte short int long boolean char float double

栈帧:局部变量表+操作数栈

在这里插入图片描述

栈运行原理:栈桢

栈满了:StackOverflowError

栈中存储什么?

  • 每个线程都有自己的栈,栈中的数据都是以栈帧的格式存在。
  • 在这个线程上正在执行的每一个方法都各自对应一个栈帧
  • 栈帧是一个内存区块,是一个数据集维系着方法执行过程中的各种数据信息

在一条活动线程中,一个时间点上,只会有一个活动的栈帧,
即只有当前正在执行的方法的栈帧(栈顶栈帧)是有效的,
这个栈帧被称为当前栈帧(Current Frame),
与当前栈帧对应的方法就是当前方法(Current method),
定义这个方法的类就是当前类(Current Class)

执行引擎运行的所有字节码指令只会针对当前栈帧进行操作

如果在该方法中调用了其他方法,对应的新的栈帧就会被创建出来,放在栈的顶端,称为新的当前帧
在这里插入图片描述
对象的创建过程
在这里插入图片描述
上面还应该在方法区中找类的信息, 还涉及类的加载

7. Hotspot和堆

JDK 1.7 分代结构
在 JDK 1.7 以及之前堆空间分为 3 部分:新生代,老年代,永久代。
然后新生代分为:Eden 区, 和两个 Survivor 区

在这里插入图片描述
JDK 1.8 分代结构
在 JDK 1.8 及其以后,堆空间中移除了永久代。

  • 这是 Hotspot 和 JRockit 虚拟机融合。JRockit 客户不需要配置永久代(因为JRockit 没有永久代),习惯不配置永久代。
  • 增加元空间解决类加载所需要的内存空间,而且元空间默认是自动拓容的。这样减少内存溢出的可能。
    堆空间移除永久代过后,堆空间的结构如下图所示:

在这里插入图片描述

常量池相关概念

  • 运行时常量池:
      Java语言并不要求常量一定只能在编译期产生,运行期间也可能产生新的常量,这些常量被放在运行时常量池中。
      类加载后,常量池中的数据会在运行时常量池中存放!
      这里所说的常量包括:基本类型包装类(包装类不管理浮点型,整形只会管理-128到127)和String(也可以通过String.intern()方法可以强制将String放入常量池)

  • 字符串常量池:
    HotSpot VM里,记录interned string的一个全局表叫做StringTable,它本质上就是个HashSet。注意它只存储对java.lang.String实例的引用,而不存储String对象的内容

  • 方法区是一种逻辑概念 永久区和元空间都是一种实现方式 元空间在内存里 永久区在堆里

版本变化

  • 在JDK1.7前,运行时常量池+字符串常量池是存放在方法区中,HotSpot VM对方法区的实现称为永久代。
  • 在JDK1.7中,字符串常量池从方法区移到堆中,运行时常量池保留在方法区中。
  • 在JDK1.8中,HotSpot移除永久代,使用元空间代替,此时字符串常量池保留在堆中,运行时常量池保留在方法区中,只是实现不一样了,JVM内存变成了直接内存。

Xms字面意思是最小内存,这里可以理解成初始化时的内存分配,Xmx也就是允许分配的最大内存空间。

默认情况下,JVM使用的最大内存Xmx为电脑总内存的四分之一,JVM使用的初始化内存Xms为电脑总内存的六十四分之一.

总结

  • 栈:局部变量表、操作数栈、动态链接、方法出口
  • 堆:存放由new创建的对象和数组, 1.7之后字符串常量池
  • 方法区:Class类信息、static静态变量,运行时常量池(常量),1.7前字符串常量池

10. 使用JProfiler工具分析OOM的原因

下载操作

两步玩成后, 创建一个程序, 设置VM参数-Xms1m -Xmx1m -XX:+HeapDumpOnOutOfMemoryError
在这里插入图片描述
当出现OOM错误,会生成一个dump文件(进程的内存镜像)
项目目录下找到dump文件,双击打开
在这里插入图片描述

  • 可以看到什么占用了大量的内存
  • 还可以看到哪一行代码出现问题

11.常见JVM调优参数

配置参数功能
-Xms初始堆大小。如:-Xms256m
-Xmx最大堆大小。如:-Xmx512m
-Xmn新生代大小。通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor,即 90%
-XX:SurvivorRatio新生代与老年代的比例,如 –XX:NewRatio=2,则新生代占整个堆空间的1/3,老年代占2/3
-XX:+PrintGCDetails打印 GC 信息
-XX:+HeapDumpOnOutOfMemoryError让虚拟机在发生内存溢出时 Dump 出当前的内存堆转储快照,以便分析用

12.常见垃圾回收算法

GC的作用范围 = 方法区+堆区

1.引用计数算法

原理是通过在对象头中分配一个空间来保存该对象被引用的次数。

如果该对象被其它对象引用,则它的引用计数加一,

如果删除对该对象的引用,那么它的引用计数就减一,

当该对象的引用计数为0时,那么该对象就会被回收。

但是这种引用计数算法有一个比较大的问题,那就是它不能处理环形数据 - 即如果有两个对象相互引用,那么这两个对象就不能被回收,因为它们的引用计数始终为1。这也就是我们常说的“内存泄漏”问题

2.复制算法

原理: 将原有的内存空间分为两块,每次只使用其中的一块,在垃圾回收时候,将正在使用的内存中的存活对象复制到为未使用的内存中,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。

优点:不会出现碎片化问题
缺点:需要两倍内存空间,浪费
在这里插入图片描述

3.标记-清除算法

此算法执行分两阶段。

  • 在标记阶段,GC从mutator根对象开始进行遍历,对从mutator根对象可以访问到的对象都打上一个标识,一般是在对象的header中,将其记录为可达对象。

  • 在清除阶段,GC对堆内存(heap memory)从头到尾进行线性的遍历,如果发现某个对象没有标记为可达对象-通过读取对象的header信息,则就将其回收

  • 优点:不会浪费内存空间

  • 缺点:此算法需要暂停整个应用,同时,会产生内存碎片
    在这里插入图片描述

4.标记-压缩算法
此算法结合了“标记-清除”和“复制”两个算法的优点。
也是分两阶段

  • 标记阶段从根节点开始标记所有可以访问到的对象

  • 压缩阶段遍历整个堆,清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。

  • 缺点:时间复杂度上升了, 用时间换空间.

  • 优点:此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

13.分代收集算法

各算法内存分析

  • 内存效率:复制算法>标记清除算法>标记整理算法
  • 内存整齐度:复制算法=标记整理算法>标记清除算法。
  • 内存利用率:标记整理算法=标记清除算法>复制算法。

年轻代(Young Gen):区域相对老年代较小, 对像存活率低。复制算法
老年代(Tenure Gen):区域较大,对像存活率高.标记清除或者是标记清除与标记压缩的混合实现

14.可达性分析

可达性分析其实是数学中的一个概念
在JVM中,会将一些特殊的引用作为 GcRoot

如果通过 GcRoot 可以访达的对象不会被当作垃圾对象。

换种方式说就是,一个对象被 GcRoot 直接 或 间接持有,

那么该对象就不会被当作垃圾对象

15. 新生代的工作流程

复制算法

当一个对象刚被创建时会放到 Eden 区域,当 Eden 区域即将存满时做一次垃圾回收,
将当前存活的对象复制到 SurvivorA
随后将 Eden 清空 当Eden 下一次存满时,再做一次垃圾回收,
先将存活对象复制到 SurvivorB ,再把 Eden 和SurvivorA 所有对象进行回收,
当Eden 再一次存满时,再做一次垃圾回收,将存活对象复制到 SurvivorA,
再把 Eden 和 SurvivorB对象进行回收。
如此反复进行大概 15 次,将最终依旧存活的对象放入到老年代区域。

补充:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值