JVM学习笔记

JVM位置

在这里插入图片描述

JVM体系结构

在这里插入图片描述

双亲委派机制

  1. 说明:当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,直到BootStrap启动类加载器,如果上级的类加载器没有加载,自己才会去加载这个类。(向上委托,向下加载)
  2. 作用:
    • 防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
    • 保证核心.class不能被篡改。通过委托方式,不会去篡改核心.class,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。
  3. 类加载器类别:(由上至下)BootstrapClassLoader(启动类加载器)-> ExtClassLoader (标准扩展类加载器)-> AppClassLoader(系统类加载器)-> CustomClassLoader(用户自定义类加载器)

native关键字(主要用于方法上)

  1. 一个native方法就是一个Java调用非Java代码的接口。一个native方法是指该方法的实现由非Java语言实现,比如用C或C++实现。
  2. 在本地方法栈中(native method stack)登记native方法,在执行引擎之行的时候加载本地库。
  3. 在定义一个native方法时,并不提供实现体(比较像定义一个Java Interface),因为其实现体是由非Java语言在外面实现的。主要是因为JAVA无法对操作系统底层进行操作,但是可以通过JNI(java native interface java本地方法接口)调用其他语言来实现底层的访问。
  4. 举例:Thread类中的start() 方法中调用一个start0()的native方法。

PC寄存器

程序计数器

