java虚拟机2

JVM内存处理全流程

实例

  • package ex2;
    
    /**
     * @author King老师
     * VM参数
     * -Xms30m -Xmx30m  -XX:MaxMetaspaceSize=30m
     *
     */
    public class JVMObject {
          
          
        public final static String MAN_TYPE = "man"; // 常量
        public static String WOMAN_TYPE = "woman";  // 静态变量
        public static void  main(String[] args)throws Exception {
          
          
            Teacher T1 = new Teacher();
            T1.setName("Mark");
            T1.setSexType(MAN_TYPE);
            T1.setAge(36);
            for(int i =0 ;i<15 ;i++){
          
          
                System.gc();//主动触发GC 垃圾回收 15次--- T1存活
            }
            Teacher T2 = new Teacher();
            T2.setName("King");
            T2.setSexType(MAN_TYPE);
            T2.setAge(18);
            Thread.sleep(Integer.MAX_VALUE);//线程休眠
        }
    }
    
    class Teacher{
          
          
        String name;
        String sexType;
        int age;
    
        public String getName() {
          
          
            return name;
        }
        public void setName(String name) {
          
          
            this.name = name;
        }
    
        public String getSexType() {
          
          
            return sexType;
        }
        public void setSexType(String sexType) {
          
          
            this.sexType = sexType;
        }
        public int getAge() {
          
          
            return age;
        }
        public void setAge(int age) {
          
          
            this.age = age;
        }
    }
    
    
  • -Xms30m:堆的起始大小是30M

  • -Xmx30m:堆的最大大小是30M

  • -XX:MaxMetaspaceSize=30m:元空间最大大小是30M

  • -XX:+UseConcMarkSweepGC:使用cms垃圾回收器

  • -XX:-UseCompressedOops:禁止压缩指针的使用,因为虚拟机有优化技术,对吧对象头进行指针压缩,不方便我们观察对象头

申请流程

  • 1.JVM申请内存

    • 上面输入的VM参数
  • 2.初始化运行时数据区

    • 方法区和堆就被创建出来了
  • 3.类加载

    • JVMObject这个类被加载进来

    • JRE很多jar包也要加载进来

    • 方法区主要存放的是class、静态变量、常量

      对应上面的例子:JVMObject.class、Teacher.class、WOMAN_TYPE、MAN_TYPE

  • 4.执行方法

    • 运行main方

    • main方法

      1.启动线程创建虚拟机栈,栈帧-main被压入虚拟机栈

      2.Teacher T1 = new Teacher(),在堆里面创建一个Teacher对象,在栈里面的局部变量表里存放T1,代表Teacher对象的引用,指向Teacher对象

      3.Teacher T2 = new Teacher(),在堆里面创建一个Teacher对象,在栈里面的局部变量表里存放T2,代表Teacher对象的引用,指向Teacher对象

  • 5.创建对象

从底层深入理解运行时数据区

堆空间分代划分

  • 新生代

    • Eden

      Teacher1、Teacher2

    • From

    • To

  • 老年代

    • Tenured

GC概念

  • 垃圾回收

  • 在JVM中垃圾回收是自动的

  • System.gc(),主动触发gc,不推荐这样的代码,非常影响性能

JHSDB工具

准备工作

  • 可视化和命令行工具,来查看JVM的运行信息

  • java写的jdk的一个工具

  • 启动前保证/jdk/bin和/jre/bin目录下都有sawindbg.dll这个文件

  • 然后在/jdk/lib目录下,打开命令行工具,输入java -cp .\sa-jdi.jar sun.jvm.hotspot.HSDB

  • 启动G:\enjoy\JVM\ref-jvm3项目下的JVMObject类

  • 打开命令行工具,输入jps,看到JVMObject类的进程号为xx,此时测试为10588

查看指定进程

  • 在HSDB工具中,打开File->Attach to Hotspot process,就可以启动的进程,里面有很多的线程在运行
    • 点击main线程,就可以该线程的具体运行情况

