学习笔记-JVM

这篇具有很好参考价值的文章主要介绍了学习笔记-JVM。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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数据类型

  • 主要作用

    • 用于算数运算

    • 用于参数传递

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

  • 举例

    • 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);
          }
      }
      

动态连接

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

返回地址

  • 存放调用该方法的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常量池

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
    }

}
  • 为什么i1 == i2false

    • 因为是new了两个新对象,两个新对象的地址不一样
  • 为什么i3 == i4true

    • Integer i3 = 66时,其实进行了一步装箱操作

    • 通过Integer.valueOf()66装箱成Integer

    • public static Integer valueOf(int i) {
              if (i >= IntegerCache.low && i <= IntegerCache.high)
                  return IntegerCache.cache[i + (-IntegerCache.low)];
              return new Integer(i);
      }
      
    • IntegerCacheInteger的静态内部类

      • 它通过static{}静态代码块,将-128~127的值全部缓存在了一个Integer数组中
  • 为什么i5 == i6false

    • 因为150超出了缓存的范围

    • 重新new了一个对象

      public static void main(String[] args){
      Integer i1 = new Integer(4);
      Integer i2 = new Integer(6);
      Integer i3 = new Integer(10);
      System.out.print(i3 == i1+i2);//true
      }

  • 为什么i3 == i1 + i2true

    • 因为对象在进行+运算时是会进行拆箱的

    • 拆箱成int再进行数值比较

String常量池

在1.6之后在堆中,在1.6及之前在永久代中

目的是为了减少字符串对内存的占用,提高效率

String是由final修饰的类,不可被继承

  • String str = new String("abcd");

    • 每次都会创建一个新对象
  • String str = "abcd"

    • 先在栈上创建一个引用

    • 然后去String常量池找是否有"abcd"

      • 若有,直接让引用指向它

      • 没有,向常量池添加一个"abcd",再指向它

字符串+连接问题

String a = "a1";   
String b = "a" + 1;   
System.out.println((a == b)); //result = true  

String a = "atrue";   
String b = "a" + "true";   
System.out.println((a == b)); //result = true 

String a = "a3.4";   
String b = "a" + 3.4;   
System.out.println((a == b)); //result = true 

JVM在编译时就会优化成+号连接后的值

字符串引用+连接问题

public static void main(String[] args){
       String str1 = "a";
       String str2 = "ab";
       String str3 = str1 + "b";
       System.out.print(str2 == str3);//false
    }

因为是变量,在编译时无法确定结果

JVM会将+连接优化成StringBuilder的append方法

反编译后的内容

public class TestDemo
{

    public TestDemo()
    {
    }

    public static void main(String args[])
    {
        String s = "a";
        String s1 = "ab";
        String s2 = (new StringBuilder()).append(s).append("b").toString();
        System.out.print(s1 = s2);
    }
}

但要注意的是,用final修饰过的字符串引用,会被视为常量,而非变量

intern()

s.intern()

  • 将字符串对象尝试放入串池中

    • 如果有,则不放入

    • 如果没有,则放入

      • 但是在1.6版本时

      • 会复制一份,然后把复制品放入串池中

  • 返回串池中的对象

调优

一些参数

  • -XX:+PrinStringTableStatistic

    • 打印串池的统计信息
  • -XX:+PrintGCDetails -verbose:gc

    • 打印GC信息
  • -XX:StringTableSize=<数值>

    • 调整StringTable底层hash表的长度

调优思路

  • 调整桶个数

    • 使用-XX:StringTableSize=<数值>

      • 因为StringTable底层是一个hashtable

      • 所以我们可以通过调整长度来减少发生碰撞的次数

      • 从而减少链表的长度

      • 最终提高速度

  • 考虑将字符串对象是否入池

    • 使用intern()方法

    • 将字符串入池

.class文件中的内容

反编译指令javap -v <.class文件>

  • 类的基本信息

    • 更改时间

    • MD5

    • 类全名

    • 版本信息

    • 父类信息

    • 接口信息

  • 常量池

    • 一张表

    • 虚拟机根据这张常量表找到要执行的

      • 类型、方法名、参数类型、字面量等信息
  • 类的方法定义

    • 构造方法

    • 成员方法

直接内存

  • Direct Memory

    • 常见于NIO操作时,用于数据缓冲区

    • 分配回收成本较高,但读写性能高

    • 不受JVM内存回收管理

    • 会出现内存溢出OOM

  • Unsafe

    • 在底层是通过Unsafe对象分配的空间

    • 于是也需要手动调用Unsafe对象的freeMemory方法释放空间

    • ByteBuffer的实现类内部,使用了Cleaner(虚引用)来检测ByteBuffer对象

    • 一旦ByteBuffer对象被垃圾回收

    • 就会由ReferenceHandler线程通过Cleaner的clean方法调用freeMemory来释放直接内存

  • 禁用显示垃圾回收对直接内存的影响

    • -XX:+DisableExplicitGC禁止显示的垃圾回收

    • 无效掉System.gc()

    • 所以使用直接内存的时候,应该手动使用Unsafe对象

  • 原始的IO操作

  • 由于java不能直接访问系统内存

  • 所以数据在被读入到系统缓冲区后,

  • 要再读进java缓冲区

  • 然后才能访问

  • 直接内存

  • 存在于系统内存中

  • 但是java和系统都能够访问文章来源地址https://www.toymoban.com/news/detail-458458.html

