JVM入门学习

文章目录

  • 一、JVM的位置
  • 二、jvm的体系结构
  • 三、类加载器
  • 四、Native
  • 五、方法区
  • 六、栈(堆栈)
  • 七、堆(重点)
  • 八、使用JPofiler工具分析OOM原因
  • 九、GC垃圾回收算法
    • GC算法:引用计数法
    • GC算法 :复制算法
    • GC标记-清除算法
    • GC标记-压缩算法
    • GC 算法总结

一、JVM的位置

在这里插入图片描述

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

二、jvm的体系结构

JVM入门学习_第1张图片

详细体系结构:

在这里插入图片描述

三、类加载器

在这里插入图片描述

这里的Car.class是.class文件,经过类加载器进行加载和初始化变成Class类,再通过实例化变为对象。

Class Loader的三个阶段(类加载的三个阶段)

1.加载阶段

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

2.链接阶段

  • 会为静态变量分配内存并设置默认值。

3.初始化阶段

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

java自带的类加载器

启动类加载器(Bootstrap ClassLoader)

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

拓展类加载器(Extension ClassLoader)

负责加载%JAVA_HOME%\bin\ext目录下的所有jar包,或者是java.ext.dirs参数指定的路径

系统类加载器(Application ClassLoader)

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

双亲委派机制

在这里插入图片描述

步骤:

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

(34步其实就是boot->ext->app的过程)

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

四、Native

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

native关键字的作用

native代表本地,java是用c++开发的,带了native关键字的说明java的作用范围达不到了,回去调用底层c语言的库!

会进入本地方法栈

调用本地方法接口 JNI java native Interface

JNI的作用:扩展java的使用范围,融合不同的编程语言为java使用

java诞生的时候,c和c++横行,想要立足,就要去学会调用c和c++的程序

它在内存区专门开辟了一块标记区域 Native Method Stack,登记native方法

在最终执行的时候,加载本地方法库中的方法通过JNI。

五、方法区

重点:

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

static final student.Class 字符串

JVM入门学习_第2张图片
JVM入门学习_第3张图片

qinjiang字符串就是常量池中的内容

上图的123就是实际变量

六、栈(堆栈)

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

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

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

在这里插入图片描述

七、堆(重点)

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

堆内存细分为三个区域:

  • 新生区(伊甸园区):Young/New
  • 养老区old
  • 永久区Perm(元空间)

在这里插入图片描述

新生区

类的诞生,成长和死亡的地方
分为:

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

老年区(养老区)

多次轻GC存活下来的对象放在老年区

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

永久区
在这里插入图片描述
注意:

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

在这里插入图片描述
默认情况下,JVM使用的最大内存为电脑总内存的四分之一,JVM使用的初始化内存为电脑总内存的六十四分之一.

总结

  • 栈:8大基本类型+对象引用+实例的方法.
  • 堆:存放由new创建的对象和数组
  • 方法区:静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池

深入理解堆

首先堆存放的是实例变量,成员变量存放在方法区的常量池中。

JVM入门学习_第4张图片

下图的qinjiang是放在常量池中。qinjiag是放在堆中。

例子:

String str = “a”;
String strr = “bc”;
String str1 = "abc";     //定义字符串变量str1
String str2 = "abc";      //定义字符串变量str2
String str3 = new String("abc"); //以new的方式定义字符串变量str3
String str4 = new String("abc");//以new的方式定义字符串变量str4
String str5 = str + strr;
String str6 = “a” + “bc”;

结果:

str1 ==str2    true;    
str2 ==str3    false;     
str3 ==str4    false;    
str1 == str5  false;       
str1.equals(str5)   true;   
str1==str6        true;   

讲解:

·‘==’比较的是地址

str1 和 str2显然指向的是String中常量池中的一个地址。(何灵鸿上次发的String的两种创建方式)
equals比较的是内容
① 由于地址相同,所以true
② 由于str3 使用的new ,相当于在String类堆中创建了一个堆地址。而str2的地址则是在常量池中。地址对不上,因此false
③ 和②的原理是一样的,相当于new了两个对象,也就是各自拥有了自己的地址。因此false。
④ str5使用的是字符串变量的相加。当看String的源码的时候,你会发现str.append()方法。本质上是先new 一个Stringbuilder对象,然后使用这个对象进行append,最后Builder对象toStirng回到String类型。也就是地址发生了变化。
结果:
⑤ 比较的是内容,所以为true
⑥ 也许看到这个会感到迷茫,一开始我也迷茫。后来一想,这种情况str6则是在常量池中进行的拼接(若存在,则直接指向;若不存在,拼接创建)

讲解总结

String str1 = “abc”;

这种创建是指向的是String中常量池中的一个地址。

String str3 = new String(“abc”);

这种创建是在String类堆中创建了一个堆地址。

八、使用JPofiler工具分析OOM原因

常用调优工具:jvisualVM Arthas(阿里的,常用!!!)

OOM:OutOfMemory

首先idea需要安装JPofiler插件然后windows安装JPofiler软件。

JVM入门学习_第5张图片

再将插件绑定到软件上。

测试:

JVM入门学习_第6张图片

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

在这里插入图片描述

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

可以看到什么占用了大量的内存
在这里插入图片描述
这里可以看到哪一行代码出现问题

常见JVM调优参数

配置参数 功能

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

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

-Xmn 新生代大小。通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。

实际可用空间为 = Eden + 1 个 Survivor,即 90%

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

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

-XX:+PrintGCDetails 打印 GC 信息

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

九、GC垃圾回收算法

JVM进行垃圾回收的区域

新生代(Eden、Survivor from、Survivor to)、老年代。

大部分GC都在新生代中发生。

新生代发生的GC叫Major GC,老年代发生的GC叫Full GC,Full GC至少伴随着一次Major GC。

GC算法:引用计数法

JVM入门学习_第7张图片

也就是一个对象有一个计数器,来计数对象被调用的次数,被调用次数少的就会被GC

计数器本身也会有消耗

不是很常用

GC算法 :复制算法

年轻代(新生区)主要包括:edem伊甸园 幸存区 from 幸存区 to

年轻代的GC主要使用复制算法(负责Survivor from、 to两区的复制)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SS0U2zgc-1645585567535)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220221205450632.png)]

1、每次GC都会将Eden活的对象转移到幸存区中:一旦Eden区被GC后,就是空的!

2、幸存区from中的内容转移到幸存区to,to变成from,所以edem的垃圾也是转移到新的from(to一定是空的!!!

3、当一个对象经历15次GC后,都还没有死,就进入老年代

好处:没有内存碎片

坏处:浪费了一块内存空间。(多了一块空间to区永远是空的)(如果在极端情况下,对象100%存活,这样就不好 因而新生代采用该算法)

GC标记-清除算法

此算法执行分两阶段。第一阶段从引用根节点开始标记可回收对象,第二阶段遍历整个堆,把未标记的对象清除。

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

在这里插入图片描述

GC标记-压缩算法

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

在这里插入图片描述

GC 算法总结

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

GC使用分代收集算法

年轻代:存活率低,所以采用复制算法
老年代:区域大,存活率高 使用标记清除算法+标记整理算法混合实现

你可能感兴趣的:(学习,java,intellij-idea,jvm)