JVM内存分配及其结构简述

602 阅读9分钟

1. JVM运行时数据区域


1.1 程序计数器

是一块很小的内存,相当于是当前线程所执行的字节码的行号指示器,它指向哪行的字节地址码就执行那行的字节码。JVM中的字节码解释器则是通过不断修改计数器然后指定执行那行字节码(比如分支,循环,跳转等)。如果当前执行的是java方法,那么当前程序计数器记录的是正在执行的虚拟字节码指令的地址;如果此时执行的是Native方法,则这个计数器为空(Undefined)。

线程私有:即每个线程都有一个程序计数器,
唯一个没有`OutOfMemoryError`错误的内存区域。

1.2 Java虚拟机栈

其实我们常说的栈,大部分时候指的是Java虚拟机栈;虚拟机栈描述的是Java方法执行的内存模型,换句话说就是在执行java方法的时候,这个方法涉及的内存分配(包含方法的描述,方法里的局部变量等)。每一个方法被执行是都会创建一个栈帧,换句话说每一个方法都是一个栈帧,被放在虚拟机栈里面;每执行一个方法就会创建一个栈帧,压入栈内,方法执行完成后就弹出栈外。方法的执行,在内存中就是不断被压栈和弹栈的过程。 栈帧包含的信息:

  • 局部变量表(八大原始类型,对象应用,returnAddress

  • 操作数栈

  • 动态链接

  • 方法出口信息

    线程私有:该内存的作用域在跟随线程的生命周期。 可能产生:OutOfMemoryStackOverflow(也就是栈的Size超过JVM规定的)的异常

1.3 Native栈

本地方法栈,与虚拟机栈十分相似,只是虚拟机使用到Native方法的服务。

线程私有:该内存的作用域在跟随线程的生命周期。
可能产生:`OutOfMemory``StackOverflow`(也就是栈的`Size`超过`JVM`规定的)的异常

1.4 堆(Heap)

在开发中我们经常说的应用内存,内存优化,这里的内存大多时候都是说的堆内存;所以堆内存是开发人员最需要关注的,也是GC系统主要管理的一块区域。此内存唯一的目的是:存放对象的实例,几乎所有的对象实例都在这里分配内存。而类中的全局变量也是放在堆内存中的。从内存回收的角度看,GC回收采用分代收集算法,所以此内存还能分为:年轻代和老年代。年轻代继续划分为Eden空间、From SurvivorTo Survivor空间;这样细致的划分都是为了优化GC回收,提升其效率,所以JVM的优化中对这几个空间的分配参数设置也是优化的一部分。

线程共享
可能产生`OutOfMemoryError`异常

1.5 方法区

方法区域用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。在方法区中可以选择不实现GC回收,但是此区域也有可能会产生内存溢出,所以此内存区域也可能存在GC回收,只是比较少,该GC回收的目标主要是针对常量池的回收和对类型的卸载。

线程共享
可能产生`OutOfMemoryError`异常

1.6 运行时常量池

运行时常量池是属于方法区的一部分,Class文件中除了有类的版本、方法。字段接口等描述信息之外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

线程共享
可能产生`OutOfMemoryError`异常

1.7 直接内存

直接内存并不属于虚拟机运行时数据区的一部分,也不是JVM规范中定义的内存区域,但是这块区域也会被频繁使用,并且可能导致OOM。 在JDK1.4中新加入了 New Input/Output (NIO类),引入了一种基于通道与缓冲区的I/O方式,他可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场合中显著提高性能,避免了Java堆和Native堆中来回复制数据。

可能产生`OutOfMemoryError`异常

2. 对象的创建过程


当虚拟机遇到new关键字时,首先会去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化。如果没有,那首先将进行加载到内存的过程。加载完成之后,便会对个类进行解析,解析的过程会先进行验证,准备最后解析;完成解析之后就进行初始化;初始化完成就可以开始使用,使用结束之后就是卸载出内存。

2.1 类的生命周期

类是有生命周期的,类在JVM中的生命周期由加载开始,卸载结束。

2.2 加载

通过类名获取类的二进制流,并在内存中生成class对象。

2.3 连接

  • 验证:验证二进制流的正确性和安全性 包括文件格式验证,元数据验证,字节码验证以及符号引用验证四个步骤。
  • 准备:给类变量(static)分配空间并完成初始化 注意:如果不是final变量,值均为零值。
  • 解析:将符号引用解析为直接引用,包括类或接口的解析,字段解析,类方法解析,接口方法解析。

2.4 初始化

执行类的<cinit>()方法,这个方法由所有对静态变量的赋值操作和所有静态代码块组成,虚拟机会保证父类的<cinit>()方法在子类的方法开始之前结束,并且提供线程安全的保证(类似于double check,多个线程同时初始化时只有一个线程进入方法,其他线程阻塞,执行完成后其他线程不会再进入方法)

3. JVM的原理


3.1 类加载器子系统(原理,机制,自定义classLoader

Classloader 类加载器,用来加载 .class字节码到 Java 虚拟机中。与普通程序不同的是:Java程序(class文件)并不是本地的可执行程序。当运行Java程序时,首先运行JVM(Java虚拟机),然后再把Java class加载到JVM里头运行,负责加载Java class的这部分就叫做Class Loader。 一个jvm中默认的classloader有Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader,分别各司其职:

  • Bootstrap ClassLoader 负责加载java基础类,主要是 %JRE_HOME/lib/ 目录下的rt.jar、- -- resources.jar、charsets.jar和class
  • Extension ClassLoader 负责加载java扩展类,主要是 %JRE_HOME/lib/ext 目录下的jarclass
  • App ClassLoader 负责加载当前java应用的classpath中的所有类。

3.2 为什么要自定义ClassLoader?

系统的ClassLoader都是加载特定的路径的标准class字节码文件,如果你想加载其他路径下的class文件,比如从网络上获取的动态文件,并且这些文件是经过加密的,系统的自然无法使用,那么就需要自定义XXClassLoader 继承 ClassLoader来实现findClass()方法或者loadClass()方法。

3.3 双亲委派加载机制?

当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。

3.4 双亲委派机制的作用

1、防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。 2、保证核心.class不能被篡改。通过委托方式,不会去篡改核心.class,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。

3.5 执行引擎子系统(分配内存空间)

执行引擎是java虚拟机最核心的组成部件之一。虚拟机的执行引擎由自己实现,所以可以自行定制指令集与执行引擎的结构体系,并且能够执行那些不被硬件直接支持的指令集格式。所有的Java虚拟机的执行引擎都是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果。

4. GC回收系统(GC机制,GC算法)


首先来看一下内存结构:

4.1 GC的主要任务

  • 1.分配内存;
  • 2.确保被引用对象的内存不被错误的回收;
  • 3.回收不再被引用的对象的内存空间;

4.2 哪些内存需要回收?

  • 1.引用计数算法
  • 2.可达性分析算法(GCRoots算法)

注:在Java中可作为GCRoots的对象:

1).虚拟机栈(栈帧中的本地变量表)中引用的对象;

2).方法区中类静态属性引用的对象;

