首页 > 其他分享 >JVM学习笔记记录

JVM学习笔记记录

时间:2022-09-23 12:44:47浏览次数:82  
标签:常量 笔记 记录 对象 回收 线程 引用 JVM 内存

目录

JVM学习记录

一、什么是JVM

1.1、定义:

Java Virtual Machine,Java程序的运行环境(Java二进制字节码的运行环境)

1.2、优点

  • 一次编写,到处运行
  • 自动内存管理,垃圾回收机制
  • 数组下标越界检查

1.3、JVM、JRE和JDK区别

img

二、内存结构

2.1、整体结构

img

2.2、程序计数器

作用:用于保存JVM中下一条所要执行的指令的地址

特点:

  • 线程私有
    • CPU会为每个线程分配时间片,当当前线程的时间片使用完以后,CPU就会去执行另一个线程中的代码
    • 程序计数器是每个线程私有的,当另一个线程的时间片用完,又返回来执行当前线程的代码时,通过程序计数器可以知道应该执行哪一句指令
  • 不会存在内存溢出

2.3、虚拟机栈

定义:

  • 每个线程运行需要的内存空间,称为虚拟机栈
  • 每个栈由多个栈帧组成,对应着每次调用方法时所占用的内存
  • 每个线程只能有一个活动,对应着当前正在执行的方法

演示:

public class Main {
	public static void main(String[] args) {
		method1();
	}

	private static void method1() {
		method2(1, 2);
	}

	private static int method2(int a, int b) {
		int c = a + b;
		return c;
	}
}

img

在控制台中可以看到,主类中的方法在进入虚拟机栈的时候,符合栈的特点

问题辨析

  • 垃圾回收是否涉及栈内存?
    • 不需要。因为虚拟机栈中是由一个个栈帧组成的,在方法执行完毕后,对应的栈帧就会被弹出栈。所以无需通过垃圾回收机制去回收内存。
  • 栈内存的分配越大越好吗?
    • 不是。因为物理内存是一定的,栈内存越大,可以支持更多的递归调用,但是可执行的线程数就会越少。
  • 方法内的局部变量是否是线程安全的?
    • 如果方法内局部变量没有逃离方法的作用范围,则是线程安全
    • 如果如果局部变量引用了对象,并逃离了方法的作用范围,则需要考虑线程安全问题

内存溢出

Java.lang.stackOverflowError 栈内存溢出

发生原因

  • 虚拟机栈中,栈帧过多(无限递归)
  • 每个栈帧所占用过大

线程运行诊断

CPU占用过高

  • Linux环境下运行某些程序的时候,可能导致CPU的占用过高,这时需要定位占用CPU过高的线程

    • top命令,查看是哪个进程占用CPU过高
    • ps H -eo pid, tid(线程id), %cpu | grep 刚才通过top查到的进程号 通过ps命令进一步查看是哪个线程占用CPU过高
    • jstack 进程id 通过查看进程中的线程的nid,刚才通过ps命令看到的tid来对比定位,注意jstack查找出的线程id是16进制的需要转换

3、本地方法栈

一些带有native关键字的方法就是需要JAVA去调用本地的C或者C++方法,因为JAVA有时候没法直接和操作系统底层交互,所以需要用到本地方法

4、堆

定义

通过new关键字创建的对象都会被放在堆内存

特点

  • 所有线程共享,堆内存中的对象都需要考虑线程安全问题
  • 有垃圾回收机制

堆内存溢出

java.lang.OutofMemoryError :java heap space. 堆内存溢出

堆内存诊断 控制台命令:

jps

jmap

jconsole

jvirsalvm

5、方法区

结构img

内存溢出

  • 1.8以前会导致永久代内存溢出
  • 1.8以后会导致元空间内存溢出

常量池

二进制字节码的组成:类的基本信息、常量池、类的方法定义(包含了虚拟机指令)