每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法中的方法字节码(用来存储指向一条指令的地址,也即将要之行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。

方法区

方法区:Method Area方法区

  • 方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,
  • 方法区在物理上存在堆中,为了和堆区分开,也称非堆
  • 简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间;
  • 静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池(static,final,Class,常量池)存在方法区中,但是实例变量存在堆内存中,和方法区无关

  • 栈:先进后出,后进先出
  • 为什么main()先执行,最后结束:每个线程都有自己的栈,栈中的数据都是以栈帧的格式存在;在这个线程上正在执行的每一个方法都各自对应一个栈帧;栈帧是一个内存区块,是一个数据集维系着方法执行过程中的各种数据信息
  • 栈:8大基本类型+对象引用+实例的方法
  • 栈是运行时的单位,Java 虚拟机栈,线程私有,生命周期和线程一致。
  • 描述的是 Java 方法执行的内存模型:每个方法在执行时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。(方法头开始入栈,结束出栈,方法里面调用别的方法 新的方法就会把旧的压在底下,最上面永远是正在执行的方法,也对应先入后出。)

  • Heap,一个jvm只有一个堆内存,堆内存的大小是可以调节的
  • new一个对象时将对象的引用放在Java栈中,把类的具体事例、方法、常量、变量放在堆中,保存我们所有引用类型的真实对象。
  • 堆内存细分:(下图为jdk8之前的,jdk8以后永久存储区的名字改为“元空间”)在这里插入图片描述
    • 永久区:这个区域常驻内存的。用来存放JDK自身携带的class对象,interface元数据,储存的是java运行时的一些环境或类信息,这个区域不存在垃圾回收,关闭VM虚拟机就会释放这个区域的内存
    • Jdk1.6之前:永久代,常量池在方法区
      Jdk1.7:永久代,慢慢退化,去永久代,常量池在堆中
      Jdk1.8之后:无永久代,常量池在元空间
  • 什么时候出现永久区满:一个启动类,加载了大量的第三方jar包,tomcat部署了太多的应用,大量动态生成的反射类,不断的被加载,直到内存满,就会出现OOM

堆结构
在这里插入图片描述

元空间逻辑上在堆内存中,物理上不在堆内存中

OOM内存溢出,解决方法

解决方法:

  • 尝试扩大堆内存看结果
  • 分析内存,看一下哪个地方出现问题(专业工具,如JProfiler等)

JProfiler工具分析OOM原因

JProfiler可以分析Dump内存文件,快速定位内存泄漏

JProfiler使用步骤

  1. 编写一个能够产生OOM的方法用来测试
    import java.util.ArrayList;
    
    public class Demo {
        byte[] array=new byte[1*1024*1024];
    
        public static void main(String[] args) {
            ArrayList<Demo> list=new ArrayList<Demo>();
            int count=0;
            try {
                while (true){
                    list.add(new Demo());
                    count++;
                }
            } catch (Error e) {
                System.out.println("count"+count);
                e.printStackTrace();
            }
        }
    }
    
  2. 安装JProfiler(官网下载即可)
  3. 在IDEA中引入JProfiler的插件在这里插入图片描述
    重启idea完成后可以看到右上角有了JProfiler的标志,代表成功安装。
    在这里插入图片描述
  4. 修改idea当前类的虚拟机的配置参数为-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
    几个不同的配置参数的含义:
    • -Xms:设置初始化内存分配大小
    • -Xmx8m:设置最大分配内存
    • -XX:+HeapDumpOnOutOfMemoryError:当发生OOM错误时自动生成dump文件
    • -XX:+PrintGCDetails:打印GC垃圾回收信息
      在这里插入图片描述
  5. 这时候运行main方法,若出现OOM错误就会自动生成DUMP文件。在这里插入图片描述
    进入当前项目的的父文件夹下就可以看到
    在这里插入图片描述
  6. 点击目录下第一个生成的文件就会自动跳转进入JProfiler的页面,从下面两张图中我们可以分别看出占用最多内存的Object和代码中发生错误的行数。
    在这里插入图片描述
    在这里插入图片描述

jvm调优

对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数。

JVM性能调优方法和步骤:

  1. 监控GC的状态;
  2. 生成堆的dump文件;
  3. 分析dump文件;
  4. 分析结果,判断是否需要优化;
  5. 调整GC类型和内存分配;
  6. 不断的分析和调整

垃圾回收GC

只在堆和方法区中存在垃圾回收

JVM在进行GC时,并不是对这三个区域统一回收,大部分时候,回收都是新生代。

  • 新生代
  • 幸存区(from、to)幸存区中谁空谁就是幸存to区,位置会交换。
  • 老年区
  • 每一次垃圾回收之后都会将Eden区活的对象移到幸存区中,一旦Eden区被GC后,就会是空的。
  • 当一个对象经历了15次(默认,可以通过设置MaxTenuringRhreshold来设定进入老年代的时间)GC后还没有被回收,就会进入老年区

GC的两种类型:轻GC(普通的GC,针对新生代,偶尔清一下幸存区)、重GC(全局GC,所有的区都清)

GC常见算法

  • 引用计数器(JVM一般不会采用,不高效)
    • 为每个对象分配一个计数器,该对象引用了几次就加几
    • 计数器本身也会有消耗
    • 计数为0的就会被清除
  • 标记清除法
    • 过程:
      • 标记:扫描对象,对活着的对象进行标记
      • 清除:对没有标记的对象进行清除
    • 优点:不需要额外的空间(比复制算法需要的空间少)
    • 缺点:两次扫描,严重浪费时间,会产生内存碎片
  • 标记整理/压缩
    • 在标记清除的基础上再优化,进行压缩。再次扫描,向一段移动存活的对象,防止内存碎片的产生。
    • 多了一个移动成本。可以进行优化:先标记清除几次,再进行压缩。
  • 复制
    • 幸存区(from、to),幸存区中谁空谁就是幸存to区,位置会交换。复制算法会将from区中的东西复制到to区中,复制完成之后to区就变成了from区,from区就变成了to区
    • 好处:没有内存碎片
    • 坏处:浪费了内存空间,多了一半空间(to区)永远是空的。假设对象100%存活,每次需要全部复制,成本太高。
    • 最佳使用场景:对象存活度较低的时候,适用于新生区中(新生区中使用的就是复制算法

总结

内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)

内存整齐度:复制算法=标记压缩算法>标记清除算法

内存利用率:标记压缩算法=标记清除算法>复制算法

没有最好的算法,只有最合适的

GC也被称为分代收集算法:

  • 年轻代存活率低,适合复制算法
  • 老年代区域大,存活率高,适合标记清除+标记压缩混合实现

JMM:Java Memory Model

Java内存模型

常见面试题

  • JVM的内存模型和分区
  • 堆里面的分区(Eden、from、to、老年区)
  • GC算法
  • 轻GC和重GC在什么时候发生
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值