JVM 快速入门-B站【狂神说Java笔记】

JVM 快速入门-B站【狂神说Java笔记】,第1张

JVM 快速入门-B站【狂神说Java笔记】

狂神视频地址: https://www.bilibili.com/video/BV1iJ411d7jS

1. JVM的位置

2. JVM的体系结构


 JVM 调优百分之99都是在堆里面调优,方法区是特殊的堆。

3. 类加载器

作用:加载Class 文件,~ new Student();

类似模板,是抽象的对象是实现,是具体的

类是模板(抽象的),而对象是具体的
分类:

虚拟机自带的加载器启动类(根)加载器扩展类加载器应用程序加载器 4. 双亲委派机制

// 双亲委派机制:安全
// APP–>EXC–>BOOT(最终执行)
// BOOT
// EXC
// APP

第一步:类加载器收到类加载的请求第二步:将这个请求向上委托给父类加载器去完成 ,一直向上委托,直到启动类加载器(Boot)第三步:启动类加载器检查是否能够加载当前和这个类 ,能加载就结束,使用当前的加载器,否则,抛出异常,通知子加载器进行加载。第四步:重复 第三步 步骤。

null : java调用不到 ~ C 、C++
Java = C++ : 去掉繁琐的东西,指针,内存管理。(Java = C++ --)

5. 沙箱安全机制

参考:https://blog.csdn.net/qq_30336433/article/details/83268945

6. Native

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

会进入本地方法栈。调用本地接口:JNI

JNI作用:扩展Java的使用,融合不同的编程语言为Java 所用!最初是想融合C、C++Java诞生的时候,C和C++横行,要想立足,必须要有调用C/C++的程序。所以它在内存区域中专门开辟了一块标记区域:Native Method Stack,登记native 方法在最终执行的时候去加载本地方法库的方法,通过JNI

本地方法接口(JNI)Java Native Interface
本地方法库

private native void start0();


调用其他接口:Scoket 、WebService~…http

7. PC寄存器

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

8. 方法区

Method Area 方法区

方法区是被线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享空间。
静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是,实例变量存在堆内存中,和方法区无关方法区里面存以下内容
static,final,Class 类模板,常量池类加载过程(面试)
new 一个类的时候,先在方法区有一个类的模板
类模板完了 ,方法区还有个常量池
引用在栈内存
真实的 对象在堆内存
引用指向堆内存真实的地址
(参考Java基础) 9. 栈

栈:数据结构
程序 = 数据结构 +算法 : 持续学习~
程序 = 框架 + 业务逻辑 : 吃饭

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

为什么main 方法 先执行,最后结束!
栈:栈内存,主管程序的运行,生命周期与线程同步
线程结束,栈内存也就释放,对于栈来说,不存在垃圾回收问题一旦线程结束,栈就over了

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

栈运行原理:栈帧

程序正在执行的方法,一定在栈的顶部

栈 + 堆 + 方法区:的一些交互关系

一个对象在内存中的实例化过程

public class People{
    String name; // 定义一个成员变量 name
    int age; // 成员变量 age
    Double height; // 成员变量 height
    void sing(){
        System.out.println("人的姓名:"+name);
        System.out.println("人的年龄:"+age);
        System.out.println("人的身高:"+height);
    }
    
    public static void main(String[] args) {
        String name; // 定义一个局部变量 name
    	int age; // 局部变量 age
    	Double height; // 局部变量 height
        
        People people = new People() ; //实例化对象people
        people.name = "张三" ;       //赋值
        people.age = 18;             //赋值
        people.stuid = 180.0 ;   //赋值
        people.sing();              //调用方法sing
    }
}


在程序的执行过程中,首先类中的成员变量和方法体会进入到方法区,如图:

程序执行到 main() 方法时,main()函数方法体会进入栈区,这一过程叫做进栈(压栈),定义了一个用于指向 Person 实例的变量 person。如图:

程序执行到 Person person = new Person(); 就会在堆内存开辟一块内存区间,用于存放 Person 实例对象,然后将成员变量和成员方法放在 new 实例中都是取成员变量&成员方法的地址值 如图:

接下来对 person 对象进行赋值, person.name = “小二” ; perison.age = 13; person.height= 180.0;

先在栈区找到 person,然后根据地址值找到 new Person() 进行赋值 *** 作。

如图:

当程序走到 sing() 方法时,先到栈区找到 person这个引用变量,然后根据该地址值在堆内存中找到 new Person(),找到方法地址 进行方法调用。

在方法体void sing()被调用完成后,就会立刻马上从栈内d出(出栈 )

最后,在main()函数完成后,main()函数也会出栈 如图:

10. 三种JVM
    Sun公司 HostSpot Java HotSpot™ 64-Bit Server VM (build 25.101-b13, mixed mode)BEA JrockitIBM J9 VM

我们学习的都是HotSpot

11. 堆

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

类加载器读取了类文件后,一般会把什么东西放到堆中?
类的实例、方法、常量、变量~,保存我们所有引用类型的真实对象堆内存中还要细分为三个区域:

