理解 JVM

1、平台无关性

Compile Once, Run Anywhere如何实现

  • 编译 java

    javac java全路径

  • javap -c 反汇编class文件

在这里插入图片描述
​ Java源码首先被编译成字节码,再由不同平台的JVM进行解析,Java语言在不同的平台上运行时不需要进行重新编译,Java虚拟机在执行字节码的时候,把字节码转换成具体平台上的机器指令。

  • 为什么JVM不直接将源码解析成机器码去执行

    1. 准备工作:每次执行都需要各种检查
    2. 兼容性:也可以将别的语言解析成字节码

2、JVM如何加载.clas文件

​ JVM 是一个内存中的虚拟机,JVM 的存储就是内存,程序中的所有类、常量、变量等都在内存中。

  • JVM 架构
    在这里插入图片描述
  1. Class Loader:依据特定格式,加载class文件到内存
  2. Execution Engine:对命令进行解析
  3. Native Interface:融合不同开发语言的原生库为Java所用,Java native 关键字修饰的 native 方法 是调用本地原生方法,提高程序运行效率。
  4. Runtime Data Area:JVM内存空间结构模型

3、反射

  • 概念

    JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

  • Robot.java

public class Robot {
    static {
    System.out.println("Hello Robot");
	}
    
    private String name;
    public void sayHi(String helloSentence){
        System.out.println(helloSentence + " " + name);
    }
    private String sayHello(String tag){
        return "Hello " + tag;
    }
}
  • 反射小例子
public class ReflectSample {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException, NoSuchFieldException {
        // Class 类的实例表示正在运行的 Java 应用程序中的类和接口。
        // 枚举是一种类,注解是一种接口。
        Class rc = Class.forName("com.moc.reflect.Robot");
        Robot r = (Robot) rc.newInstance();
        System.out.println("Class name is " + rc.getName());

        // getDeclaredMethod 获取类的所有方法,不包括继承和实现接口的方法
        Method getHello = rc.getDeclaredMethod("sayHello", String.class);
        getHello.setAccessible(true);
        Object str = getHello.invoke(r, "Bob");
        System.out.println("getHello result is " + str);

        // getMethod  只能获取类公有的方法,包括继承和实现接口的方法
        Method sayHi = rc.getMethod("sayHi", String.class);
        sayHi.invoke(r, "Welcome");

        // getDeclaredField 获得类的所有属性
        Field name = rc.getDeclaredField("name");
        name.setAccessible(true);
        name.set(r, "Alice");
        sayHi.invoke(r, "Welcome");
    }
}

4、ClassLoader

ClassLoader 在Java中有着非常重要的作用,它主要工作在 Class装载的加载阶段,其主要作用是 **从系统外部获得 Class二进制数据流 **。它是Java的核心组件,所有的 Class都是由 ClassLoader进行加载的,ClassLoader 负责通过将 Class文件里的二进制数据流装载进系统,然后交给Java虚拟机进行连接、初始化等操作。

  • 类从编译到执行的过程
  1. 编译器将 Robot.java源文件编译为 Robot.class字节码文件
  2. ClassLoader 将字节码转换为 JVM中的 Class<Robot> 对象
  3. JVM 利用 Class <Robot>对象 去实例化 Robot对象
  • ClassLoader的种类
  1. BootStrapClassLoader :C++编写,加载核心库java.*
  2. ExtClassLoader:Java编写,加载扩展库javax.*
  3. AppClassLoader:Java编写,加载用户程序所在目录
  4. 自定义ClassLoader:Java编写,定制化加载
// ExtClassLoader 加载的路径
System.out.println(System.getProperty("java.ext.dirs"));
// AppClassLoader 加载的路径
System.out.println(System.getProperty("java.class.path"));
  • 自定义ClassLoader的实现
// 关键函数
// findClass  根据位置去加载 .class字节码
protected Class<?> findClass(String name) throws ClassNotFoundException {
	throw new ClassNotFoundException (name);
}

// defineClass  解析定义 .class字节流,返回 Class对象
protected final Class<?> defineClass (byte[] b, int off, int Len) throws ClassFormatError {
	return defineClass(null, b, off, Len, null);
}
  • Person.class
// 编译成 .class 文件放入 D盘根目录
public class Person {
    static {
        System.out.println("Hello World!");
    }
}
  • 自定义类加载器
public class MyClassLoader extends ClassLoader {
    private String path;  // 解析的字节码的路径
    private String classLoaderName;  // 字节码加载的名称

