宁睿 发表于 2023-5-25 11:21:01

学习笔记-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]
查看完整版本: 学习笔记-JVM