其他分享
首页 > 其他分享> > 黑马JVM教程——自学笔记(一)

黑马JVM教程——自学笔记(一)

作者:互联网

一、引言

1.1、什么是JVM

定义: Java Virtual Machine - java的运行环境(java二进制字节码的运行环境)

好处

比较:

JVM JRE JDK的区别

image

1.2、学JVM有什么用

1.3、常见的JVM

JVM是一套规范,只要遵循这个规范,自己也可以写一个JVM

1.4、学习路线

image

  1. JVM内存结构
  2. GC垃圾回收
  3. Java Class
  4. ClassLoader
  5. JIT Compiler

二、内存结构

2.1、程序计数器

image

2.1.1 定义

Program Counter Register程序计数器(寄存器)

2.1.2 作用

image

2.2、虚拟机栈

2.2.1 定义

Java Virtual Machine Stacks ( Java虚拟机栈)

image

问题辨析

2.2.2 栈内存溢出

Java.lang.stackOverflowError 栈内存溢出

2.2.3 线程运行判断

CPU占用过高

程序运行时间很长时间没有结果

image

2.3、本地方法栈

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

image

2.4、堆

2.4.1 定义

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

特点

image

2.4.2 堆内存溢出

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

-Xmx :最大堆空间

2.4.3 堆内存诊断

  1. jps工具

    • 查看当前系统中有哪些java进程
  2. jmap工具

  1. jconsole工具
  1. jvisualvm工具

    垃圾回收之后,内存占用依然很高:

    使用堆转储 dump 抓取堆内存快照 分析堆内存使用最大的那些对象,对照代码就可以排查出问题

2.5、方法区

2.5.1 定义

方法区为所有线程共享,存储了类的结构相关的信息,比如成员变量,方法数据,成员方法,构造器,运行时常量池等。

方法区在虚拟机启动时被创建,在逻辑上是堆区的组成一部分。

方法区是一种规范,而永久代或者元空间都是方法区的一种实现。

image

2.5.2 组成

image

1.8的方法区实现方式是元空间,不受JVM管理。

2.5.3 方法区内存溢出问题

image

image

2.5.4 运行时常量池

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

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

image

image

image

image

image

2.5.5 串池StringTable

特征

image

用来放字符串对象且里面的元素不重复

public class StringTableStudy {
	public static void main(String[] args) {
		String a = "a"; 
		String b = "b";
		String ab = "ab";
	}
}

常量池中的信息,都会被加载到运行时常量池中,但这是a b ab 仅是常量池中的符号,还没有成为java字符串

0: ldc           #2                  // String a
2: astore_1
3: ldc           #3                  // String b
5: astore_2
6: ldc           #4                  // String ab
8: astore_3
9: return

当执行到 ldc #2 时,会把符号 a 变为 “a” 字符串对象,并放入串池中(hashtable结构 不可扩容)

当执行到 ldc #3 时,会把符号 b 变为 “b” 字符串对象,并放入串池中

当执行到 ldc #4 时,会把符号 ab 变为 “ab” 字符串对象,并放入串池中

最终StringTable [“a”, “b”, “ab”]

注意:字符串对象的创建都是懒惰的,只有当运行到那一行字符串且在串池中不存在的时候(如 ldc #2)时,该字符串才会被创建并放入串池中。

使用拼接字符串变量对象创建字符串的过程

public class StringTableStudy {
	public static void main(String[] args) {
		String a = "a";
		String b = "b";
		String ab = "ab";
		//拼接字符串对象来创建新的字符串
		String ab2 = a+b; 
	}
}

反编译后的结果

	 Code:
      stack=2, locals=5, args_size=1
         0: ldc           #2                  // String a
         2: astore_1
         3: ldc           #3                  // String b
         5: astore_2
         6: ldc           #4                  // String ab
         8: astore_3
         9: new           #5                  // class java/lang/StringBuilder
        12: dup
        13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
        16: aload_1
        17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String
;)Ljava/lang/StringBuilder;
        20: aload_2
        21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String
;)Ljava/lang/StringBuilder;
        24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/Str
ing;
        27: astore        4
        29: return