新生区 (伊甸园区) Young/New养老区 old永久区 Perm

    GC 垃圾回收主要是在伊甸园区和养老区~假设内存满了,OOM ,堆内存不够!在JDK 8以后,永久存储区改了个名字(元空间)
11.1 新生区

类:诞生 和 成长的地方、甚至死亡。伊甸园区,所有的的对象都是在伊甸园区new 出来的!幸存者区(0,1)
假如伊甸园区满了,就触发一次轻GC,这次GC有以下情况:

有的对象可以还被引用,就幸存下来了。有的对象没有被引用了,就死了、没了。幸存的下来的对象就移动到幸存区。 当伊甸园区和幸存区都满了,就会触发一次重GC。

重gc 清理一次后,能活下来的对象就进入养老区了。
(就跟一场战争一样,不断的活下来)

真理:经过研究,99%的对象都是临时对象! new

11.2 永久区
    jdk 1.6之前:永久代,常量池是在方法区中jdk 1.7 :永久代,但是慢慢的退化了,去 永久代,常量池在堆中jdk 1.8 之后:无永久代,常量池在元空间

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

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

元空间:逻辑上存在,物理上不存在。

11.3 OOM排查

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

能够看到代码第几行出错:内存快照分析工具,Eclipse MAT,JprofilerDebug,一行行分析代码~ MAT,Jprofiler作用:

分析Dump内存文件,快速定位内存泄漏;获得堆中的数据获得大的对象~ 当出现OOM

尝试扩大堆内存看结果
-Xms1024m -Xmx1024m -XX:+PrintGCDetails

// -Xms1024m -Xmx1024m -XX:+PrintGCDetails
public static void main(String[] args) {
        // 返回虚拟机试图使用的最大内存
        long maxMemory = Runtime.getRuntime().maxMemory(); //字节 1024*1024

        // 返回JVM的总内存
        long totalMemory = Runtime.getRuntime().totalMemory();

        System.out.println("max="+maxMemory+"字节t"+(maxMemory/(double)1024/1024)+"MB");
        System.out.println("total="+totalMemory+"字节t"+(totalMemory/(double)1024/1024)+"MB");

        // 默认情况下,分配的总内存 是电脑内存的1/4,而初始化内存 是1/64
    }
 // -Xms8m -Xmx8m -XX:+PrintGCDetails
   public static void main(String[] args) {
       String str = "jdskanvkdfjhaljfnds";

       while (true) {
           str += str + new Random().nextInt(888888888) + new Random().nextInt(999999999);
       }
    }

分析内存,看一下哪个地方出现了问题(专业工具)
idea 安装Jprofiler
。。。 12. GC 垃圾回收机制

垃圾回收的区域只有在堆里面(方法区在堆里面)

JVM 在进行垃圾回收(GC)时,并不是堆这三个区域统一回收。大部分时候,回收的都是新生代~

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

GC 两种类

轻GC(普通GC):只针对 新生代 和 偶尔走一下 幸存区。重GC (全局GC): 全部清完。

面试题目:

JVM的内存模型和分区~详细到每一个区放什么?堆里面的分区有哪些?GC的算法有哪些?

标记清除法标记整理复制算法引用计数法怎么用的? 轻GC 和 重GC 分别再什么时候发生? 12.1 引用计数法

假设我对象A 用了 一次就给它加上1
假设我对象B 用了 两次就给它加上2
假设我对象C 没有使用 就是 0

引用计数法就是给每个对象分配一个计数器

假设C 对象为 0,它就要被清除出去了

JVM 现在一般不采用这种方式,不高效。

12.2 复制算法

每次GC 都会将 伊甸园区 活得对象 移动到 幸存区 中,如果幸存区放不下 ,就移到养老区中。
一旦伊甸园区被GC 后,就会是空的。
当某对象从伊甸园区 存活下来了。

谁空谁是to
假设这个对象还活着,它就把这个对象复制到另一个区域 要么是 form 要么是 to
当某个对象 经历15次(默认值)GC 都还没有死的时候,就会进入养老区。

图解复制算法:

假设 幸存区里面,to 是空的,form 里面有对象。
现在要做一次垃圾回收了
首先:伊甸园区 存活的对象往 to 里面走

其次,form 区里面的 对象也要往 to 里面走

每次清理完之后,伊甸园区是空的,to区是空的。
经历15次GC之后,会把幸存区 里面或者的对象 移到养老区,或者有一些没有到15次就被清理掉

好处:没有内存的碎片
坏事:浪费一半内存的空间:多了一半空间用于是空的。

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

12.3 标记清除算法

扫描这些对象,对活着的对象进行标记
清除:对没有标记的对象,进行清除

优点:不需要额外的空间
缺点:两次扫描严重浪费时间,会产生内存碎片。

12.4 标记压缩

上面的再优化:防止内存碎片的产生(再次扫描,向一端移动存活的对象)
但是多了一个移动成本

12.5总结

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

没有最优算法吗?

没有最好的算法,只有最合适的算法~ ---->GC: 分代收集算法每一代用合适的算法就好了。 年轻代: (大部分的对象都在这里都死了)

存活率低复制算法! 老年代:

存活率高,区域大标记清除 + 标记压缩 混合实现

JMM 新知识快速学习


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5710008.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存