通过反编译来查看类的信息

  • 在控制台输入 javap -v 类的绝对路径

  • 然后能在控制台看到反编译以后类的信息了

    • 类的基本信息

      img

    • 常量池

    img

    • 虚拟机中执行编译的方法(框内的是真正编译执行的内容,#号的内容需要在常量池中查找)\

      img

运行时常量池

  • 常量池
    • 就是一张表(如上图中的constant pool),虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量信息
  • 运行时常量池
    • 常量池是*.class文件中的,当该类被加载以后,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址

常量池与串池的关系

串池StringTable

特征

  • 常量池中的字符串仅是符号,只有在被用到时才会转化为对象
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是StringBuilder
  • 字符串常量拼接的原理是编译器优化
  • 可以使用intern方法,主动将串池中还没有的字符串对象放入串池中
  • 注意:无论是串池还是堆里面的字符串,都是对象

6、直接内存

  • 属于操作系统,常见于NIO操作时,用于数据缓冲区
  • 分配回收成本较高,但读写性能高
  • 不受JVM内存回收管理

文件读写流程

img 使用了DirectBuffer img 直接内存是操作系统和Java代码都可以访问的一块区域,无需将代码从系统内存复制到Java堆内存,从而提高了效率

释放原理

直接内存的回收不是通过JVM的垃圾回收来释放的,而是通过unsafe.freeMemory来手动释放

//通过ByteBuffer申请1M的直接内存
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1M);

申请直接内存,但JVM并不能回收直接内存中的内容,它是如何实现回收的呢?

allocateDirect的实现

public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}

DirectByteBuffer类

DirectByteBuffer(int cap) {   // package-private
   
    super(-1, 0, cap, cap);
    boolean pa = VM.isDirectMemoryPageAligned();
    int ps = Bits.pageSize();
    long size = Math.max(1L, (long)cap + (pa ? ps : 0));
    Bits.reserveMemory(size, cap);

    long base = 0;
    try {
        base = unsafe.allocateMemory(size); //申请内存
    } catch (OutOfMemoryError x) {
        Bits.unreserveMemory(size, cap);
        throw x;
    }
    unsafe.setMemory(base, size, (byte) 0);
    if (pa && (base % ps != 0)) {
        // Round up to page boundary
        address = base + ps - (base & (ps - 1));
    } else {
        address = base;
    }
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); //通过虚引用,来实现直接内存的释放,this为虚引用的实际对象
    att = null;
}

这里调用了一个Cleaner的create方法,且后台线程还会对虚引用的对象监测,如果虚引用的实际对象(这里是DirectByteBuffer)被回收以后,就会调用Cleaner的clean方法,来清除直接内存中占用的内存

