内存模型

线程独占:

虚拟机栈:先进后出的 又称方法栈,线程执行方法都是创建一个栈帧,用来存储局部变量表(存放局部变量的),操作数栈(后进先出的操作数栈。负责写入数据和提取数据),动态链接(执行常量池中的方法引用),方法出口等信息,JVM对栈的操作有2种,出栈和入栈。方法调用就是入栈。方法返回就是出栈

本地方法栈:区别是虚拟机栈为执行Java方法服务,而本地方法栈则为Native方法服务 什么是native方法呢 就是非Java方法。与Java环境外交互。因为JVM一些底层是C写的。比如Thread类中的setPrioruty方法

HotspotJVM中 将本地栈和虚拟机栈合二为一的 栈是运行时单位 堆是存储的单位

程序计数器:一块较小的内存空间,是当前线程所执行的字节码的行号指令器,每条线程都要有一个独立的程序计数器,这类内存也被称为线程私有的内存。用来记录程序执行到哪一个地方,下次可以在这个地方继续执行

线程共享:

堆:java虚拟机内存最大的一块,被所有线程共享,几乎所有线程实例都在这里分配内存。

1.7包含新生代、老年代、永久代

1.8只有新生代和老年代(永久代被删除,新增元空间并且直接存放在内存上)

元空间(方法区):存储已被虚拟机加载的类信息,常量,静态变量

JVM的组成部分

JVM包含两个子系统和两个组件。

类加载子系统: 根据类的全限定名来装载class文件

执行引擎子系统: 执行class中的指令

本地接口: 与编程语言交互的接口(c函数)

运行时数据区: JVM的内存模型

方法区、永久代、元空间的关系

方法区实际上是一种规范,我们可以理解成一个接口。不同的虚拟机厂商针对这个规范做了不同的实现。以HotSpot虚拟机举例。永久代就是最初的一个实现。慢慢的,这个接口一直变更。最后放弃实现类。使用元空间进行替代。

深入理解Java虚拟机中。是这么说的。方法区和堆一样,是各个线程共享的内存区域。用于存储被虚拟机加载的类信息,常量,静态变量和一些即时编译后的代码数据

新生代和老年代默认比值是1:2

到了Java8,永久代被移除,元空间接替。

元空间不与堆存在一起,而是直接存在本地内存上。所以理论上,机器的内存有多大,元空间就有多大

为什么使用元空间替代永久代

永久代的版本下,字符串常量池存在永久代中。大量使用字符串的情况下,很容易发生OOM异常,此外,JVM加载的class总数,方法总大小有时也不稳定。所以永久代的大小很难确定。使用元空间可以解决这个问题。

对象内存布局

对象内存布局分为三个部分,对象头,实例部分,对齐填充

对象头(Header)包含三部分

  1. Mark Word 存放对象自身的运行时数据,如hashcode,gc分代年龄,锁状态标识,线程持有的锁,偏向线程ID

  2. Class Pointer 对象指向类型元数据的指针 虚拟机通过这个指针来确定这个对象是哪个类的实例。

  3. 如果这个对象是数组,还有数组长度的信息。因为虚拟机可以通过Java对象的元数据确定Java对象的大小。但是从数组的元数据无法确定数组的小。

实例部分(Instance Data): 对象真正存储的有效信息,也是程序代码定义的类型字段内容。

对齐填充(Padding): 没什么特别的含义,可以理解为占位符。因为VM的内存管理对象起始地址必须是8字节的整数倍,也就是对象的大小必须是8字节的整数倍,对象头部正好是8字节的整数倍,所以如果我们对象实例数据没有对齐时,就要通过对齐填充来补齐。

为什么CG分代年龄最大是15?

因为对象头header的mark word里面存的信息有对象年龄。使用的是4bit 4bit所能表达的最大值就是15

判断对象是否存活的2种算法

引用计数算法: 给对象种添加一个引用计数器,每当一个地方引用它时,计数器+1 当引用失效时,计数器-1 引用数量为0时,说明对象没有被任何引用,可以被认为时垃圾对象,效率高,但无法解决循环引用的问题 Python采用此算法

可达性算法(根搜索算法): 为了解决引用计数法的循环引用问题,可达性分析算法的基本思路就是一系列名为GC Roots的对象作为起点,从这些节点向下搜索,搜索走过的路径成为引用链 当一个对象到GC Rootes没有任何引用链相连时,证明此对象不可用(不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程,两次标记后仍然是可回收对象,将要面临回收)

可做为GCRoots的对象: 虚拟机栈中引用对象 方法区常量引用对象 方法区静态对象 本地方法引用对象

CMS垃圾回收器和G1垃圾回收器

CMS垃圾回收器: 老年代的回收器,以最短回收停顿时间为目标的收集器。标记清除算法运作过程: 初始标记,并发标记,重新标记,并发清除。完成后会产生大量空间碎片。

G1垃圾回收器: JDK7诞生,JDK8走向成熟,JDK9成为默认的垃圾回收器。主要作用于老年代和新生代,基于标记整理算法实现。回收的范围是整个Java堆,分为初始标记,并发标记,最终标记,筛选标记。不会产生空间碎片,可以精确控制停顿。

双亲委派模型

双亲委派模型是JVM类加载的一种。其他的加载方式还有

全盘负责:当一个类加载器去负责加载某个类。那么这个类所引用的其他类也要有这个类去加载

父类委托:先让父类加载器加载这个类。只有父类加载器无法加载此类自己才加载。

缓存机制:缓存机制会保证所有加载过的class都会被缓存。当程序加载某个类的时候,会先去缓存区寻找,如果不存在,才会去加载对应的类。会造成修改Class后,必须重启

双亲委派加载过程:如果一个类加载器收到了类加载的请求。首先不会自己去加载这个类。而是把这个请求委派给父类加载器去完成。不断地向上抛,直到最顶层的启动类加载器。当父类无法完成加载才会让子类去加载。

加载过程 : 自定义类加载器 -》 应用程序加载器 -》 扩展类加载器 -》 启动类加载器

应用程序加载器: 负责加载用户类路径上的类

扩展类加载器: 加载lib里的类 比如java.*开头的

启动类加载器:加载javahome/lib库中的类

为什么要使用双亲委派模型?

如果不去最顶层这样一层层加载。假如用户自己定义一个Object类。或者是String又或者是Integer。编译的时候,出现了多份同样的字节码,直接破坏了一致性,程序的安全收到极大影响。

打破双亲委派的方法

继承classLoader类,重写loadClass和findClass方法

待更…