    public MyClassLoader(String path, String classLoaderName) {
        this.path = path;
        this.classLoaderName = classLoaderName;
    }

    @Override
    public Class findClass(String name) {
        // loadClassData 中可以对二进制流进行改造,(字节码增强技术)
        // 应用:远程获取二进制流来获取类,对类的字节码加密解密、
        //       给类添加一些信息(AOP实现方式之一)
        // JAVAssist: 一个开源的分析、编辑和创建Java字节码的类库
        byte[] b = loadClassData(name);
        // 直接调用 java提供的 defineClass方法即可
        return defineClass(name, b, 0, b.length);
    }

    //加载类文件
    private byte[] loadClassData(String name) {
        name = path + name + ".class";
        InputStream in = null;
        ByteArrayOutputStream out = null;
        try {
            in = new FileInputStream(new File(name));
            out = new ByteArrayOutputStream();
            int i = 0;
            while ((i = in.read()) != -1) {
                out.write(i);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
                in.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return out.toByteArray();
    }
}
  • 测试自定义类加载器
public class ClassLoaderChecker {
    public static void main(String[] args)
            throws ClassNotFoundException,
                   IllegalAccessException,
                   InstantiationException {
        // 用自定义类加载器加载 D盘里的 Person.class 字节码文件
        MyClassLoader m = new MyClassLoader("D:\\", "myClassLoader");
        Class c = m.loadClass("Person");
        c.newInstance();
                       
         // 下小节,类加载器的双亲委派
		System.out.println(c.getClassLoader());
		System.out.println(c.getClassLoader().getParent());
		System.out.println(c.getClassLoader().getParent().getParent());
		System.out.println(c.getClassLoader().getParent().getParent().getParent());
    }
}

在这里插入图片描述

5、类加载器的双亲委派机制

在这里插入图片描述

  • ClassLoader.java 源码
protected Class<?> loadClass(String name, boolean resolve)
	throws ClassNotFoundException
{
	synchronized (getClassLoadingLock(name)) {
		// First, check if the class has already been loaded
		Class<?> c = findLoadedClass(name);
		if (c == null) {
			long t0 = System.nanoTime();
			try {
				if (parent != null) {
					c = parent.loadClass(name, false);
				} else {
					// 为null时,其父 类加载器为 BootStrap 类加载器,代码由c语言实现
					c = findBootstrapClassOrNull(name);
				}
			} catch (ClassNotFoundException e) {
				// ClassNotFoundException thrown if class not found
				// from the non-null parent class loader
			}

			if (c == null) {
				// If still not found, then invoke findClass in order
				// to find the class.
				long t1 = System.nanoTime();
				c = findClass(name);

				// this is the defining class loader; record the stats
				sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
				sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
				sun.misc.PerfCounter.getFindClasses().increment();
			}
		}
		if (resolve) {
			resolveClass(c);
		}
		return c;
	}
}

6、类的加载方式

隐式加载: new

显式加载: loadClass,forName

  • loadClass 和 forName 的区别
    在这里插入图片描述
  1. Class.forName 得到的class 是已经初始化完成的,会执行静态代码块;
  2. Classloder.loadClass 得到的class 是只完成了类的加载,还没有链接、初始化;可以加快加载速度,把类链接、初始化留到真正使用的时候再完成,如 spring IOC 的 lazy load (延迟加载)。
public class LoadDifference {
    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader cl = Robot.class.getClassLoader();
        System.out.println("-------");
        Class r = Class.forName("com.moc.reflect.Robot");
        System.out.println("-------");
        //Class.forName("com.mysql.jdbc.Driver");
    }
}

在这里插入图片描述

7、java 内存模型

内存简介:

在这里插入图片描述
​ 32位处理器:2^32的可寻址范围
​ 64位处理器:2^64的可寻址范围

