JVM探究--详细版

jvm的位置

在这里插入图片描述
jvm是运行在操作系统之上的,与硬件没有直接的交互,但是却可以调用本地方法(native)与硬件交流。

jvm的架构

在这里插入图片描述

类加载器

在这里插入图片描述
类加载器负责加载字节码文件到内存中,字节码文件在文件的开头有特定的文件标识。这些文件的内容将会被转换成方法区中的运行时数据结构,ClassLoader只负责.class文件的加载,至于能否运行,则是由执行引擎决定的。

  • 类加载器的种类
    在这里插入图片描述

    • 启动类加载器:Bootstrap(C++语言编写)
    • 扩展类加载器:Extension(Java语言编写)
    • 系统类加载器:System(也叫做AppClassLoader,加载当前application的classpath的所有类文件)
      public class UserObject {
        public static void main(String[] args) {
          Object object = new Object();
          // null (C++语言编写,Java中作为顶级超类)
          System.out.println(object.getClass().getClassLoader());
      
          UserObject userObject = new UserObject();
          // sun.misc.Launcher$AppClassLoader@18b4aac2(此时获得的是系统类加载器)
          System.out.println(userObject.getClass().getClassLoader());
          // sun.misc.Launcher$ExtClassLoader@74a14482(系统类加载器的父类是扩展类加载器)
          System.out.println(userObject.getClass().getClassLoader().getParent());
        }
      }
      
      在这里插入图片描述
  • 双亲委派机制

当一个类加载器收到了类加载请求,它不会去尝试自己加载这个类,而是把这个加载请求委托给父类完成,而每一个层次的类加载器的做法都是如此,因此所有的加载请求都会被发送到Bootstrap类加载器中。只有当父类加载器反馈无法完成这个请求时,子类加载器才会去尝试加载。

这样做的好处是很明显的,例如,用户自定义一个java.lang.String类,系统类加载器不会直接加载该类,而是将加载请求向上传递直到启动类加载器,启动类加载器会发现在rt.jar中已经存在java.lang.String,则会将该类加载进来。最终这样就保证了尽管使用不同的类加载器,结果得到的都是同一个Java原生的String对象(Java语言特性中的安全性体现)。如果的确需要我们自定义的String,那么我们应该避免使用jvm自带的三个类加载器,继承ClassLoader类实现我们自己的类加载器

本地方法接口和栈

​本地接口的作用是融合不同的编程语言为Java所利用,具体做法就是在内存中开辟一块区域处理标记为native的方法,在本地方法栈中进行登记,执行引擎执行时加载本地类库。

程序计数器

每个线程都有一个程序计数器,是线程私有的,可以简单理解为一个指针指向方法区中的方法字节码(指向即将要执行的指令代码),是一个非常小的内存空间,由执行引擎读取下一条指令。若执行的是一个native方法,则程序计数器是空的。程序计数器主要用来完成分支、循环、跳转以及异常处理等基础功能。

方法区

各线程共享的运行时内存区域,方法区中存储了每一个类的结构信息Class(字段、常量池、方法数据、构造函数、普通方法的字节码… …)方法区是一个规范,不同的jvm的实现是不一样的最典型的是永久代和元空间。方法区是一个常驻内存的区域,存放JDK自身所携带的类和接口的元数据,也就是说存储的是运行环境所必需的信息(例如连接数据库的驱动… …),垃圾回收器不会回收方法区,关闭jvm才会释放该区域所占用的内存空间。

​ 注意:实例变量是存储在堆中的,和方法区无关!

  • 永久代:JDK8以前
  • 元空间:JDK8及以后

​ Java栈主管Java程序的运行,其生命周期跟随线程的生命周期,栈是线程私有的。栈不存在垃圾回收的问题。栈中存储三类数据:

  • 本地变量:输入参数(形参)和输出参数(返回值)以及方法内的变量
  • 栈操作:记录出栈和入栈的操作
  • 栈帧数据:包括类文件,方法等
    在这里插入图片描述
    注意:栈溢出 Exception in thread “main” java.lang.StackOverflowError是一个ERROR!

栈 堆 方法区的关系
在这里插入图片描述

在这里插入图片描述

参数调优

  • 元空间
    JDK8及以后的元空间并不在虚拟机中,而存在于本机物理内存。因此,默认情况下,元空间的大小受到本地内存的限制。类的元数据放入本地内存中,字符串池和类的静态变量放入堆中。
  • 堆空间
    • -Xms:设置初始分配大小,默认为物理内存的1/64

    • -Xmx:最大分配内存,默认为物理内存的1/4

      (以上两个参数强烈建议设置一致,避免出现内存峰值波动)

    • -XX:+PrintGCDetails:输出详细的GC处理日志

      e.g -Xms10m -Xmx10m -XX:+PrintGCDetails

GC日志信息分析