到了这里,关于学习笔记-JVM的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【操作系统学习笔记】文件管理1.5

    参考书籍: 王道考研 视频地址: Bilibili 逻辑结构: 从用户角度看,由创建文件的用户自己设计的 无结构文件 有结构文件 顺序文件 顺序存储 链式存储 索引文件 索引顺序文件 物理结构: 从操作系统看,由操作系统决定 连续分配 链接分配 索引分配

    2024年03月09日
    浏览(42)
  • 《30天自制操作系统》学习笔记(七)

    30天自制操作系统光盘代码在下面链接,但是没有编译仿真工具: https://gitee.com/zhanfei3000/30dayMakeOS 仿真工具在下面链接: https://gitee.com/909854136/nask-code-ide 这是一个集成的编译仿真工具,只需要把上面仿真工具的文件夹: nask-code-ide-mastercrtools 复制到源码文件加下,并改名为

    2024年01月19日
    浏览(29)
  • 软考学习笔记--操作系统-进程管理

    进程管理是一个具有独立功能的程序关于数据集合的一次可以并发执行的运行活动,是系统进行资源分配和调度的基本单位。相对于程序,进程是动态的概念,而程序是静态的概念,是指令的集合。进程具有动态性和并发性,需要一定的资源来完成任务。在大多数操作系统中

    2024年01月18日
    浏览(30)
  • 安装Linux-CentOS7.9操作系统虚拟机环境(适用于学习、测试环境)

    具体名称以及路径根据自己的实际情况在个人pc或者其他的设备上进行创建。 阿里云开源镜像站地址 https://developer.aliyun.com/mirror/ 清华大学开源镜像站地址 https://mirrors.tuna.tsinghua.edu.cn/ 有需要的朋友可以自行下载 VMware Workstation pro下载链接,许可证可自行浏览器搜索获取 http

    2024年02月03日
    浏览(37)
  • 银河麒麟操作系统基础学习笔记十三

    tar可以将很多文件打包成一个文件,目录也可以。bzip2和gzip只能压缩单个文件。 bzip2程序能提供比gzip更高的压缩比,是gzip的升级版,推荐使用bzip2进行压缩。

    2024年02月12日
    浏览(40)
  • 《操作系统真象还原》学习笔记:第七章 中断

    由于 CPU 获知了计算机中发生的某些事,CPU 暂停正在执行的程序,转而去执行处理该事件的程序,当这段程序执行完毕后,CPU 继续执行刚才的程序。整个过程称为中断处理,也称为中断。 把中断按事件来源分类,来自CPU外部的中断就称为外部中断,来自CPU内部的中断就称为

    2024年02月11日
    浏览(39)
  • 【操作系统OS】学习笔记:第二章 进程与线程 (上)【哈工大李治军老师】

    基于本人观看学习 哈工大李治军老师主讲的操作系统课程 所做的笔记,仅进行交流分享 特此鸣谢李治军老师,操作系统的神作! 如果本篇笔记帮助到了你,还请点赞 关注 支持一下 ♡𖥦)!! 主页专栏有更多,如有疑问欢迎大家指正讨论,共同进步! 给大家跳段街舞感谢支持

    2024年02月02日
    浏览(38)
  • 查看虚拟机操作系统版本

    看虚拟机操作系统版本,可以按照以下步骤进行: 打开虚拟机并登录到操作系统。 打开终端,输入以下命令: 这个命令会显示操作系统的版本信息,包括发行版名称、版本号和描述。 另外,你也可以尝试以下命令来获取操作系统的版本信息: 这个命令会显示操作系统的名

    2024年02月12日
    浏览(39)
  • Java编程技巧:获取ip地址、通过ip获取地理位置、获取客户端操作系统、获取客户端浏览器、获取主机名、获取操作系统、获取系统架构

    说明: 大家直接去对应项目位置找到代码,然后看着复制就行了 1.1、若依(自己写的代码) 项目:https://gitee.com/y_project/RuoYi 子模块:ruoyi-common 所在类:com.ruoyi.common.utils.IpUtils 所在方法:getIpAddr 详细位置:整个方法 1.2、Snowy(借助hutool工具包) 项目:https://gitee.com/xiaonuo

    2024年02月04日
    浏览(49)
  • 编辑虚拟网络和安装操作系统

    创建的虚拟机搭建一个虚拟双网卡网络,使它能与宿主机(安装VMware的主机)进行通信并能够通过宿主机连接上互联网。 一、编辑虚拟网络 1.进入虚拟网络编辑器 单击主界面菜单中的“编辑”→“虚拟网络编辑器”,弹出“虚拟网络编辑器”对话框  接下来,单击“更改设

    2024年02月07日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包