随笔记——JVM快速入门篇(狂神说)

1.JVM的位置

在这里插入图片描述

程序通过java XX.class运行,这个命令是JRE的命令,JRE中包括JVM,而JRE是建立在操作系统上使用的,操作系统又是基于硬件为基础的。

2.JVM的体系结构

简化图:

在这里插入图片描述

原图连接:https://www.processon.com/view/5ea63d58637689746693a768?fromnew=1

  • 方法区是一个特殊的堆;
  • 虚拟机栈、本地方法栈、程序技术器不会产生垃圾;
  • JVM调优几乎都在调堆(包括方法区)

在这里插入图片描述

3.类加载器

在这里插入图片描述

类加载器作用:加载.class文件

类加载流程(三个阶段):
1.加载阶段

  • ​ 将编译好的class文件加载到内存中(方法区),然后会生成一个代表这个类的Class对象。

2.链接阶段

会为静态变量分配内存并设置默认值。
3.初始化阶段

执行类构造器()进行初始化赋值。

类加载的种类

  1. 虚拟机自带的加载器

  2. java自带的类加载器:

    启动类加载器(Bootstrap ClassLoader):又名根类加载器或引导类加载器,负责加载%JAVA_HOME%\bin目录下的所有jar包,或者是-Xbootclasspath参数指定的路径,例:rt.jar

    扩展类加载器(Extension ClassLoader):负责加载%JAVA_HOME%\bin\ext目录下的所有jar包,或者是java.ext.dirs参数指定的路径

    系统类加载器(Application ClassLoader):又名应用类加载器,负责加载用户类路径上所指定的类库,如果应用程序中没有自定义加载器,那么次加载器就为默认加载器

在这里插入图片描述

双亲委派机制

  1. 类加载器收到加载请求
  2. 把请求委托给父类加载器,如果父类加载器还存在其父类加载器,则进一步向上委托,最终将到达顶层的启动类加载器
  3. 如果父类可以完成加载任务,就成功返回
  4. 如果完不成,子加载器才会尝试自己去加载

优点:避免重复加载 + 避免核心类篡改

4.Java历史-沙箱安全机制(了解)

Java安全模型的核心就是Java沙箱(sandbox) ,  什么是沙箱?沙箱是一个限制程序运行的环境。

沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。

沙箱主要限制系统资源访问,那系统资源包括什么? CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。

所有的Java程序运行都可以指定沙箱,可以定制安全策略。

在Java中将执行程序分成本地代码和远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的Java实现中,安全依赖于沙箱Sandbox)机制。如下图所示JDK1.0安全模型

在这里插入图片描述
JDK1.0中如此严格的安全机制也给程序的功能扩展带来障碍,比如用户希望远程代码希望远程代码访问本地系统的文件时候,就无法实现。因此在后续的java1.1中,针对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问权限。如下图所示。

在这里插入图片描述

在Java JDK1.2版本中,再次改进了安全机制,增加了代码签名,不论是本地代码还是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中不同权限的运行空间,来实现差异化的代码执行权限控制

在这里插入图片描述

当前最新的安全机制实现,即JDK1.6时期,则引入了域 (Domain) 的概念。虚拟机会把所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域 (Protected Domain),对应不一样的权限 (Permission)。存在于不同域中的类文件就具有了当前域的全部权限,如下图所示
在这里插入图片描述
组成沙箱的基本组件:

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

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

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

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

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

参考链接:沙箱安全机制的迭代

5.Native

程序中使用:private native void start0();

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

本地方法栈(Native Method Stack)

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

本地方法接口(JNI,java native interface)

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

6.PC程序计数器(了解)

程序计数器:Program Counter Register

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

7.方法区(Method Area)

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

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

在这里插入图片描述

8.栈

栈:后进先出,每个线程都有自己的栈,栈内存主管程序的运行,生命周期和线程同步,线程结束,栈内存也就是释放。

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

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

栈运行原理:栈桢

栈满了:StackOverflowError

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

在这里插入图片描述

9.堆(重点)

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

堆内存细分为三个区域:

新生代(伊甸园区):Young/New

老年代old

持久区Perm

在这里插入图片描述

**新生代:**类的诞生,成长和死亡的地方,具体分为:

  • 伊甸园区:所有对象都在伊甸园区new出来
  • 幸存0区和幸存1区:轻GC之后存下来的

老年代(养老区):多次轻GC存活下来的对象放在老年区

真理:GC垃圾回收主要在新生代和老年代;经过研究,99%的对象都是临时对象!

持久区

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

注意:

元空间:逻辑上存在,物理上不存在 ,因为:存储在本地磁盘内,不占用虚拟机内存

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

总结

  1. 栈:基本类型的变量,对象的引用变量,实例对象的方法
  2. 堆:存放由new创建的对象和数组
  3. 方法区:Class对象,static变量,常量池(常量)

10.使用JPofiler工具分析OOM原因

测试:OutOfMemoryError

public class Test2 {
    static String a="12345678";

    public static void main(String[] args) {
        while (true){
            a=a+ new Random().nextInt(8888888)+new Random().nextInt(99999999);
        }
    }

}

下载地址:https://www.ej-technologies.com/download/jprofiler/version_92

安装完成后,需要在IDEA中安装插件。

添加参数运行程序:
-Xms1m -Xmx1m -XX:+HeapDumpOnOutOfMemoryError:当出现OOM错误,会生成一个dump文件(进程的内存镜像)
在这里插入图片描述

在项目目录下找到dump文件,双击打开
在这里插入图片描述
在这里插入图片描述

可以看到什么占用了大量的内存
在这里插入图片描述

这里可以看到哪一行代码出现问题

11.常见JVM调优参数

  1. -Xms 初始堆大小。如:-Xms256m

  2. -Xmx 最大堆大小。如:-Xmx512m

  3. -Xmn 新生代大小。通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor,即 90%

  4. -XX:NewRatio 新生代与老年代的比例,如 –XX:NewRatio=2,则新生代占整个堆空间的1/3,老年代占2/3

  5. -XX:SurvivorRatio 新生代中 Eden 与 Survivor 的比值。默认值为 8。即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10

  6. -XX:+PrintGCDetails 打印 GC 信息

  7. -XX:+HeapDumpOnOutOfMemoryError 让虚拟机在发生内存溢出时 Dump 出当前的内存堆转储快照,以便分析用

12.常见垃圾回收算法

1. 引用计数算法
原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为 0 的对象。此算法最致命的是无法处理循环引用的问题。
在这里插入图片描述

2. 复制算法
此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理。
在这里插入图片描述

优点:不会出现碎片化问题
缺点:需要两倍内存空间,浪费

3.标记-清除算法
此算法执行分两阶段。第一阶段从引用根节点开始标记可回收对象,第二阶段遍历整个堆,把未标记的对象清除。
在这里插入图片描述

优点:不会浪费内存空间
缺点:此算法需要暂停整个应用,同时,会产生内存碎片

4.标记-压缩算法
此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有可回收对象,第二阶段遍历整个堆,清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。
此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。
在这里插入图片描述

13.分代回收策略

  1. 绝大多数刚刚被创建的对象会存放在Eden区
  2. 当Eden区第一次满的时候,会触发MinorGC(轻GC)。首先将Eden区的垃圾对象回收清除,并将存活的对象复制到S0,此时S1是空的。
  3. 下一次Eden区满时,再执行一次垃圾回收,此次会将Eden和S0区中所有垃圾对象清除,并将存活对象复制到S1,此时S0变为空。
  4. 如此反复在S0和S1之间切换几次(默认15次)之后,还存活的对象将他们转移到老年代中。
  5. 当老年代满了时会触发FullGC(全GC)

MinorGC

  1. 使用的算法是复制算法
  2. 年轻代堆空间紧张时会被触发
  3. 相对于全收集而言,收集间隔较短

FullGC

  1. 使用的算法一般是标记压缩算法
  2. 当老年代堆空间满了,会触发全收集操作
  3. 可以使用 System.gc()方法来显式的启动全收集
  4. 全收集非常耗时

14.JMM

JMM(java内存模型)Java Memory Model,本身是一个抽象的概念,不是真实存在的,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段、静态字段和构成数组对象的元素)的访问方式。

在这里插入图片描述

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

JMM内存模型三大特性

1、原子性

使用 synchronized 互斥锁来保证操作的原子性

2、可见性:

  1. volatile,会强制将该变量自己和当时其他变量的状态都刷出缓存。
  2. synchronized,对一个变量执行 unlock 操作之前,必须把变量值同步回主内存。
  3. final,被 final 关键字修饰的字段在构造器中一旦初始化完成,并且没有发生 this 逃逸(其它线程通过 this 引用访问到初始化了一半的对象),那么其它线程就能看见 final 字段的值。

3、有序性

  1. 源代码 -> 编译器优化的重排 -> 指令并行的重排 -> 内存系统的重排 ->最终执行的命令

  2. 重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性

  3. 处理器在进行重排时必须考虑数据的依赖性,多线程环境线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值