[GC (Allocation Failure) [PSYoungGen: 2028K->499K(2560K)] 2028K->771K(9728K), 0.0011613 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1998K->510K(2560K)] 2270K->1485K(9728K), 0.0007512 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2392K->304K(2560K)] 4579K->2793K(9728K), 0.0005962 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1596K->336K(2560K)] 7721K->7067K(9728K), 0.0008696 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 336K->0K(2560K)] [ParOldGen: 6731K->3637K(7168K)] 7067K->3637K(9728K), [Metaspace: 3196K->3196K(1056768K)], 0.0061910 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 1259K->160K(2560K)] 6109K->6221K(9728K), 0.0007565 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 160K->0K(2560K)] [ParOldGen: 6061K->3028K(7168K)] 6221K->3028K(9728K), [Metaspace: 3207K->3207K(1056768K)], 0.0054982 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 45K->96K(1536K)] 5497K->5548K(8704K), 0.0009081 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 96K->128K(2048K)] 5548K->5580K(9216K), 0.0004201 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 128K->0K(2048K)] [ParOldGen: 5452K->4241K(7168K)] 5580K->4241K(9216K), [Metaspace: 3215K->3215K(1056768K)], 0.0081965 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 4241K->4241K(9216K), 0.0004658 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 4241K->4221K(7168K)] 4241K->4221K(9216K), [Metaspace: 3215K->3215K(1056768K)], 0.0064194 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 2048K, used 85K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 1024K, 8% used [0x00000000ffd00000,0x00000000ffd154f8,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
ParOldGen total 7168K, used 4221K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 58% used [0x00000000ff600000,0x00000000ffa1f4e8,0x00000000ffd00000)
Metaspace used 3261K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 353K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread “main” java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:674)
at java.lang.StringBuilder.append(StringBuilder.java:208)
at se.Test.main(Test.java:14)

[GC (Allocation Failure) [PSYoungGen: 854K->587K(2048K)]

GC类型:PSYoungGen;GC前新生代内存占用:854K;GC后新生代内存占用:587K;新生代总内存大小:2048K(大约1/3堆内存)

2874K->3164K(9216K), 0.0008812 secs]

GC前jvm堆内存占用:2874K;GC后jvm堆内存占用:3164K;jvm堆总内存占用:9216K;GC耗时:0.0008812 secs

[Times: user=0.00 sys=0.00, real=0.00 secs]

用户耗时:user=0.00;系统耗时:sys=0.00;实际耗时:real=0.00 secs;

注意:java.lang.OutOfMemoryError是ERROR!

GC算法

  • 引用计数法(jvm的实现一般不采用此种方式)
    在这里插入图片描述
  • 复制算法

HotSpot JVM把年轻代分为了三个部分:一个Eden区和两个Survivor区,默认比例为8:1:1。一般情况下,新创建的对象都会被分配到Eden区(特殊的大对象特殊处理),这些对象经过第一次minor GC后,存活的将会被转移到Survivor区,在Survivor区中每经历一次minor GC,年龄就会增加1,当年龄增加到一定程度时,就会被移动到年老代中。由于年轻代的对象回收率特别高,所以年轻代的GC算法采用的是复制算法,它的基本思想是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另一块上面。复制算法不会产生内存碎片。缺点是耗费空间(S0的容量有多大则S1的容量也必须和S0匹配。并且需要双倍空间)
在这里插入图片描述

  • 标记清除算法
    算法分为标记和清除两个阶段,先标记出要回收的对象,然后统一回收这些对象。当程序运行时,若可用内存即将耗尽,GC线程就会被触发并将程序暂停,随后将要回收的对象标记一遍,最后统一回收这些对象,完成标记清理工作后继续让程序恢复运行。
    它的优点是不需要额外的空间,缺点是耗时严重且会产生内存碎片
    老年代一般是标记清除和标记整理(压缩)的混合实现。
    在这里插入图片描述

  • 标记压缩(整理)算法
    标记压缩(整理)算法是在标记清除算法的基础上,再次扫描,并向一端滑动存活的对象(压缩)。优点是没有内存碎片,缺点是增加了移动对象的成本。
    在这里插入图片描述

JMM(Java内存模型)

jmm是jvm的一种规范,定义了jvm的内存模型。它屏蔽了各种硬件和操作系统的访问差异。它的主要目的是解决由于多线程共享内存进行通信时,存在的本地内存数据不一致等带来的问题。可以保证并发编程中的原子性、可见性和有序性。

由于jvm运行程序的实体是线程,而每个线程创建时jvm多会为其创建一个工作内存(也可称为栈空间),工作内存是每个线程的私有数据区域。而Java内存模型中所有变量都存储在主内存,主内存是共享数据区域,所有线程都可以访问,但线程对变量的操作必须在工作内存中进行,首先要将变量从主内存拷贝到线程自己的工作空间,然后对变量进行操作,待操作完成后将变量写回主内存。不能直接操作主内存的变量,各个线程中的工作内存中存储着主内存的变量副本的拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信必须通过主内存来完成。

  • 三个特性

    • 可见性
    • 原子性
    • 有序性
      在这里插入图片描述
  • jmm关于同步的规定

    1. 线程解锁前,必须把共享变量的值同步回主内存
    2. 线程加锁前,必须读取主内存的最新值到自己的工作内存
    3. 加锁解锁是同一把锁
  • 代码示例

    • 反面演示

      public class Test
      {
         public static void main(String[] args)
         {
            MT mt = new MT();
            // 一个线程对共享变量中的值进行修改
            new Thread(() -> {
               System.out.println(Thread.currentThread().getName() + "---------------------------");
               try
               {
                  Thread.sleep(3000);
               } catch (InterruptedException e)
               {
                  e.printStackTrace();
               }
               mt.updateNum();
               System.out.println(Thread.currentThread().getName() + "num value is " + mt.num);
            }).start();
            
            // 主线程读取共享变量中的值
            while (mt.num == 10)
            {
               // 上面的操作对主线程来说是不可见的,因此将不会跳出循环
            }
            System.out.println(Thread.currentThread().getName() + "done!");
         }
      }
      
      class MT
      {
         // 主内存中的共享变量
         int num = 10;
      
         public void updateNum()
         {
            this.num = 20;
         }
      }
      

      result:
      在这里插入图片描述

    • 正确演示

      使变量num具有可见性

      volatile int num = 10;
      

      result:
      在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值