Java 底层知识-JVM相关

6-1 谈谈你对Java的理解 6-2 平台无关性如何实现 6-3 JVM如何加载class文件 6-4 什么是反射 6-5 谈谈ClassLoader 6-6 loadClass和forName的区别 6-7 Java内存模型之线程独占部分 6-8 Java内存模型之线程共享部分 6-9 Java内存模型之 常考题解析

6-1 谈谈你对Java的理解

  • 平台无关性(一处编译多处运行)
  • GC(垃圾回收机制)
  • 语言特性(泛型、反射、lambda表达式)
  • 面向对象(封装、继承、多态)
  • 类库(集合、网络库、并发库、nio)
  • 异常处理

6-2 平台无关性如何实现

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

javac编译,JVM 解析

public class ByteCodeSample {
    public static void main(String[] args) {
        int i = 1,j = 5;
        i++;
        ++j;
        System.out.println(i);
        System.out.println(j);
    }
}
javac 生成 ByteCodeSample.class 字节码文件后javap -c ByteCodeSample对代码进行反汇编
Compiled from "ByteCodeSample.java"
public class com.imocc.javabasic.bytecode.ByteCodeSample {
  public com.imocc.javabasic.bytecode.ByteCodeSample();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_5
       3: istore_2
       4: iinc          1, 1
       7: iinc          2, 1
      10: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      13: iload_1
      14: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      17: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      20: iload_2
      21: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      24: return
}

6-3 JVM如何加载class文件

JVM主要由Class Loader、Runtime Data Area、Execution Engine以及Native Interface这四个部分组成。它主要通过Class Loader将符合其格式要求的class文件加载到内存,并通过Execution Engine去解析class文件里的字节码并提交给操作系统去执行。

Java 虚拟机

  • 类加载子系统(Class Loader):依据特定格式,加载 class 文件到内存
  • 执行引擎(Execution Engine):对命令进行解析
  • 本地接口(Native Interface):融合不同开发语言的原生库为 Java 所用
  • 运行时数据区(Runtime Data Area):JVM 内存空间结构模型

6-4 什么是反射

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

package com.imocc.javabasic.reflect;

/**
 * @author zhangjingyu
 */
public class Robot {
    private String name;
    public void sayHi(String helloSentence){
        System.out.println(helloSentence+" "+name);
    }
    private String throwHello(String tag){
        return "Hello "+tag;
    }
}
package com.imocc.javabasic.reflect;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author zhangjingyu
 * getDeclaredMethod()可以获取到公共的私有的包私有的方法(private、protected、public和default),即所有的都可以,但是不能获取到继承的,实现接口的方法。
 * 私有方法必须设置 .setAccessible(true);
 * getMethod() 可以获取public方法和继承的方法、实现接口的方法。
 */
public class ReflectSample {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class rc = Class.forName("com.imocc.javabasic.reflect.Robot");
        Robot r = (Robot) rc.newInstance();
        System.out.println("Class name is "+rc.getName());
        //私有方法
        Method getHello = rc.getDeclaredMethod("throwHello", String.class);
        getHello.setAccessible(true);
        Object str = getHello.invoke(r,"zhjynet");
        System.out.println("getHello result is "+ str);
        //公有方法
        Method sayHi = rc.getMethod("sayHi", String.class);
        sayHi.invoke(r,"Welcome");
        //私有属性
        Field name = rc.getDeclaredField("name");
        name.setAccessible(true);
        name.set(r,"Alice");
        sayHi.invoke(r,"Welcome");

    }
}
  • 类从编译到执行的过程:
    • 编译器将Robot.java源文件编译为Robot.class字节码文件
    • ClassLoader将字节码转换为JVM中的Class<Robot>对象
    • JVM利用Class<Robot>对象实例化为Robot对象