查看对象

  • 所有类的信息,打开Tools->Object Histogram,
    • 在输入栏中输入全路径包名,就可以找到上面定义的Teacher
    • 当前测试数据,1380000000–King对象,142de950–Mark对象

查看堆的参数

  • 打开Tools->Heap Parameters

    • Heap Parameters:
      Gen 0:   eden [0x0000000013800000,0x00000000139a8080,0x0000000014000000) space capacity = 8388608, 20.70465087890625 used
        from [0x0000000014000000,0x0000000014000000,0x0000000014100000) space capacity = 1048576, 0.0 used
        to   [0x0000000014100000,0x0000000014100000,0x0000000014200000) space capacity = 1048576, 0.0 usedInvocations: 0
      
      Gen 1: concurrent mark-sweep generation
      free-list-space[ 0x0000000014200000 , 0x0000000015600000 ) space capacity = 20971520 used(4%)= 920488 free= 20051032
      Invocations: 15
      
      
    • Eden:起始地址13800000~14000000

      From:起始地址14000000~14100000

      To :起始地址14100000~14200000

      Old :起始地址14200000~15600000

    • King对象在Eden区,Mark对象在老年代

      $为什么Mark对象在老年代?

      (1).我们执行了15次System.gc(),进行了15次垃圾回收,但是Mark对象并不会被回收,是存活的,因为这个对象一直被T1引用,不能被回收

      (2).T1首先在Eden区,经过一次垃圾回收进入From区,然后一直在From和To之间来回存在,会有一个分代年龄,达到15次以后就会进入老年代

    • JVM的堆是分代划分的,这种划分是连续的

    • 所有对象都是优先在Eden区分配

查看栈

  • 在线程列表信息中,点击一下main线程,然后再点击标签栏上的第二项Stack memory

    • ┌ 类似于这样一个符号框住的部分就是一个栈帧,所以当前栈里面有两个栈帧

    • 第一个栈帧-----当前栈帧,同时是一个native方法栈帧,此时这个栈里面栈顶是一个栈帧,是一个native方法,因为是调用的Thead.sleep方法,这就是一个native方法

    • 第二个栈帧-----是一个java方法栈帧,也就是main方法的栈帧,

      (1)黑色箭头是操作数栈

虚拟机优化技术

栈的优化技术

  • 栈帧信息
  • 操作数栈
  • 局部变量表

实例

  • package ex2;
    /**
     * @author King老师
     * VM参数
     * JVM对栈帧空间的优化
     *
     **/
    public class JVMStack {
          
          
    
        public int work(int x) throws Exception{
          
          
            int z =(x+5)*10;//局部变量表有, 32位
            Thread.sleep(Integer.MAX_VALUE);
            return  z;
        }
        public static void main(String[] args)throws Exception {
          
          
            JVMStack jvmStack = new JVMStack();
            jvmStack.work(10);//10  放入main栈帧  10 ->操作数栈
        }
    }
    
    
  • main方法执行时,10要被放到main栈帧的操作数栈中

  • 在HSDB工具中,发现main栈帧和work栈帧中间有一个内存地址重合了,实现了数据的共享,也就是main栈帧的操作数栈和work栈帧的局部变量表共享了10这个基础类型的变量,节约了内存空间

深入辨析堆和栈

存储内容

  • 主要是虚拟机栈
  • 存储的内容主要是基础的八大数据类型,以及对象的引用
  • 方法执行完了,内存就会释放掉

与线程的关系

  • 是线程私有的,对于其他线程不可见

空间大小

  • 默认大小是1M,同时跑1000个线程才占1G

存储内容

  • 存储的是对象,几乎所有的对象都是存储在堆中的
  • new一个对象时,类中的成员变量,如果基础类型,也是在堆中的

与线程的关系

  • 线程共享,对其余线程可见

空间大小

  • 远远大于栈的,性能调优和垃圾回收主要关注的是栈

内存溢出

  • OOM,OutOfMemoryError

