前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >jvm学习笔记

jvm学习笔记

作者头像
小王不头秃
发布2024-06-19 16:54:46
1520
发布2024-06-19 16:54:46
举报

前言

在b站看的网课,留下的笔记与截图

JVM

java二进制字节码运行环境

  • 一次编写,到处运行的基础 jvm对外提供了一致的运行环境
  • 自动内存管理机制—垃圾回收功能
  • 数组下标越界检查 有些语言无法进行检查,可能导致越界的数组数据占据了其他程序的空间
  • 多态

jre与jvm

jvm+基础类库--------jre

jre+编译工具--------jdk

内存结构

java源码-----》二进制字节码--------》解释器翻译为机器语言--------》cpu来执行

程序计数器:

记录下一条jvm指令的执行地址,

例如指令如下

代码语言:javascript
复制
1 lalalal
2 啦啦啦啦啦

1执行时,会将2放入程序计数器中,待1执行结束之后就在程序计数器中取得2进行执行,这样依次进行执行

一般是使用寄存器来实现的

特点
  • 线程私有 每一个线程都有自己的程序计数器,当分给该线程的时间片结束之后,假如线程还未完成,则需要进行记录下一条指令的地址,等到重新分配时间片时可以继续执行该程序
  • 不会内存溢出

每个线程需要一个栈,存放着多个栈帧,一个栈帧对应一个方法,每个方法运行时需要的内存

栈帧
  • 参数
  • 局部变量
  • 返回地址

一个栈中可以有多个栈帧

每个线程只能有一个活动栈帧()

这里方法调用的栈可以在idea中直观看到

tips
  • 垃圾回收不涉及到栈内存
  • 栈内存大小可通过 -Xss size 来设置程序栈大小
    • windows取决于虚拟内存大小
    • linux/macos/Oracle默认为1024kb
    • 并不是栈越大越好,栈越大可运行的线程越少
    • idea通过以下方式进行设置
  • 局部变量是否线程安全
    • 就看变量是线程私有的还是共享的
    • 线程私有就不需要考虑线程是否安全,共享的话就需要考虑
    • 如果方法内的局部变量未逃离方法作用范围,就是线程安全的,例如如果该变量作为返回值,那么其他线程就可能会拿到这个变量,那么就会导致不安全
栈内存溢出
  • 栈内存放的栈帧数量超出了栈的大小就回导致占内存溢出===》递归就可以做到,一直递归不停,就会产生栈内存溢出
  • 栈帧过大也会导致栈内存溢出
线程运行诊断

定位

代码语言:javascript
复制
top:定位哪一个进程对cpu占用高

ps H -eo pid,tid,%cpu | grep 进程id:进一步定位哪一个线程引起的 
jstack 进程id:列出该进程的所有的线程的信息

长时间未输出结果

jstack 进程id 列出所有的线程信息,可以展示出死锁信息

本地方法栈

本地方法不是由java编写的,因为java无法直接与计算机底层进行交互,因此需要通过本地方法来进行对底层的交互,一般本地方法是由c或c++编写的。

这些本地方法利用的就是本地方法栈

  • 线程共享的,需要考虑线程安全问题
  • new创建的对象都是存放在堆
  • 有垃圾回收机制
堆内存溢出

不断生成新对象,并且所有对象一直在使用,就会导致堆内存溢出

修改堆空间大小

代码语言:javascript
复制
-Xmx 8m

以下代码可以用来测试堆空间是否溢出的问题

代码语言:javascript
复制
   public static void main(String[] args) {
        int count=0;
        String s="123";
    try {
        List<String> list =new LinkedList<>();
        count=0;
        while(true){
            s=s+s;
            list.add(s);
            count++;
        }
    }
    catch (Exception e){
        e.printStackTrace();
        System.out.println(count);
    }
    }

启示

服务器内存越跑越小,可能是因为有一些内存未被来得及回收

堆内存诊断