通过拼接的方式来创建字符串的过程是:StringBuilder().append(“a”).append(“b”).toString()

最后的toString方法的返回值是一个新的字符串,但字符串的和拼接的字符串一致,但是两个不同的字符串,一个存在于串池之中,一个存在于堆内存之中

String ab = "ab";
String ab2 = a+b;
//结果为false,因为ab是存在于串池之中,ab2是由StringBuffer的toString方法所返回的一个对象,存在于堆内存之中
System.out.println(ab == ab2);

使用拼接字符串常量对象的方法创建字符串

public class StringTableStudy {
	public static void main(String[] args) {
		String a = "a";
		String b = "b";
		String ab = "ab";
		String ab2 = a+b;
		//使用拼接字符串的方法创建字符串
		String ab3 = "a" + "b";
	}
}

反编译后的结果

 	  Code:
      stack=2, locals=6, args_size=1
         0: ldc           #2                  // String a
         2: astore_1
         3: ldc           #3                  // String b
         5: astore_2
         6: ldc           #4                  // String ab
         8: astore_3
         9: new           #5                  // class java/lang/StringBuilder
        12: dup
        13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
        16: aload_1
        17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String
;)Ljava/lang/StringBuilder;
        20: aload_2
        21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String
;)Ljava/lang/StringBuilder;
        24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/Str
ing;
        27: astore        4
        //ab3初始化时直接从串池中获取字符串
        29: ldc           #4                  // String ab
        31: astore        5
        33: return

intern方法 1.8

调用字符串对象的intern方法,会将该字符串对象尝试放入到串池中

无论放入是否成功,都会返回串池中的字符串对象

注意:此时如果调用intern方法成功,堆内存与串池中的字符串对象是同一个对象;如果失败,则不是同一个对象

例1

public class Main {
	public static void main(String[] args) {
		//"a" "b" 被放入串池中,str则存在于堆内存之中
		String str = new String("a") + new String("b");
		//调用str的intern方法,这时串池中没有"ab",则会将该字符串对象放入到串池中,此时堆内存与串池中的"ab"是同一个对象
		String st2 = str.intern();
		//给str3赋值,因为此时串池中已有"ab",则直接将串池中的内容返回
		String str3 = "ab";
		//因为堆内存与串池中的"ab"是同一个对象,所以以下两条语句打印的都为true
		System.out.println(str == st2);
		System.out.println(str == str3);
	}
}

例2

public class Main {
	public static void main(String[] args) {
        //此处创建字符串对象"ab",因为串池中还没有"ab",所以将其放入串池中
		String str = "ab";
        //"a" "b" 被放入串池中,str则存在于堆内存之中
		String str2 = new String("a") + new String("b");
        //此时因为在创建str3时,"ab"已存在与串池中,所以放入失败,但是会返回串池中的"ab"
		String str3 = str.intern();
        //false
		System.out.println(str == str2);
        //true
		System.out.println(str == str3);
        //false
		System.out.println(str2 == str3);
	}
}

intern方法 1.6

调用字符串对象的intern方法,会将该字符串对象尝试放入到串池中

无论放入是否成功,都会返回串池中的字符串对象

注意:此时无论调用intern方法成功与否,串池中的字符串对象和堆内存中的字符串对象都不是同一个对象

StringTable 垃圾回收

StringTable在内存紧张时,会发生垃圾回收

StringTable调优

-XX:StringTableSize=桶个数

2.6、直接内存

2.6.1 定义

Direct Memory, java和系统都可以访问到

java本身没有磁盘读写的能力,必须调用操作系统提供的磁盘读写的方法。

文件读写流程:

image

磁盘文件得复制两份,直到Java堆内存中才能被java访问到,因此读写效率较低。

使用DirectBuffer

image

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

2.6.2 释放原理

直接内存的回收不是通过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方法,且后台referenceHandler线程还会对虚引用的对象监测,如果虚引用的实际对象(这里是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);
}

6.3 直接内存的回收机制总结

标签:教程,ab,String,串池,内存,JVM,字符串,线程,自学
来源: https://www.cnblogs.com/linklate/p/14772282.html