JVM架构图

Java运行时数据区域

Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。JDK. 1.8 和之前的版本略有不同

  • 线程共享

    • 堆内存

      堆是进程中最大的一块内存,主要用于存放新创建的对象 (几乎所有对象都在这里分配内存)
      JDK7版本及以前,堆内存通常被分为下面三部分

      • 新生代内存(Young Generation)
      • 老生代(Old Generation)
      • 永生代(Permanent Generation)

      JDK 8 版本之后方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),
      取而代之是元空间,元空间使用的是直接内存。

    • 方法区

      HotSpot虚拟机的永久代

      方法区主要用于存放已被加载的类信息(构造方法,接口定义)、常量、静态变量、
      即是编译器编译后的代码等数据。简而言之,static,final,Class类,运行时常量池都存在方法区中。
      方法区还有一块区域叫:运行时常量池
      运行时常量池

      • JDK1.7 之前运行时常量池逻辑包含字符串常量池存放在方法区, 此时 hotspot 虚拟机对方法区
        的实现为永久代
      • JDK1.7 字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池
        被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是 hotspot 中的永久代 。
        字符串常量池和静态变量在1.7迁出了方法区
      • JDK1.8 hotspot 移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆,
        运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)
    • 直接内存(非运行时数据区的一部分)

      直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,
      但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现。

      本机直接内存的分配不会受到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及
      处理器寻址空间的限制。

      个人理解:JVM进程向操作系统申请的一块内存,一部分作为JVM运行时的内存使用,另一部分就是
      未被使用的内存,这就是直接内存。

  • 线程私有

    • 虚拟机栈

      每个线程都有各自的虚拟机栈,虚拟机栈被分为一个个大小相等的栈帧.

      栈帧组成部分如下:

      • 局部变量表
        局部变量表最重要,局部变量表主要存放了编译期可知的各种基本数据类型
        (boolean、byte、char、short、int、float、long、double)、
        对象引用(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,
        也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
      • 操作数栈
      • 动态链接
      • 方法返回值信息

      栈帧具体解释:Java 栈可用类比数据结构中栈,Java 栈中保存的主要内容是栈帧,
      每一次函数调用都会有一个对应的栈帧被压入 Java 栈,每一个函数调用结束后,
      都会有一个栈帧被弹出。

      对象的两种访问定位方式

      • 直接指针,即栈帧中的一个指针直接指向堆中对象
      • 句柄访问,即栈帧中的一个指针指向堆中句柄池中的一个句柄,该句柄再指向堆中的对象
    • 程序计数器(PC寄存器)

      程序计数器是记录下一条指令的地址,程序计数器私有主要是为了线程切换后能恢复到正确的执行位置。
      程序计数器的作用

      • 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,
        如:顺序执行、选择、循环、异常处理。

      • 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。

        需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,
        只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。

    • 本地方法栈

      本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、
      操作数栈、动态链接、出口信息。方法执行完毕后相应的栈帧也会出栈并释放内存空间

      不同的虚拟机,栈空间不一样,hotspot虚拟机中(默认使用的虚拟机),虚拟机栈和本地方法栈合而为一,
      统一都叫虚拟机栈。

每一个线程都有各自的虚拟机栈,程序计数器,本地方法栈,多个线程共享堆内存和方法区

了解一下类加载器

  • 类加载的过程
  • 类加载器的种类
    • 虚拟机自带的加载器
    • 启动类(根)加载器(BootStrapClassLoader),rt.jar中
    • 扩展类加载器,jdk/jre/lib/ext目录
    • 应用程序(系统类,AppClassLoader)加载器
  • 双亲委派机制

    保证安全的机制,向上委派,向下加载
    类字节码的加载顺序:将字节码文件交给最顶层的加载器优先加载
    具体过程

    • 类加载器收到类加载的请求
    • 将这个请求向上委托给父加载器去完成,一直向上委托,直到启动类加载器
    • 启动类加载器检查是否能加载这个类,能加载就结束,使用当前的加载器,否则,抛出异常
      通知子加载器进行加载
    • 重复步骤3
    public class Car {
        public static void main(String[] args) {
          Car car = new Car();
            Class<? extends Car> aClass = car.getClass();
            ClassLoader classLoader = aClass.getClassLoader();
            System.out.println(classLoader);
            System.out.println(classLoader.getParent());
            System.out.println(classLoader.getParent().getParent());
        }
    }
    //输出结果
    jdk.internal.loader.ClassLoaders$AppClassLoader@2f0e140b
    jdk.internal.loader.ClassLoaders$PlatformClassLoader@16b98e56
    null
    //扩展类加载器往上就是null,获取不到加载器,因为上面根加载器是用c++语言写的
    //注意,jdk1.9之前,扩展类加载器是EXTClassLoader,1.9版本之后改成了PlatformClassLoader

了解一下Native关键字

  • 应用场景
    在Thread类中start方法调用过被native修饰的start0方法。
  • 底层原理
    • 被native修饰的方法会进入线程的本地方法栈,这个方法会去调用底层C语言的库。
    • 首先这个方法会去调用JNI,java native interface,本地方法接口。
      JNI就会去调用本地方法库,例如C语言库,Python库,可以达到扩展java类的使用。
      融合不同的编程语言为java所用。