6-5 谈谈ClassLoader

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

  • ClassLoader的种类:
    • BootStrapClassLoader:C++编写,加载核心库java.*
    • ExtClassloader:java编写,加载扩展库javax.*(System.getProperty("java.ext.dirs")可以看到扩展库路径)
    • AppClassLoader:java编写,加载程序所在目录(System.getProperty("java.class.path")可以看到加载路径,最重要的是javabasic路径)
    • 自定义ClassLoader:java编写,定制化加载(关键函数,findClass、defineClass)
  • 自定义 CLassLoader 的实现
    • 关键函数
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
    
    protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        postDefineClass(c, protectionDomain);
        return c;
    }
    
    • 具体实现
    package com.imocc.javabasic.reflect;
    
    import java.io.*;
    
    /**
    * @author zhangjingyu
    */
    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){
            byte[] b = loadClassData(name);
            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 (IOException e) {
                e.printStackTrace();
            }finally {
                try {
                    out.close();
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return out.toByteArray();
        }
    }
    
    package com.imocc.javabasic.reflect;
    
    /**
    * @author zhangjingyu
    */
    public class ClassLoaderChecker {
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
            MyClassLoader m = new MyClassLoader("/Users/zhangjingyu/Desktop/","MyClassLoader");
            Class c= m.loadClass("Wail");
            System.out.println(c.getClassLoader());
            c.newInstance();
        }
    }
    
  • 谈谈类加载器的双亲委派机制
    • loadClass()
        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 {
                        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;
        }
        ```    
    
  • 为什么要使用双亲委派机制去加载类
    • 避免多份同样字节码的加载
  • 类的加载方式
    • 隐式加载:new
    • 显示加载:loadClass、forName等,获取到class对象之后调用newInstance()方法来生成对象实例(newInstance()不支持传入参数,需要反射getConstructor()然后调用构造器的newInstance()方法传入参数)

6-6 loadClass和forName的区别

  • 类的装载过程(加载和生成实例的过程)
    类的装载过程
  • loadClass和forName的区别
    • Class.forName得到的class是已经完成初始化的
    • Classloder.loadClass得到的class是还没有链接的
    public class Robot {
        static {
            System.out.println("Hello Robot");
        }
    }
    
    public class LoadDifference {
        public static void main(String[] args) throws ClassNotFoundException {
            //不会初始化 所以没有打印(加快加载速度,延迟加载)
            ClassLoader cl = Robot.class.getClassLoader();
            //会初始化 所以打印了(mysql驱动就是用的这个)
            Class r = Class.forName("com.imocc.javabasic.reflect.Robot");
        }
    }
    

6-7 Java内存模型之线程独占部分

  • 内存简介

    内存简介

  • 地址空间划分

    • 内核空间
    • 用户空间
  • JVM 内存模型-JDK8

    JVM 内存模型-JDK8

    • 线程私有:程序计数器、虚拟机栈、本地方法栈
    • 线程共享:MetaSpace、Java 堆
  • 程序计数器(Program Counter Register)

    • 当前线程锁执行的字节码行号指示器(逻辑)
    • 改变计数器的值来选取小一条需要执行的字节码指令
    • 和线程使一对一的关系即“线程私有”
    • 对 Java 方法计数,如果是 Native 方法则计数器值为 Undefined
    • 不会发生内存泄露
  • Java 虚拟机栈(Stack)

    • Java 方法执行的内存模型
    • 包含多个栈帧
  • 局部变量表和操作数栈

    • 局部变量表:包含方法执行过程中的所有变量(包括this引用、所有方法参数、其他局部变量)
    • 操作数栈:入栈、出栈、复制、交换、产生消费变量(在执行字节码指令过程中被用到,这种方式类似于原生cpu寄存器,大部分jvm字节码把时间花费在操作数栈的操作上,因此局部变量的数组和操作数栈的操作指令通过字节码频繁执行)
    package com.imocc.javabasic.bytecode.jvm.model;
    
    /**
    * @author zhangjingyu
    */
    public class ByteCodeSample {
        public static int add(int a,int b){
            int c = 0;
            c =a + b;
            return c;
        }
    }
    

    执行 javac 生成.class 文件后执行 javap -verbose 反编译字节码

    Classfile /Users/zhangjingyu/Documents/Programing/2020/2020FirstQuarter/javabasic/src/com/imocc/javabasic/bytecode/jvm/model/ByteCodeSample.class
    Last modified 2020-2-26; size 309 bytes
    MD5 checksum 16f355d5ed373660bdcc6d53fa6b3c34
    Compiled from "ByteCodeSample.java"
    public class com.imocc.javabasic.bytecode.jvm.model.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/imocc/javabasic/bytecode/jvm/model/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/imocc/javabasic/bytecode/jvm/model/ByteCodeSample
    #14 = Utf8               java/lang/Object
    {
    public com.imocc.javabasic.bytecode.jvm.model.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 6: 0
    
    public static int add(int, int);
        descriptor: (II)I
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
        stack=2, locals=3, args_size=2
            0: iconst_0
            1: istore_2
            2: iload_0
            3: iload_1
            4: iadd
            5: istore_2
            6: iload_2
            7: ireturn
        LineNumberTable:
            line 8: 0
            line 9: 2
            line 10: 6
    }
    SourceFile: "ByteCodeSample.java"
    
    • descriptor:对方法的描述:有2个int参数,函数返回值为int
    • flags:标记public,static
    • stack=2:操作数栈,深度2
    • locals:本地变量是3
    • args_size:参数大小是2个
    • LineNumberTable:代码的行号对应字节码行号

    执行 add(1,2)

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

    • 递归过深,栈帧数超出虚拟栈深度,解决方法就是减少递归次数或者循环替换递归
    • 虚拟机栈过多会引发java.lang.OutOfMemoryError异常
    • 虚拟机栈不需要GC回收,用完就会释放掉
  • 本地方法栈:

    • 与虚拟机栈相似,主要作用于标注了native的方法

6-8 Java内存模型之线程共享部分

  • 元空间(MetaSpace)和永久代(PermGen)的区别?

    • 元空间使用本地内存,而永久代使用的是 JVM 的内存

    • 元空间和永久代都是存放class的相关信息,包括class和Meta、field的信息。

    • 元空间和永久代都是方法区的实现,只是实现不同,所以说方法区只是JVM的一种规范。

  • MetaSpace相比PermGen的优势

    • 字符串常量池存在永久代中,容易出现性能问题和内存溢出
    • 类的方法信息大小难确定,给永久代的大小置顶带来困难(太小会出现永久代溢出,太大容易导致老年代溢出)
    • 永久代会为GC带来不必要的复杂性,回收效率低(永久代中的元数据的可能会随着Full GC发生而进行移动,比较消耗虚拟机性能。HotSpot虚拟机的每种类型的垃圾回收器都需要特殊处理永久代中的元数据。将元数据从永久代剥离出来,不仅实现了对元空间的无缝管理,还可以简化Full GC以及对以后的并发隔离类元数据等方面进行优化)
    • 方便HotSpot与其他JVM如Jrockit的集成(永久代是hotspot特有的,别的VM没有永久代)
  • Java堆(Heap):

    • 对象实例的分配区域(heap是个大头,占用最多的内存空间。xmx设置jvm最大可用内存)Java 进程内存布局
    • GC管理的主要区域(所以也称为GC堆)GC 管理
    • 主流的垃圾回收机制都是采用分代收集算法,所以堆可以分为新生代、老年代,再细点就有eden区、from survivor区、to survivor区。

6-9 Java内存模型之常考题解析

  • JVM三大新能调优参数 -Xms -Xmx -Xss的含义?

    • Xss:规定了每个线程虚拟机栈(堆栈)的大小(一般256k足够,此设置会影响并发线程数大小)
    • -Xms:堆的初始值(就是JVM进程刚启动的时候分配的堆空间大小,后面运行时堆空间超过初始值,就会动态扩容至堆的最大空间-Xmx)
    • -Xmx:堆能达到的最大值(一般Xms Xmx 都设置成一样的,因为xms不够用时动态扩容,会发生内存抖动,影响程序稳定性)
  • 内存分配策略

    • 静态存储:编译时确定每个数据目标再运行时的存储空间需求
    • 栈式存储:数据区需求在编译时未知,运行时模块入口前确定
    • 堆式存储:编译时或运行时模块入口都无法确定存储空间,需要动态分配
  • java内存模型中堆和栈的联系和区别?

    • 联系:引用对象、数组时、栈里定义变量保存堆中目标的首地址Java 堆和栈
    • 管理方式:栈自动释放,堆需要GC
    • 空间大小:栈比堆小
    • 碎片相关:栈产生的碎片远小于堆
    • 分配方式:栈支持静态和动态分配,而堆仅支持动态分配
    • 效率:栈的效率高于堆
  • 举例

    package com.imocc.javabasic.bytecode.jvm.model;
    
    /**
    * @author zhangjingyu
    */
    public class HelloWorld {
        private String name;
        public void sayHello(){
            System.out.println("Hello " + name);
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public static void main(String[] args) {
            int a = 1;
            HelloWorld hw = new HelloWorld();
            hw.setName("test");
            hw.sayHello();
        }
    }
    
    • 元空间、堆、线程独占部分间的联系-内存角度
      元空间、堆、线程独占部分间的联系-内存角度
  • 不同JDK版本的intern()方法的区别?

    https://www.jianshu.com/p/0d1c003d2ff5

    https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html

Comment