public void clean() {
   if (remove(this)) {
       try {
           this.thunk.run(); //调用run方法
       } catch (final Throwable var2) {
           AccessController.doPrivileged(new PrivilegedAction<Void>() {
               public Void run() {
                   if (System.err != null) {
                       (new Error("Cleaner terminated abnormally", var2)).printStackTrace();
                   }

                   System.exit(1);
                   return null;
               }
           });
       }

对应对象的run方法

public void run() {
    if (address == 0) {
        // Paranoia
        return;
    }
    unsafe.freeMemory(address); //释放直接内存中占用的内存
    address = 0;
    Bits.unreserveMemory(size, capacity);
}
直接内存的回收机制总结
  • 使用了Unsafe类来完成直接内存的分配回收,回收需要主动调用freeMemory方法
  • ByteBuffer的实现内部使用了Cleaner(虚引用)来检测ByteBuffer。一旦ByteBuffer被垃圾回收,那么会由ReferenceHandler来调用Cleaner的clean方法调用freeMemory来释放内存

7、类加载器

8、双亲委派模式

三、垃圾回收

垃圾回收大致主要分为两个阶段:第一阶段为判断哪些对象符合回收的条件;第二阶段是对这些符合为垃圾的对象进行回收。

1、如何判断对象可以回收

引用计数法

  • 记录每个对象的被引用次数,若大于0则不能回收
  • 弊端:循环引用时,两个对象的计数都为1,导致两个对象都无法被释放

img

可达性分析算法

  • JVM中的垃圾回收器通过可达性分析来探索所有存活的对象
  • 扫描堆中的对象,看能否沿着GC Root对象为起点的引用链找到该对象,如果找不到,则表示可以回收
  • 可以作为GC Root的对象
    • 虚拟机栈(栈帧中的本地变量表)中引用的对象。 
    • 方法区中类静态属性引用的对象
    • 方法区中常量引用的对象
    • 本地方法栈中JNI(即一般说的Native方法)引用的对象

五种引用img强引用

只有GC Root都不引用该对象时,才会回收强引用对象

  • 如上图B、C对象都不引用A1对象时,A1对象才会被回收
软引用

当GC Root指向软引用对象时,在内存不足时,会回收软引用所引用的对象

  • 如上图如果B对象不再引用A2对象且内存不足时,软引用所引用的A2对象就会被回收

强引用,软引用,弱引用,幻象引用有什么区别?

https://cloud.tencent.com/developer/article/1632634

2、垃圾回收算法

  • 标记-清除

    img

    定义:标记清除算法顾名思义,是指在虚拟机执行垃圾回收的过程中,先采用标记算法确定可回收对象,然后垃圾收集器根据标识清除相应的内容,给堆内存腾出相应的空间

    • 这里的腾出内存空间并不是将内存空间的字节清0,而是记录下这段内存的起始结束地址,下次分配内存的时候,会直接覆盖这段内存

    缺点容易产生大量的内存碎片,可能无法满足大对象的内存分配,一旦导致无法分配对象,那就会导致jvm启动gc,一旦启动gc,我们的应用程序就会暂停,这就导致应用的响应速度变慢

  • 标记-整理

    img

    标记-整理 会将不被GC Root引用的对象回收,清楚其占用的内存空间。然后整理剩余的对象,可以有效避免因内存碎片而导致的问题,但是因为整体需要消耗一定的时间,所以效率较低

  • 复制

    • 定义:将内存分为等大小的两个区域,FROM和TO(TO中为空)。先将被GC Root引用的对象从FROM放入TO中,再回收不被GC Root引用的对象。然后交换FROM和TO。

    • 优缺点

      • 可以避免内存碎片的问题,但是会占用双倍的内存空间

标签:常量,笔记,记录,对象,回收,线程,引用,JVM,内存
From: https://www.cnblogs.com/malongfeistudy/p/16722324.html

相关文章

  • 今年前端面试太难了,记录一下自己的面试题
    react的优化shouldcomponentUpdatepureCompomentsetStateCPU的瓶颈(当有大量渲染任务的时候,js线程和渲染线程互斥)IO的瓶颈就是网络(如何在网络延迟客观存在的情况下......
  • 记录Windows下安装beego遇到的问题
    一、安装步骤1.任何位置创建一个文件夹2.cmd方式进入创建的文件夹、或者使用Golang编辑器打开(我就是用Golang打开的)3.在编辑器的命令行下依次输入如下代码#初始化项......
  • Java基础(学习笔记非本人纯原创)
    Java学习Java基础语法一、注释:注释的说明:1、注释是在程序指定位置添加的说明信息(例如衣服的吊牌等)2、注释不参与程序运行,仅起到说明作用注释的分类://......
  • uniapp学习笔记-transition过渡动画切换卡顿的问题
    //主页分页面1分页面2 发现从主页跳转到1和2页面不存在动画卡顿,不过1和2之间切换存在卡顿,猜测可能是动画都是zoomin个人探索的解决方法:先切换到......
  • 图数据库学习笔记
    目录图数据库学习笔记图数据库简介什么是图数据库图数据库如何表达数据示例应用场景社交网络应用实时推荐地理空间管理主数据管理(MasterDataManagerment)网络和数据中心管......
  • vue学习笔记(五):组件的重用
    父组件代码:放在views文件夹中<template><div><h1>父组件</h1><!--指定type和size,传值给组件--><mybuttontype="fail"size="middle">按......
  • sqlserver 表的空间收缩 执行记录
    收缩sqlserver表的空间:一个47亿条记录的表删除了,需要收缩一下磁盘空间,不够是删除打表里的部分数据还是整个表的数据,都需要执行收缩命名才能释放存储空间 执行命名:EXE......
  • 【笔记】搜索
    题单简单记录一下较典型的\(\text{or}\)较有思维含量的题。0X00P1123取数游戏考虑从上往下,从左往右依次取数。每次取完一个数将其标记掉,然后下次取前看周围八个是否......
  • ansible 笔记
    ansible基于ssh  主要组成部分  安装yum安装需要先安装epel源:yuminstall-yhttps://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm ......
  • SpringBoot中集成MobileIMSDK并实现记录所有用户以实现消息群发
    场景若依(基于SpringBoot的权限管理系统)集成MobileIMSDK实现IM服务端的搭建:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/111032404在上面将MobileIMSD......