栈溢出

  • 情况1:死递归,没有出口的递归,不断的创建栈帧,超过1M后报错
  • 情况2:机器有2个g,堆占1个g,方法区占800M,我们跑200个线程,占200M,如果同时跑201个线程就超过计算机的内存了,但是这种情况模拟不了,计算机会死机,因为栈区整体不能被限制,只能限制单个虚拟机栈的大小

堆溢出

  • -XX:+PrintGCDetails

    • 打印GC详细信息
  • 情况1:假设堆设置最大30M,new String[35000000],创建一个35M的对象

  • package ex2.oom;
    
    
    /**
     * @author  King老师
     * VM Args:-Xms30m -Xmx30m -XX:+PrintGCDetails
     * 堆内存溢出(直接溢出)
     */
    public class HeapOom {
          
          
       public static void main(String[] args)
       {
          
          
           String[] strings = new String[35*1000*1000];  //35m的数组(堆)
       }
    }
    
    
    • OutOfMemoryError:Java heap space
    • 这就是直接就溢出了
  • 情况2:写一个死循环,不停的往list里面添加元素

  • package ex2.oom;
    
    import java.util.LinkedList;
    import java.util.List;
    
    
    /**
     * @author King老师
     * VM Args:-Xms30m -Xmx30m     堆的大小30M
     * 造成一个堆内存溢出(分析下JVM的分代收集)
     * GC调优---生产服务器推荐开启(默认是关闭的)
     * -XX:+HeapDumpOnOutOfMemoryError
     */
    public class HeapOom2 {
          
          
       public static void main(String[] args) throws Exception {
          
          
           List<Object> list = new LinkedList<>(); // list   当前虚拟机栈(局部变量表)中引用的对象
           int i =0;
           while(true){
          
          
               i++;
               if(i%1000==0) Thread.sleep(10);
               list.add(new Object());// 不能回收2,  优先回收再来抛出异常。
           }
    
       }
    }
    
    
    • OutOfMemoryError:GC overhead limit exceeded

      ##如果垃圾回收占据98%的资源,回收率不足2%

      $$发生这个错有可能是内存泄漏造成的

    • 这种异常首先打印普通gc,然后一片片的Full gc,然后回收不动了

      为什么不断的gc呢?

      因为对象是在慢慢增长的,不是一次性把堆搞垮的,JVM认为这是正常的,所以会进行gc,但是gc很多次后,发现毫无作用,就会抛出这个异常

方法区溢出

  • 使用cglib,写一个while循环,不断的去加载这个类

  • package ex2.oom;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    /**
     * 方法区导致的内存溢出
     * VM Args: -XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M
     * */
    public class MethodAreaOutOfMemory {
          
          
    
        public static void main(String[] args) {
          
          
            while (true) {
          
          
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(TestObject.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
          
          
                    public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
          
          
                        return arg3.invokeSuper(arg0, arg2);
                    }
                });
                enhancer.create();
            }
        }
    
        public static class TestObject {
          
          
            private double a = 34.53;
            private Integer b = 9999999;
        }
    }
    
    
  • -XX:MetaspaceSize=10M

    • 方法区大小
  • -XX:MaxMetaspaceSize=10M

    • 方法区最大
  • 抛出异常OutOfMemoryError:metaspace

本机直接内存溢出

  • 没有被JVM虚拟化的称为直接内存

  • package ex2.oom;
    
    import java.nio.ByteBuffer;
    
    /**
     * @author King老师
     * VM Args:-XX:MaxDirectMemorySize=100m
     * 限制最大直接内存大小100m
     */
    public class DirectOom {
          
          
        public static void main(String[] args) {
          
          
            //直接分配128M的直接内存
            ByteBuffer bb = ByteBuffer.allocateDirect(128*1024*1204);
        }
    }
    
    
  • -XX:MaxDirectMemorySize=100m

    • 限制直接内存最大100M
  • 抛出错误OutOfMemoryError:Direct buffer memory