jps工具:查看系统中有哪些java进程

代码语言:javascript
复制
jps

jmap工具:查看堆内存占用情况

代码语言:javascript
复制
jmap -heap 进程id

测试代码

代码语言:javascript
复制
  public void testThread(){
        try {
            System.out.println("1");
            Thread.sleep(30000);

            byte array[]=new byte[1024*1024*10];
            System.out.println("2");
            Thread.sleep(30000);
            array=null;
            System.gc();
            System.out.println("3");
            Thread.sleep(30000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

jconsole工具:有ui的,多功能的检测工具

代码语言:javascript
复制
jconsole
  • jvisualvm
方法区

存放方法,构造器,成员属性之类的数据

方法区在虚拟机启动时就创建,逻辑上是堆的组成部分,但不同的厂商不一定按照这个实现

方法区溢出
运行时常量池

常量池:

就是一张常量表,虚拟机指令根据这张常量表找到要执行的类名和方法名,参数类型,字面量等信息

运行时常量池:

常量池是*.class中的,当该类被加载,他的常量池信息就会放入运行时常量池中,并且把里面的符号地址变为真实地址

反编译
代码语言:javascript
复制
javap -v Main.class

这里编译的class文件在out文件夹下

如下图就是常量池

常量池加载过程

最开始时常量池中是没有数据的,是在一步步加载中填入的,是一种懒加载机制

  • 常量池存放常量的结构是hash表,每次需要常量时就会以常量在hash表中查找,若不存在则创建
常量池与串池的区别

运行常量池(constant pool)中存放的仅仅是符号,而并非对象,串池(StringTable)中存放的则是字符串对象,作用就是防止创建重复的字符对象

1.6和1.8中常量池和串池存放位置的差别

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LaIaj5oj-1642754222930)(jvm/image-20220118165905767.png)]

StringTable(串池)的垃圾回收
代码语言:javascript
复制
-Xmx16m -XX: +PrintStringTablestatistics -XX: +PrintGCDetails -verbose:gc

-Xmx16m :设置堆的大小

-XX: +PrintStringTablestatistics :打印串池中的对象信息

-XX: +PrintGCDetails -verbose:gc : 若存在垃圾回收,则进行打印信息

-XX: StringTableSize=200000 : 因为串池的结构是数组加链表这种方式,数组中的一个关键字称为一个桶,这里就是设计桶的数量,桶的数量越大性能越好,但相对的占用空间就可能过大,造成资源浪费

StringTable性能调优
  • 可以适当调大STringTable的数组长度也就是桶的数量,可以减少冲突从而使得查找效率得到提升
  • 使用串池可对系统性能进行调优,若是new出来的字符串对象只存在堆中,并不会进入串池中,这时若是存在大量的重复的字符串对象,可以采用串池来对这些数据进行去重,所谓去重就是将利用串池的特性将大量的重复的字符串对象只存储一个字符串对象,其他对象只是对其的引用

直接内存

操作系统内存

ByteBuffer为什么读写更快

使用ByteBuffer实际上就是通过直接内存进行读取

传统io操作

因为java无法直接访问系统资源,因此需要再建立一个java缓冲区,整个过程就是:本地文件==》系统缓存==》java缓存==》使用

直接内存的io方式

此时文件直接放入直接内存缓冲区中,java可以直接读取,减少了一层缓冲区,从而使得速度得到提升

直接内存的溢出

因为DM不受java垃圾机制管理,因此可能会出现内存溢出问题

测试代码

直接内存分配与释放的原理

通过代码来申请直接内存的大小,这里直接内存不受jvm管理,因此需要在任务管理器里查看

代码语言:javascript
复制
ByteBuffer byteBuffer=ByteBuffer.allocateDirect(1024*1024*1024);
        System.out.println("try");
        System.in.read();
        System.out.println("end");

直接内存的回收是通过unsafe对象来进行回收的

禁用显示回收的影响
代码语言:javascript
复制
System.gc()     //显式的垃圾回收

关闭显示垃圾回收机制,即System.gc()无效

代码语言:javascript
复制
-XX:+DisableExplicitGC    

垃圾回收

如何判断对象可以回收
引用计数法

即有一个引用该对象,则计数器加一,为0则释放,

弊端

循环引用:即A引用B,B也引用A,没有其他引用他们,但是他们互相引用,都无法释放,就会导致内存泄漏

可达性分析算法(java中使用的垃圾回收机制)

根对象:肯定不可以当作垃圾回收的对象

如果一个对象没有被根对象引用,就可以回收

解析

扫描堆中的对象,看是否能够沿着GC Root对象为起点的引用链找到该对象,找不到,表示可以回收

抓取当前堆使用的快照

代码语言:javascript
复制
jmap -dump :format=b,live,file=1.bin 21384

-dump ==》存储

format=b ==》存储二进制文件

live ==》只记录那些未被垃圾回收的内容

file=1.bin 设置存储文件

21384 进程id(jps获取活动的java的进程id)

mat查看gc root对象

System class 系统对象

Busy Monitor 加锁的对象

Thread 活动线程中的对象,局部对象所引用的对象可左gcroot,同时参数中对象也是可以作为gcroot对象

可以作为GC Root的对象

System class 系统对象

Busy Monitor 加锁的对象

Thread 活动线程中的对象,局部对象所引用的对象可左gcroot,同时参数中对象也是可以作为gcroot对象

四种引用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fXQDinnG-1642754222939)(jvm/image-20220119162157683.png)]