  • JVM内存模型 - JDK8
    在这里插入图片描述
    线程私有:程序计数器、虚拟机栈、本地方法栈
    线程共享:MetaSpace.Java堆
1. 程序计数器(Program Counter Register)
  1. 当前线程所执行的字节码行号指示器(逻辑)
  2. 改变计数器的值来选取下一条需要执行的字节码指令
  3. 和线程是一对一的关系即“线程私有”
  4. 对Java方法计数,如果是Native方法则计数器值为Undefined
  5. 不会发生内存泄露
2. Java虚拟机栈(Stack)
  1. Java方法执行的内存模型
  2. 虚拟机栈包含了单个线程每个方法执行的栈帧,调用每个方法都会创建一个栈帧
    在这里插入图片描述
  • 局部变量表 和 操作数栈
  1. 局部变量表:包含方法执行过程中的所有变量
  2. 操作数栈:入栈、出栈、复制、交换、产生消费变量
package com.moc.jvm;

public class ByteCodeSample {
    public static int add(int a, int b) {
        int c = 0;
        c = a + b;
        return c;
    }
}
  • 反编译 javap -c ByteCodeSample.class
Compiled from "ByteCodeSample.java"
public class com.moc.jvm.ByteCodeSample {
  public com.moc.jvm.ByteCodeSample();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static int add(int, int);
    Code:
       0: iconst_0
       1: istore_2
       2: iload_0
       3: iload_1
       4: iadd
       5: istore_2
       6: iload_2
       7: ireturn
}
  • 更易读的方式反编译 javap -verbose ByteCodeSample.class
// 文件信息
Classfile /D:/java/advanced-stage/jvm/src/com/moc/jvm/ByteCodeSample.class
  Last modified 2019-4-28; size 282 bytes
  MD5 checksum 812fa09debbad037b9dac0fba2906a0d
  Compiled from "ByteCodeSample.java"
// 描述类信息  公有、继承Object
public class com.moc.jvm.ByteCodeSample
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
// 常量池
Constant pool:
   #1 = Methodref          #3.#12         // java/lang/Object."<init>":()V
   #2 = Class              #13            // com/moc/jvm/ByteCodeSample
   #3 = Class              #14            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               add
   #9 = Utf8               (II)I
  #10 = Utf8               SourceFile
  #11 = Utf8               ByteCodeSample.java
  #12 = NameAndType        #4:#5          // "<init>":()V
  #13 = Utf8               com/moc/jvm/ByteCodeSample
  #14 = Utf8               java/lang/Object
// 方法区
{
  //  初始化方法
  public com.moc.jvm.ByteCodeSample();
    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 3: 0

  // 自定义 add方法
  public static int add(int, int);
    descriptor: (II)I
    flags: ACC_PUBLIC, ACC_STATIC  // 方法为 public、static
    Code:
      // 操作数栈深度2  本地变量容量3  参数个数2
      stack=2, locals=3, args_size=2
      	// 方法指令 
         0: iconst_0   // 常量 int 0 压入操作数栈
         1: istore_2   // 将操作数栈栈顶元素0弹出,存入局部变量表第2的位置
         2: iload_0    // 将局部变量表第0位置的元素1,压入操作数栈顶
         3: iload_1    // 将局部变量表第1位置的元素2,压入操作数栈顶
         4: iadd       // 从操作数栈弹出两个元素2和1,进行加法运算,将结果3压入操作数栈栈顶
         5: istore_2   // 将操作数栈的栈顶元素3弹出到局部变量表第2的位置
         6: iload_2    // 将局部变量表第2位置的元素3,压入操作数栈顶
         7: ireturn    // 将操作数的栈顶元素返回,销毁栈帧
      // 行号对应表
      LineNumberTable:
        line 5: 0    // 代码第5行对应字节码指令第0行
        line 6: 2
        line 7: 6
}
SourceFile: "ByteCodeSample.java"
  • 内存执行方法过程分析
    在这里插入图片描述

  • 内存过程
    在这里插入图片描述

  • 递归为什么会引发 java.lang.StackOverflowError 异常

    》递归过深,栈帧数超出虚拟栈深度

  • 虚拟机栈过多会引发 java.lang.OutOfMemoryError 异常

public void stackLeakByThread(){
    while(true) {
        new Thread() {
            public void run() {
                while(true) {}
            }
        }
    }
}
// Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
3.本地方法栈
  • 与虚拟机栈相似,主要作用于标注了native方法
4. 元空间(MetaSpace)与永久代(PermGen)

​ 元空间和永久代都是方法区的实现,方法区是 JVM 的一种规范,方法区主要用于存储类的信息、常量池、方法数据、方法代码等。
​ Java 7 之后,原先位于方法区里的字符串常量池已被移动到 java 堆当中,并且jdk 8 之后使用 元空间替代了永久代, 永久废弃永久代。

