JVM内存模型:
- 线程独占:栈,本地方法栈,程序计数器;
栈 | 本地方法栈 | 程序计数器 |
|
|
|
- 线程共享:堆,方法区
堆 | 方法区 |
|
|
堆 | 栈 | |
功能 | 存储Java中的对象 | 存储局部变量和方法调用 |
共享性 | 所有线程共有的 | 线程私有的 |
异常错误 | java.lang.OutOfMemoryError(内存溢出) | java.lang.StackOverFlowError(栈溢出) |
空间大小 | 大 | 小 |
堆内存根据GC(垃圾回收机制)分为:
新生代(Minor) | 老年代(Major) |
是用来存放新生的对象。 一般占据堆的 1/3 空间。 由于频繁创建对象,所以新生代会频繁触发 MinorGC 进行垃圾回收。 | 主要存放应用程序中生命周期长的内存对象。 老年代的对象比较稳定,所以 MajorGC 不会频繁执行。 在进行 MajorGC 前一般都先进行 了一次 MinorGC,使得有新生代的对象晋升入老年代,导致空间不够用时才触发。 当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间。 |
新生代分为:
Eden 区 | ServivorFrom | ServivorTo |
Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行一次垃圾回收。 | 上一次 GC 的幸存者,作为这一次 GC 的被扫描者。 | 保留了一次 MinorGC 过程中的幸存者。 |
Minor GC | Major GC |
复制算法,将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当一块的内存用完了,就将还存活着的对象复制到另一块上面,然后再把已经使用过的内存空间一次清理掉。 | 标记清除算法,首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。 |
永久代和元数据:
- 在 Java8 中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。
- 元空间的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用 本地内存。
- 默认情况下,元空间的大小仅受本地内存限制。
- 类的元数据放入native memory, 字符串池和类的静态变量放入 java 堆中,这样可以加载多少类的元数据就不再由 MaxPermSize 控制, 而由系统的实际可用空间来控制
垃圾收集算法:
- 标记-清除算法:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象;
- 复制算法:将可用内存按容量划分为大小相等的两块,每次只使用其中一块,一块内存使用完了将还存活的对象复制到另一块上,再吧已经使用过的内存空间一次清理掉;
- 标记-压缩算法:首先标记出所有需要回收的对象,让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存;
- 分代收集算法:把Java堆分我新生代和老年代,根据各自特点采用适当的收集算法。
JVM加载过程:
- 加载:通过类的完全限定名,查找此类字节码文件,利用字节码文件创建Class对象;
- 连接:a、验证,确保Class文件符合当前虚拟机的要求,不会危害到虚拟机自身安全;b、准备,为类的静态变量进行内存分配,为static修饰的类变量分配内存,并设置初始值(0或null),不包含final修饰的静态变量,final修饰的静态变量在编译时分配;c、解析,将常量池中的符号引用转换为直接引用;
- 初始化:完成静态块执行以及静态变量的赋值,先初始化父类,再初始化当前类,只有对类主动使用时才会初始化;
- 使用:new除对象程序中使用;
- 卸载:执行垃圾回收。
触发条件:创建类的实例时,访问类的静态方法或静态变量,使用Class.forName反射类的时候,初始化某个子类时;
Java自带的加载器加载的类,在虚拟机的生命周期中不会被卸载,只有用户自定义的加载器加载的类才能被卸载;
加载 | 文件到内存 |
验证 | 文件格式、元数据、字节码、符号引用 |
准备 | 类变量内存 |
解析 | 引用替换、字段解析、接口解析、方法解析 |
初始化 | 静态块、静态变量 |
使用 | 实例化 |
卸载 | GC |
JVM的内存模型:JVM在执行程序时,会把管理的内存划分为若干个区域,每个区域都有自己的用途和创建销毁时间。
Java虚拟机:一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。Java虚拟机知道底层硬件平台的指令长度和其它特性,所有被称为是平台无关的编程语言。
JVM加载class文件的原理机制:
- JVM中类的装载是由类加载器和它的子类实现的,Java中的类加载器是一个重要的Java运行时系统组件,负责在运行时查找和装入类文件中的类;
- 由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件;
- 当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化;
- 类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象;
- 类的加载是由类加载器完成的,类加载器包括:根加载器、扩展加载器、系统加载器和用户自定义类加载器;
- 采用双亲委派模式:加载器加载类时先把请求委托给自己的父类加载器执行,直到顶层的启动类加载器,父类加载器能够完成加载则成功返回,不能则子类加载器才自己尝试加载。
- 可以避免类的重复加载;能避免Java的核心被篡改;
根加载器(BootStrap) | 用本地代码实现,负责加载JVM基础核心类库 |
扩展加载器(Extension) | 从java.ext.dirs系统属性所指定的目录中加载类库,父加载器是Bootstrap |
系统加载器(System) | 又叫应用类加载器,父类是Extension。是应用最广泛的类加载器。从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。 |
Java对象创建过程:
- JVM遇到一条新建对象的指令时首先去检查这个指令的参数是否能在常量池中定义一个类的符号引用,然后加载这个类;
- 为对象分配内存;
- 将除对象头外的对象内存空间初始化为0;
- 对对象头进行必要设置。
Java的对象结构:
- 对象头:第一部分存储对象自身的运行时数据:哈希码、GC分代年龄、锁标识状态、线程持有的锁、偏向线程ID;第二部分是指针类型,指向对象的类元数据架构(对象代表哪个类);如果是数组对象,则对象头中还有一部分用来记录数组长度;
- 实例数据:用来存储对象真正的有效信息(包括父类继承下来的和自己定义的);
- 对齐填充:JVM要求对象起始地址必须是8字节的整数倍。
对象是否存活(是否可以被回收):
- 引用技术:每个对象有一个引用计数属性,新增计数加一,引用释放时计数减一,计数为0时可以回收;
- 可达性分析:从GC Roots开始乡下搜索,搜索所走过的路径成为引用链,当一个对象到GC Roots没有任何引用链相连时,就证明此对象是布可用的。
JVM性能调优:
- 设定堆内存大小;
- 设定新生代大小,新生代不宜太小,否则会有大量对象涌入老年代;
- 设定垃圾回收器。