常量池

  • 运行时常量池一定是放在方法区中间的,这是虚拟机规范里面定义的

  • class常量池,就是class对象里面的东西

    • javap -v JVMObject.class

    • Classfile /G:/enjoy/JVM/ref-jvm3/out/production/ref-jvm3/ex2/JVMObject.class
        Last modified 2021-3-11; size 1071 bytes
        MD5 checksum 630079e5e3c9aa2c35874d2e4c645eec
        Compiled from "JVMObject.java"
      public class ex2.JVMObject
        minor version: 0
        major version: 52
        flags: ACC_PUBLIC, ACC_SUPER
      Constant pool:
         #1 = Methodref          #18.#46        // java/lang/Object."<init>":()V
         #2 = Class              #47            // ex2/Teacher
         #3 = Methodref          #2.#46         // ex2/Teacher."<init>":()V
         #4 = String             #48            // Mark
         #5 = Methodref          #2.#49         // ex2/Teacher.setName:(Ljava/lang/String;)V
         #6 = Class              #50            // ex2/JVMObject
         #7 = String             #51            // man
         #8 = Methodref          #2.#52         // ex2/Teacher.setSexType:(Ljava/lang/String;)V
         #9 = Methodref          #2.#53         // ex2/Teacher.setAge:(I)V
        #10 = Methodref          #54.#55        // java/lang/System.gc:()V
        #11 = String             #56            // King
        #12 = Class              #57            // java/lang/Integer
        #13 = Long               2147483647l
        #15 = Methodref          #58.#59        // java/lang/Thread.sleep:(J)V
        #16 = String             #60            // woman
        #17 = Fieldref           #6.#61         // ex2/JVMObject.WOMAN_TYPE:Ljava/lang/String;
        #18 = Class              #62            // java/lang/Object
        #19 = Utf8               MAN_TYPE
        #20 = Utf8               Ljava/lang/String;
        #21 = Utf8               ConstantValue
        #22 = Utf8               WOMAN_TYPE
        #23 = Utf8               <init>
        #24 = Utf8               ()V
        #25 = Utf8               Code
        #26 = Utf8               LineNumberTable
        #27 = Utf8               LocalVariableTable
        #28 = Utf8               this
        #29 = Utf8               Lex2/JVMObject;
        #30 = Utf8               main
        #31 = Utf8               ([Ljava/lang/String;)V
        #32 = Utf8               i
        #33 = Utf8               I
        #34 = Utf8               args
        #35 = Utf8               [Ljava/lang/String;
        #36 = Utf8               T1
        #37 = Utf8               Lex2/Teacher;
        #38 = Utf8               T2
        #39 = Utf8               StackMapTable
        #40 = Class              #47            // ex2/Teacher
        #41 = Utf8               Exceptions
        #42 = Class              #63            // java/lang/Exception
        #43 = Utf8               <clinit>
        #44 = Utf8               SourceFile
        #45 = Utf8               JVMObject.java
        #46 = NameAndType        #23:#24        // "<init>":()V
        #47 = Utf8               ex2/Teacher
        #48 = Utf8               Mark
        #49 = NameAndType        #64:#65        // setName:(Ljava/lang/String;)V
        #50 = Utf8               ex2/JVMObject
        #51 = Utf8               man
        #52 = NameAndType        #66:#65        // setSexType:(Ljava/lang/String;)V
        #53 = NameAndType        #67:#68        // setAge:(I)V
        #54 = Class              #69            // java/lang/System
        #55 = NameAndType        #70:#24        // gc:()V
        #56 = Utf8               King
        #57 = Utf8               java/lang/Integer
        #58 = Class              #71            // java/lang/Thread
        #59 = NameAndType        #72:#73        // sleep:(J)V
        #60 = Utf8               woman
        #61 = NameAndType        #22:#20        // WOMAN_TYPE:Ljava/lang/String;
        #62 = Utf8               java/lang/Object
        #63 = Utf8               java/lang/Exception
        #64 = Utf8               setName
        #65 = Utf8               (Ljava/lang/String;)V
        #66 = Utf8               setSexType
        #67 = Utf8               setAge
        #68 = Utf8               (I)V
        #69 = Utf8               java/lang/System
        #70 = Utf8               gc
        #71 = Utf8               java/lang/Thread
        #72 = Utf8               sleep
        #73 = Utf8               (J)V
      {
              
              
        public static final java.lang.String MAN_TYPE;
          descriptor: Ljava/lang/String;
          flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
          ConstantValue: String man
      
        public static java.lang.String WOMAN_TYPE;
          descriptor: Ljava/lang/String;
          flags: ACC_PUBLIC, ACC_STATIC
      
        public ex2.JVMObject();
          descriptor: ()V
          flags: ACC_PUBLIC
          Code:
            stack=1, locals=1, args_size=1
               0: aload_0
               1: invokespecial #1                  // Method java/lang/Object."<init>":()V
               4: return
            LineNumberTable:
              line 10: 0
            LocalVariableTable:
              Start  Length  Slot  Name   Signature
                  0       5     0  this   Lex2/JVMObject;
      
        public static void main(java.lang.String[]) throws java.lang.Exception;
          descriptor: ([Ljava/lang/String;)V
          flags: ACC_PUBLIC, ACC_STATIC
          Code:
            stack=2, locals=3, args_size=1
               0: new           #2                  // class ex2/Teacher
               3: dup
               4: invokespecial #3                  // Method ex2/Teacher."<init>":()V
               7: astore_1
               8: aload_1
               9: ldc           #4                  // String Mark
              11: invokevirtual #5                  // Method ex2/Teacher.setName:(Ljava/lang/String;)V
              14: aload_1
              15: ldc           #7                  // String man
              17: invokevirtual #8                  // Method ex2/Teacher.setSexType:(Ljava/lang/String;)V
              20: aload_1
              21: bipush        36
              23: invokevirtual #9                  // Method ex2/Teacher.setAge:(I)V
              26: iconst_0
              27: istore_2
              28: iload_2
              29: bipush        15
              31: if_icmpge     43
              34: invokestatic  #10                 // Method java/lang/System.gc:()V
              37: iinc          2, 1
              40: goto          28
              43: new           #2                  // class ex2/Teacher
              46: dup
              47: invokespecial #3                  // Method ex2/Teacher."<init>":()V
              50: astore_2
              51: aload_2
              52: ldc           #11                 // String King
              54: invokevirtual #5                  // Method ex2/Teacher.setName:(Ljava/lang/String;)V
              57: aload_2
              58: ldc           #7                  // String man
              60: invokevirtual #8                  // Method ex2/Teacher.setSexType:(Ljava/lang/String;)V
              63: aload_2
              64: bipush        18
              66: invokevirtual #9                  // Method ex2/Teacher.setAge:(I)V
              69: ldc2_w        #13                 // long 2147483647l
              72: invokestatic  #15                 // Method java/lang/Thread.sleep:(J)V
              75: return
            LineNumberTable:
              line 14: 0
              line 15: 8
              line 16: 14
              line 17: 20
              line 18: 26
              line 19: 34
              line 18: 37
              line 21: 43
              line 22: 51
              line 23: 57
              line 24: 63
              line 25: 69
              line 26: 75
            LocalVariableTable:
              Start  Length  Slot  Name   Signature
                 28      15     2     i   I
                  0      76     0  args   [Ljava/lang/String;
                  8      68     1    T1   Lex2/Teacher;
                 51      25     2    T2   Lex2/Teacher;
            StackMapTable: number_of_entries = 2
              frame_type = 253 /* append */
                offset_delta = 28
                locals = [ class ex2/Teacher, int ]
              frame_type = 250 /* chop */
                offset_delta = 14
          Exceptions:
            throws java.lang.Exception
      
        static {
              
              };
          descriptor: ()V
          flags: ACC_STATIC
          Code:
            stack=1, locals=0, args_size=0
               0: ldc           #16                 // String woman
               2: putstatic     #17                 // Field WOMAN_TYPE:Ljava/lang/String;
               5: return
            LineNumberTable:
              line 12: 0
      }
      SourceFile: "JVMObject.java"
      
    • 这里面的Constant pool,这个属于class常量池,也可以叫静态常量池

class常量池(静态常量池)

  • 类的版本

  • 有哪些字段,有哪些方法

  • 如果是接口的话,还有接口描述

  • 编译时存放的两个东西

    • 一个是字面量,String a = “b”,int a = 13,b和13就是字面量
    • 另一个是符号引用,Person类里面引用了Tools类,编译的时候,不知道Tools的真实内存地址,只能用符号引用代替Org.king.Tools(全路径名),类加载的时候就可以根据符号引用找到实际的内存地址

运行时常量池

  • 直接引用放在运行时常量池,jdk1.7以后运行时常量池的实现可以放在堆上,但是逻辑上还是属于方法区

字符串常量池

  • 官方资料里面是没有字符串常量池这个概念的
  • 核心还是String类,所以应该弄清String类

String类分析(final)

  • 两个对象

    • 主要是对char value[]的数组进行封装
    • int hash(哈希值)
  • final char value[]数组;Stirng对象的不可变

    • 被final修饰,不可更改

      为什么这么设计?

    • 1.可变就不安全,可能被恶意修改

    • 2.hash值也不会轻易改变,hash值也是唯一的,可以确保hashmap key-value的稳定

    • 3.这种设计可以去实现字符串常量池

String的创建方式

第一种
  • String str = “abc”;
    • 编译加载时,会在常量池中创建常量,如果有了就不会创建了,而是直接返回这个字符串的引用
    • “abc”运行时,返回常量池中的字符串引用
第二种
  • String str = new String(”abc“)
    • 编译加载时,会在常量池中创建常量“abc”
    • new方法,会在堆空间创建String对象,并引用常量池中的字符串对象char数组"abc",并返回String对象引用
    • 所以直接用String str = “abc”更好,可以少一个String对象
第三种
  • public void mode3(){
          
          
        Location location = new Location();
        location.setCity("深圳");
        location.setRegion("南山");
    }
    
  • 直接在堆里面,不会在常量池中创建

第四种
  • String str = “ab” +“cd"+“ef”
    • 会生成3个对象,“ab”、“abcd”、“abcdef”,效率最低
    • 编译器会自动优化成“abcded"
第五种
  • String str = "abcdef";
    for(int i=0; i<1000; i++) {
          
          
        str = str + i;
    }
    
  • 对于这种大循环,编译器会优化出StringBuilder,进行append拼接,如果就几个,就跟第四种是一样的

    • String str = "abcdef";
      for(int i=0; i<1000; i++) {
              
              
          str = (new StringBuilder(String.valueOf(str)).append(i).toString());
      }
      
第六种
  • String str = new String(“king”).intern();

    String str2 = new String(“king”).inter();

    • intern()会在字符串常量池中去检查,有直接返回引用,没有则在字符串常量池中进行创建
    • a==b 输出true(这里需要认为是对象),因为调用了intern()方法,不会创建新的对象了
    • 推特公司优化内存,“深圳”、“北京“相同的字符串常量
  • 对象是堆中间的,引用是栈、方法区中的

  • ==是比较值,当是对象,比较的是地址,equal比较的是否同一个对象

总结

  • 1.字符串常量池在哪个区 方法区

  • 2.“abc"重复,统一放在字符串常量池

  • 3.inter()方法

    // 去字符串常量池找到是否有等于该字符串的对象,如果有,直接返回对象的引用。

  • 4.虚拟栈的优化

    参数10是被调用方法的局部变量

    如果改成对象引用,此时传递的是引用,一样可以共享

  • 5.字符串常量池不会被垃圾回收,垃圾回收的主要对象是堆里面的对象

  • 6.值传递、引用传递、int缓存,需要一节课讲清楚

  • 7.生产环境频繁gc怎么排查?需要体系化的jvm知识

  • 8.String如果不加final,就不是常量了,就不需要字符串常量池了,如果不加,就是一个对象,就放在堆里面了

猜你喜欢

转载自blog.csdn.net/Markland_l/article/details/114684511