程序通过java XX.class
运行,这个命令是JRE的命令,JRE中包括JVM,而JRE是建立在操作系统上使用的,操作系统又是基于硬件为基础的。
详细体系结构:
这里的Car.class是.class文件,经过类加载器进行加载和初始化变成Class类,再通过实例化变为对象。
Class Loader的三个阶段(类加载的三个阶段)
1.加载阶段
2.链接阶段
3.初始化阶段
java自带的类加载器
启动类加载器(Bootstrap ClassLoader)
又名根类加载器或引导类加载器,负责加载%JAVA_HOME%\bin目录下的所有jar包,或者是-Xbootclasspath参数指定的路径,例:rt.jar
拓展类加载器(Extension ClassLoader)
负责加载%JAVA_HOME%\bin\ext目录下的所有jar包,或者是java.ext.dirs参数指定的路径
系统类加载器(Application ClassLoader)
又名应用类加载器,负责加载用户类路径上所指定的类库,如果应用程序中没有自定义加载器,那么次加载器就为默认加载器
双亲委派机制
步骤:
(34步其实就是boot->ext->app的过程)
优点:避免重复加载 + 避免核心类篡改
程序中使用: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 字符串
qinjiang字符串就是常量池中的内容
上图的123就是实际变量
栈:后进先出,每个线程都有自己的栈(线程独享),栈内存主管程序的运行,生命周期和线程同步,线程结束,栈内存也就是释放。
对于栈
来说不存在垃圾回收机制
,一旦线程结束,栈就结束
栈内存中运行:8大基本类型
+对象引用
+实例的方法
.
一个JVM只有一个堆内存,堆内存的大小是可以调节的,类加载器读取类文件后,一般会把类,方法,常量,变量,我们所有引用类型的真实对象,放入堆中。
堆内存细分为三个区域:
新生区
类的诞生,成长和死亡的地方
分为:
老年区(养老区)
多次轻GC存活下来的对象放在老年区
真理:经过研究,99%的对象都是临时对象!
永久区
注意:
元空间:逻辑上存在,物理上不存在 ,因为:存储在本地磁盘内,不占用虚拟机内存
默认情况下,JVM使用的最大内存为电脑总内存的四分之一,JVM使用的初始化内存为电脑总内存的六十四分之一.
总结
8大基本类型
+对象引用
+实例的方法
.深入理解堆
首先堆存放的是实例变量,成员变量存放在方法区的常量池中。
下图的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类堆中创建了一个堆地址。
常用调优工具:jvisualVM
Arthas
(阿里的,常用!!!)
OOM:OutOfMemory
首先idea需要安装JPofiler插件然后windows安装JPofiler软件。
再将插件绑定到软件上。
测试:
添加参数运行程序:
-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 出当前的内存堆转储快照,以便分析用
JVM进行垃圾回收的区域
新生代(Eden、Survivor from、Survivor to)、老年代。
大部分GC都在新生代中发生。
新生代发生的GC叫Major GC
,老年代发生的GC叫Full GC
,Full GC至少伴随着一次Major 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使用分代收集算法
年轻代:存活率低,所以采用复制算法
老年代:区域大,存活率高 使用标记清除算法+标记整理算法
混合实现