  • 元空间与永久代的区别
  1. 元空间使用本地内存,而永久代使用的是jvm的内存,从而不存在 java.lang.OutOfMemoryError : PermGen space 异常
  2. 字符串常量池存在永久代中,容易出现性能问题和内存溢出
  3. 类和方法的信息大小难易确定,给永久代的大小指定带来困难
  4. 永久代会为GC带来不必要的复杂性
  5. 方便HotSpot(Sun JDK和OpenJDK中所带的虚拟机)与其他JVM(如Jrockit)的集成
5. Java 堆( Heap)

​ 对象实例的分配区域,JVM 内存管理的最大一块,被所有线程共享,JVM启动时创建,唯一目的是存放对象实例。
在这里插入图片描述
​ Java 堆可以处于物理上不连续的内存空间中,只要逻辑地址连续即可,主流的都设置为可以扩展其大小。
​ Java 堆是 GC管理的主要区域。分代回收机制下,java堆的结构如下:
在这里插入图片描述

6、JVM 三大性能调优参数-Xms -Xmx -Xss

java -Xms128m -Xmx128m -Xss256k -jar xxxx.jar

-Xss:规定了每个线程虚拟机栈(堆栈)的大小, 一般256k,此配置将会影响程序进程并发线程数的大小。

-Xms:堆的初始值,即程序进程刚创建时 Java Heap的大小,一旦对象容量超过初始值将进行扩容。

-Xmx:堆能达到的最大值,即 Java Heap能够扩容到的最大值。

​ 通常将 Xms 与 Xmx 设置为一样的,因为当Java Heap容量不够用时,进行扩容时会发生内存抖动,影响程序运行的稳定性。

8、Java内存模型中堆和栈的区别

  • 内存分配策略
  1. 静态存储:编译时确定每个数据目标在运行时的存储空间需求;要求程序中不允许可变数据结构的存在,也不允许有嵌套或递归的结构存在,否则无法计算准确的存储空间。
  2. 栈式存储:数据区需求在编译时未知,运行时模块入口前确定;动态存储分配,由类似于堆栈的运行栈实现,进入一个程序模块前,必须知道其模块的数据区大小,按照先前后出原则分配。
  3. 堆式存储:编译时或运行时模块入口都无法确定,动态分配;如可变长度串、对象实例,由大片的可利用块和空闲块组成,堆中的内存可以按照任意顺序创建和释放。
  • 堆和栈的联系

    引用对象、数组时,栈里定义变量(引用变量)保存堆中目标的首地址。

    就和 C 中的 指针变量 指向 malloc 出来的空间一样。

    在这里插入图片描述

    • 堆和栈的区别
    1. 管理方式:栈自动释放,堆需要GC
    2. 空间大小:栈比堆小
    3. 碎片相关:栈产生的碎片远小于堆
    4. 分配方式:栈支持静态分配(编译器分配的)和动态分配,而堆仅支持动态分配。
    5. 效率:栈的效率比高。栈效率高、堆灵活。

9、元空间、堆、线程独占部分间的联系-内存角度

在这里插入图片描述在这里插入图片描述

10、JDK6 与 JDK6+ 中 String 的 intern() 方法的区别

String s = new String("abc");
s.intern(); // 把字符串对象s加入常量池中,不改变s的指向
  • JDK 6

    当调用intern方法时,如果字符串常量池先前己创建出该字符串对象,则返回池中的该字符串的引用。否则,将此字符串对象添加到字符串常量池中,并且返回该字符串对象的引用。

  • JDK6+

    当调用intern方法时,如果字符串常量池先前己创建出该字符串对象,则返回池中的该字符串的引用。否则,如果该字符串对象已经存在于Java堆中,则将堆中对此对象的引用添加到字符串常量池中,并且返回该引用;如果堆中不存在,则在池中创建该字符串并返回其引用。

public class InternDifference {
    public static void main(String[] args) {
        // "a" 在常量池中创建字符串常量,new 同时也堆中创建字符串对象并返回给s
        String s = new String("a");
        // jDK6和6+,将堆中的String Obj放入常量池,但在常量池中已存在
        s.intern();
        // 获取 常量池中的字符串对象 "a"
        String s2 = "a";
        System.out.println(s == s2);  // 比较的是地址

        // 在堆中创建aa 字符串对象并返回给s3, 没有"aa",所以不会再常量池中创建
        String s3 = new String("a") + new String("a");
        // JDK6 将堆中 aa字符串对象的副本放入常量池,和堆中字符串的地址不一样
        // JDk6+ 将 "aa"字符串对象的引用放入常量池
        s3.intern();
        
        // 获取 常量池中的字符串对象 "aa", JDK6拿到的是副本,JDK6+拿到的是引用
        String s4 = "aa";
        System.out.println(s3 == s4);
    }
}
// JDK6 的执行结果   false   false
// JDK6+ 的执行结果  false   true

JDK6在这里插入图片描述
JDK6+
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值