强引用

例如new出来的就是强引用

特点

  • 只要沿着gc root链可以找到该对象,就无法被垃圾回收,例B对A-A4,以及ByteBuffer
  • 只要没有直接或则间接对其强引用之后就可以垃圾回收了
软引用

特点

  • 只要未被gc root直接引用,垃圾回收时就会自动回收,例从C到软引用再到A2,当然此时需要B不在引用A2时,就可以发生垃圾回收

应用场景

强引用下导致堆空间溢出

代码语言:javascript
复制
   /**
         * 强引用会导致堆空间不够用
         */
        int _1M=1024*1024;
        List<byte[]> list=new LinkedList<>();
       for(int i=0;i<5;i++){
           list.add(new byte[_1M*2]);
       }

软引用下

在这种方式下其实就是使用软引用进行嵌套强引用,也就是SoftReference嵌套byte数组,从而达到软引用的目的,这样一旦出现堆内存不够就会进行释放软引用对象

代码语言:javascript
复制
List<SoftReference<byte[]>> list=new LinkedList<>();
for (int i = 0; i <100 ; i++) {
    SoftReference softReference=new SoftReference(new byte[_1M*2]);
    list.add(softReference);
}

这个过程中一旦出现了堆空间不够,就会清理软引用对象引用的对象,但是此时软引用对象还在,虽然占据内存比较小,但最好还是清理一下

使用引用队列进行处理,下方代码,关联了软引用队列,软引用关联的对象回收时,软引用对象会加入队列中,从而实现回收

这里我个人的理解就是判断这些软引用有没有引用其他对象,如果没有,则将其在队列中删除,从而将队列对软引用对象的强引用解除掉,从而实现对象的回收

代码语言:javascript
复制
   /**
         * 关联了软引用队列,软引用关联的对象回收时,软引用对象会加入队列中,从而实现回收
         */
        ReferenceQueue<byte[]> referenceQueue=new ReferenceQueue<>();
        List<SoftReference<byte[]>> list=new LinkedList<>();
        for (int i = 0; i <10 ; i++) {
            SoftReference softReference=new SoftReference(new byte[_1M*2],referenceQueue);
            list.add(softReference);
        }
        Reference<? extends byte[]> poll = referenceQueue.poll();
        while(poll!=null){
            referenceQueue.remove();
            poll=referenceQueue.poll();
        }
