学习笔记-JVM
JVM的位置JVM是运行在操作系统上的虚拟机,存在于JRE当中
JVM的类型
[*]HotSpot
[*]Sun公司
[*]用的基本都是这个
[*]JRockit
[*]BEA
[*]J9VM
[*]IBM
JVM的体系结构
https://img1.imgtp.com/2023/05/25/Q6CEQUTP.png
https://img1.imgtp.com/2023/05/25/ooI8MNL8.png
https://img1.imgtp.com/2023/05/25/en97f3kT.png
本地方法接口JNI
[*]JNI的作用
[*]拓展java的使用,融合不同的编程语言为java所用
[*]最初是C/C++
[*]因为最初java诞生的时候,市面上全是C/C++,java要想立足,必须有能调用C/C++的方法
[*]于是在内存中设置了本地方法栈,专门用来登记native方法
[*]然后由JNI去调用本地方法库
[*]凡是带了native关键字的方法
[*]说明java的作用范围达不到了
[*]会进入本地方法栈
[*]执行引擎会调用本地方法接口JNI
[*]去调用底层c语言的库
[*]常见的本地方法
[*]线程
[*]打印机
[*]管理系统
[*]现在除了通过JNI,也有其他方法去调用其他语言的方法,比如说Socket
类加载器
ClassLoader
用于加载类
分类
[*]虚拟机自带的加载器
[*]java调用不到这个类
[*]是用C/C++写的
[*]启动类(根)加载器
[*]加载java核心类库
[*]扩展类加载器
[*]加载ext目录中的jar包
[*]应用程序加载器
[*]加载当前classpath下的所有类
除此之外,用户也能自定义类加载器,用来加载指定路径的class类
双亲委派机制
[*]类加载器收到类加载的请求
[*]将这个请求向上委托给父类加载器去完成
[*]一直向上委托
[*]直到启动类加载器
[*]当前加载器检查是否能够加载当前这个类
[*]能加载就结束,使用当前的加载器
[*]否则通知子加载器进行加载
[*]重复步骤3
[*]若没有任何类加载器可以加载
[*]Class Not Found
双亲委派机制的作用:
[*]沙箱隔离机制,安全,防止Java的核心API类被篡改
[*]恶意代码无法通过同名类的方法获得高级权限
[*]避免重复加载
类加载的过程
[*]验证
[*]准备
[*]解析
[*]初始化
程序计数器
[*]可以看作
[*]当前线程所执行的字节码的行号指示器
[*]指向下一个将要执行的指令代码的地址
[*]如果是Java方法,记录的是虚拟机字节码指令的地址
[*]如果是native方法,记录的是Undefined
[*]由执行引擎来读取下一条指令
[*]更确切地说
[*]一个线程的执行
[*]是通过字节码解释器改变当前线程的计数器的值
[*]来获取下一条需要执行的字节码指令
[*]在物理上是通过寄存器来使用的
[*]不存在OOM
栈
[*]线程运行需要的内存空间
[*]虚拟机栈为java方法服务
[*]本地方法栈为native方法服务
[*]两者在作用上是非常相似的
[*]下面主要描述虚拟机栈
栈中存储的是什么
[*]栈帧(stack frame)是栈的元素
[*]每个方法在执行时都会创建一个栈帧
[*]栈帧主要包含四个部分
[*]局部变量表(local variable)
[*]操作数栈(operand stack)
[*]动态连接(dynamic linking)
[*]方法出口
https://img1.imgtp.com/2023/05/25/D56JFv9j.png
局部变量表
[*]用于存储数据
[*]存储的类型有两种
[*]基本数据类型的局部变量
[*]包括方法参数
[*]对象的引用
[*]但是不存储对象的内容
[*]所需的内存空间在编译期间完成分配
[*]方法运行期间不会改变局部变量表的大小
[*]变量槽(Variable Slot)
[*]局部变量表的容量的最小单位
[*]一个slot最大32位
[*]对于64位的数据类型(long和double)会分配两个连续的slot
[*]java通过索引定位的方法使用局部变量表
[*]从0开始
[*]一个Slot占1位
[*]非static方法第0个槽存储方法所属对象实例的引用
[*]https://img1.imgtp.com/2023/05/25/d0oVY5Eu.png
[*]slot复用
[*]为了节省栈帧空间,slot是可以复用的
[*]如果某个变量失效了
[*]即超出了某个变量的作用域
[*]那么这个变量的slot就会交给其他变量使用
[*]副作用(这一段存疑):
[*]会影响系统的垃圾收集行为
[*]当某个变量失效后,因为它的slot可能还会交给其他变量复用,所以它占用的slot就不会被回收
[*]线程安全
[*]当局部变量表中的引用逃离了线程的范围
[*]也就是当一个引用可以被另一个线程拿到的时候
[*]就变成线程不安全的了
操作数栈
[*]一个栈
[*]元素可以是任意的java数据类型
[*]主要作用
[*]用于算数运算
[*]用于参数传递
[*]栈帧中用于计算的临时数据存储区
[*]举例
[*]public class OperandStack{
public static int add(int a, int b){
int c = a + b;
return c;
}
public static void main(String[] args){
add(100, 98);
}
}
[*]https://img1.imgtp.com/2023/05/25/ZZV0KDSR.png
[*]https://img1.imgtp.com/2023/05/25/TnesW18m.jpg
动态连接
指向运行时常量池中该栈帧所属方法的引用
返回地址
[*]存放调用该方法的pc寄存器的值
[*]正常退出时会使用
[*]异常退出时会通过异常表来确认
可能出现的异常
[*]StackOverflowError
[*]栈溢出错误
[*]如果一个线程在计算时所需的栈大小>配置允许最大的栈大小
[*]那么jvm将抛出该错误
[*]OutOfMemoryError
[*]内存不足
[*]栈进行动态扩展时如果无法申请到足够的内存
[*]会抛出该错误
设置栈参数
[*]-Xss
[*]设置栈大小
[*]通常几百K
jstack命令
[*]jstack是JVM自带的JAVA栈追踪工具
[*]它用于打印出给定的java进程ID、core file、远程调试Java栈信息
[*]常用命令:
[*]jstack pid
[*]打印某个进程的堆栈信息
[*]选项
[*]-F强制输出
[*]-m显示本地方法的堆栈
[*]-l显示锁信息
[*]使用案例
[*]查看进程死锁情况
[*]查看高cpu占用情况
[*]还需要用到top命令
堆
被所有线程共享
主要存储
[*]new关键字创建的对象实例
[*]数组
[*]静态变量
[*]string池(1.8之后)
GC就是在堆上收集对象所占用的内存空间
堆的空间结构
https://img1.imgtp.com/2023/05/25/vW8u4ZCg.png
[*]新创建的对象会存储在生成区
[*]年轻代内存满之后,会触发Minor CG,清理年轻代内存
[*]长期存活的对象和大对象会存储在老年代
[*]当老年代内存满之后,会触发Full CG,清理全部内存
[*]如果清理后仍然无法存储进新的对象
[*]会抛出OutOfMemoryError
堆内存诊断
[*]jps工具
[*]查看当前系统中有哪些java进程
[*]jmap工具
[*]查看堆内存占用情况
[*]jmap -heap pid
[*]jmap -dump:format=b,live,file=1.bin pid
[*]将堆内存占用情况转储
[*]format=b:以二进制的形式
[*]live:抓取之前调用一次垃圾回收
[*]file=1.bin:将文件导出为1.bin
[*]jconsole工具
[*]图形界面的,多功能的监测工具
[*]jvisualvm
[*]可视化虚拟机
[*]案例:调用垃圾回收后,占用的内存依然非常大
[*]使用jvisualvm
[*]查看对象个数
[*]使用堆转储dump
方法区
被所有线程共享
主要存储
[*]类信息
[*]版本
[*]字段
[*]方法
[*]接口
[*]运行时的常量池
[*]字面量
[*]final修饰的常量
[*]基本数据类型的值
[*]字符串(1.8之前)
[*]符号引用
[*]类和接口的全类型
[*]方法名和描述符
[*]字段名和描述符
[*]当类被加载时,.class中的常量池会被放进运行时常量池中
永久区
JDK1.7及之前,方法区的具体实现是PermSpace永久区
MetaSpace
JDK1.8后,使用MetaSpace元空间替代PermSpace
元空间不在JVM中,而是使用本地内存
有两个参数:
[*]MetaSpaceSize
[*]初始化元空间大小
[*]控制发生GC的阈值
[*]MaxMetaSpaceSize
[*]限制元空间大小上限
[*]防止异常占用过多的物理内存
使用常量池的优点
[*]避免了频繁的创建和销毁对象而影响系统性能
[*]实现了对象的共享
Integer常量池
public void TestIntegerCache()
{
public static void main(String[] args)
{
Integer i1 = new Integer(66);
Integer i2 = new Integer(66);
Integer i3 = 66;
Integer i4 = 66;
Integer i5 = 150;
Integer i6 = 150;
System.out.println(i1 == i2);//false
System.out.println(i3 == i4);//true
System.out.println(i5 == i6);//false
}
}<ul>为什么i1 == i2为false
[*]因为是new了两个新对象,两个新对象的地址不一样
为什么i3 == i4为true
<ul>当Integer i3 = 66时,其实进行了一步装箱操作
通过Integer.valueOf()将66装箱成Integer
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i
页:
[1]