学习笔记-JVM

宁睿  金牌会员 | 2023-5-25 11:21:01 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 969|帖子 969|积分 2917

JVM的位置

JVM是运行在操作系统上的虚拟机,存在于JRE当中
JVM的类型


  • HotSpot

    • Sun公司
    • 用的基本都是这个

  • JRockit

    • BEA

  • J9VM

    • IBM

JVM的体系结构




本地方法接口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)
    • 方法出口


局部变量表


  • 用于存储数据
  • 存储的类型有两种

    • 基本数据类型的局部变量

      • 包括方法参数

    • 对象的引用

      • 但是不存储对象的内容


  • 所需的内存空间在编译期间完成分配

    • 方法运行期间不会改变局部变量表的大小

  • 变量槽(Variable Slot)

    • 局部变量表的容量的最小单位
    • 一个slot最大32位

      • 对于64位的数据类型(long和double)会分配两个连续的slot

    • java通过索引定位的方法使用局部变量表

      • 从0开始
      • 一个Slot占1位
      • 非static方法第0个槽存储方法所属对象实例的引用


    • slot复用

      • 为了节省栈帧空间,slot是可以复用的
      • 如果某个变量失效了

        • 即超出了某个变量的作用域

      • 那么这个变量的slot就会交给其他变量使用
      • 副作用(这一段存疑):

        • 会影响系统的垃圾收集行为
        • 当某个变量失效后,因为它的slot可能还会交给其他变量复用,所以它占用的slot就不会被回收



  • 线程安全

    • 当局部变量表中的引用逃离了线程的范围
    • 也就是当一个引用可以被另一个线程拿到的时候
    • 就变成线程不安全的了

操作数栈


  • 一个栈
  • 元素可以是任意的java数据类型
  • 主要作用

    • 用于算数运算
    • 用于参数传递

  • 栈帧中用于计算的临时数据存储区
  • 举例

      1. public class OperandStack{
      2.     public static int add(int a, int b){
      3.         int c = a + b;
      4.         return c;
      5.     }
      6.     public static void main(String[] args){
      7.         add(100, 98);
      8.     }
      9. }
      复制代码



动态连接

指向运行时常量池中该栈帧所属方法引用
返回地址


  • 存放调用该方法的pc寄存器的值
  • 正常退出时会使用
  • 异常退出时会通过异常表来确认
可能出现的异常


  • StackOverflowError

    • 栈溢出错误
    • 如果一个线程在计算时所需的栈大小>配置允许最大的栈大小
    • 那么jvm将抛出该错误

  • OutOfMemoryError

    • 内存不足
    • 栈进行动态扩展时如果无法申请到足够的内存
    • 会抛出该错误

设置栈参数


  • -Xss

    • 设置栈大小
    • 通常几百K

jstack命令


  • jstack是JVM自带的JAVA栈追踪工具
  • 它用于打印出给定的java进程ID、core file、远程调试Java栈信息
  • 常用命令:

    • jstack [option] pid

      • 打印某个进程的堆栈信息

    • 选项

      • -F强制输出
      • -m显示本地方法的堆栈
      • -l显示锁信息


  • 使用案例

    • 查看进程死锁情况
    • 查看高cpu占用情况

      • 还需要用到top命令




被所有线程共享
主要存储

  • new关键字创建的对象实例

    • 数组

  • 静态变量
  • string池(1.8之后)
GC就是在堆上收集对象所占用的内存空间
堆的空间结构



  • 新创建的对象会存储在生成区
  • 年轻代内存满之后,会触发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常量池
  1. public void TestIntegerCache()
  2. {
  3.     public static void main(String[] args)
  4.     {
  5.         Integer i1 = new Integer(66);
  6.         Integer i2 = new Integer(66);
  7.         Integer i3 = 66;
  8.         Integer i4 = 66;
  9.         Integer i5 = 150;
  10.         Integer i6 = 150;
  11.         System.out.println(i1 == i2);//false
  12.         System.out.println(i3 == i4);//true
  13.         System.out.println(i5 == i6);//false
  14.     }
  15. }
复制代码
<ul>为什么i1 == i2为false

  • 因为是new了两个新对象,两个新对象的地址不一样
为什么i3 == i4为true
<ul>当Integer i3 = 66时,其实进行了一步装箱操作
通过Integer.valueOf()将66装箱成Integer
[code]public static Integer valueOf(int i) {        if (i >= IntegerCache.low && i

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

宁睿

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表