弱引用

特点

  • 当没有强引用时,若内存不够会回收软引用的对象,无论够不够都会回收弱引用对象
  • 释放之后,因为软弱引用仍占用空间,因此需将二者放入引用队列中,进行循环依次释放空间

应用实例

虚引用(必须配合引用队列)

之前的bytebuffer就是需要一个虚引用对象Cleaner,因为ByteBuffer若是在强引用引用结束之后,会对其进行回收,但是此时直接内存不由jvm管理,这就需要把虚引用对象放置在引用队列中,从而实现对直接内存的回收(虚引用对象就是Cleaner,来调用Unsafe的Free memory()来进行释放)

终结器引用(必须配合引用队列)

例如A对象重写了finalize,并且A即将被垃圾回收,会调用finalize方法,将放置一个终结器引用到队列中,会有一个优先级很低的线程会来检查队列中有无需要释放的引用,从而实现对象的回收

垃圾回收算法

标记清除算法
  • 判断哪些对象未被gcroot对象引用,对其进行标记
  • 对标记对象进行清除,将对象的首地址存储在队列中,在新的对象分配地址时,会在队列中进行查找,判断有无空间,在进行分配

优点

  • 清除速度快

缺点

  • 会产生大量的碎片空间,导致总剩余空间虽然足够,但有些大空间对象仍无法分配到足够的内存,导致内存溢出
标记整理
  • 判断哪些对象未被gcroot对象引用,对其进行标记
  • 清楚时,将可用的对象向前移动,从而使得内存空间更见紧凑,从而实现空间更加连续

优点

没有内存碎片

缺点

耗费时间较多,例如如果有引用对象引用就是将移动的对象,需要修改大量内容,造成浪费时间

复制算法

划分成两片区域,将from中存活的对对象复制到to中,待复制结束之后就对from所有的对象进行回收,然后交换from与to的位置

优点

  • 没有碎片空间

缺点

需要占用双倍的内存空间

小结

三种算法都会协同工作

分代回收
  • 长时间使用的放在老年代中,用完即弃的放在新生代中,也可以认为重要的,常用的在老年代中,而不常使用的在新生代中
  • 清理时先清理新生代,如果内存实在不够,再开始清理老年代

笔记

字符串

字符串拼接
代码语言:javascript
复制
String a="123"
String b="55"
String c=a+b

c的赋值其实是先调用Stringbuilder的toString方法生成一个新的String对象,然后返回给c

但是如下图这样

代码语言:javascript
复制
String a="a"
String b="b"
String c="a"+"b";
String d="ab";

此时,c==d是true,因为在编译时,javac会默认认为"a"+“b"就是"ab”,因此直接调用常量池的内容就可以

主动将字符串对象放入串池

itern():将字符串对象放入串池,若不存在,则放入,否则不进行放入

垃圾回收

原来的占用内存->回收后的内存,Full GC表示垃圾回收资源太少,因此采用更加强烈的垃圾回收,即软链接垃圾回收

初次回收时会将所有的弱引用对象引用的对象回收掉,若是回收之后内存依然不够,会对软引用在进行回收

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-06-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • JVM
    • jre与jvm
      • 内存结构
        • 程序计数器:
        • 方法区
      • 直接内存
        • 垃圾回收
          • 如何判断对象可以回收
          • 四种引用
        • 垃圾回收算法
          • 标记清除算法
          • 标记整理
          • 复制算法
          • 小结
          • 分代回收
      • 笔记
        • 字符串
          • 字符串拼接
          • 主动将字符串对象放入串池
        • 垃圾回收
        相关产品与服务
        检测工具
        域名服务检测工具(Detection Tools)提供了全面的智能化域名诊断,包括Whois、DNS生效等特性检测,同时提供SSL证书相关特性检测,保障您的域名和网站健康。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档