3).方法区中常量引用的对象;

4).本地方法栈中JNI引用的对象

4.3 什么时候回收?

即使是被判断不可达的对象,也要再进行筛选,当对象没有覆盖finalize()方法,或者finalize方法已经被虚拟机调用过,则没有必要执行;如果有必要执行——放置在F-Queue的队列中——Finalizer线程执行。 注意:对象可以在被GC时可以自我拯救(this),机会只有一次,因为任何一个对象的finalize()方法都只会被系统自动调用一次。并不建议使用,应该避免。

4.4 如何回收?(垃圾收集算法)

  • 1.标记—清除算法
  • 2.复制算法(年轻代)
  • 3.标记—整理算法(老年代)

4.5 整理一下新生代和老年代的收集器。

新生代收集器:

  • Serial (-XX:+UseSerialGC)
  • ParNew(-XX:+UseParNewGC)
  • ParallelScavenge(-XX:+UseParallelGC)
  • G1 收集器

老年代收集器:

  • SerialOld(-XX:+UseSerialOldGC)
  • ParallelOld(-XX:+UseParallelOldGC)
  • CMS(-XX:+UseConcMarkSweepGC)
  • G1 收集器

4.6 什么是Minor GCFUll GC

  • Minor GC:新生代GC,指发生在新生代的垃圾收集动作,所有的Minor GC都会触发全世界的暂停(stop-the-world),停止应用程序的线程,不过这个过程非常短暂。
  • Major GC/Full GC:老年代GC,指发生在老年代的GC。耗时会很长,而JVM的优化其中就需要减少FUll GC的次数。