JVM包含两个子系统和两个组件,两个子系统为Class loader(类装载)、 Execution engine(执行引擎);两个组件为Runtime data area(运行时数据 区)、Native Interface(本地接口)。 Class loader(类装载):根据给定的全限定名类名(如: java.lang.Object)来装载class文件到Runtime data area中的methodarea。 Execution engine(执行引擎):执行classes中的指令。 Native Interface(本地接口):与native libraries交互,是其它编程语 言交互的接口。 Runtime data area(运行时数据区域):这就是我们常说的JVM的内 存。 作用 :首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader) 再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方 法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作 系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将 字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他 语言的本地库接口(Native Interface)来实现整个程序的功能。
Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存区域划分为若干个 不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有些区域 随着虚拟机进程的启动而存在,有些区域则是依赖线程的启动和结束而建立和销 毁。Java 虚拟机所管理的内存被划分为如下几个区域:
程序计数器(Program Counter Register):当前线程所执行的字节码的行号 指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的 字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个 计数器来完成;
Java 虚拟机栈(Java Virtual Machine Stacks):用于存储局部变量表、操作 数栈、动态链接、方法出口等信息;
本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚 拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;
Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享 的,几乎所有的对象实例都在这里分配内存;
方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变 量、即时编译后的代码等数据。
浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址, 深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加 的指针指向这个新的内存, 使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的 错误。 浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来 的对象也会相应的改变。 深复制:在计算机中开辟一块新的内存地址用于存放复制的对象。
在java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行 执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会 执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没 有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。
GC 是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问 题的地方,忘记或者错误的内存 回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动监测 对象是否超过作用域从而达到自动 回收内存的目的,Java 语言没有提供释放已分配内存的显示操作方法。
强引用:发生 gc 的时候不会被回收。
软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。
弱引用:有用但不是必须的对象,在下一次GC时会被回收。
虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。
垃圾收集器在做垃圾回收的时候,首先需要判定的就是哪些内存是需要被回收 的,哪些对象是「存活」的,是不可以被回收的;哪些对象已经「死掉」了,需 要被回收。 一般有两种方法来判断:
引用计数器法:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用 被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用 的问题;
可达性分析算法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。 当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。
标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清 除垃圾碎片。
复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的 对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不 高,只有原来的一半。
标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清 除掉端边界以外的内存。
分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年 代,新生代基本采用复制算法,老年代采用标记整理算法。
如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体 实现。下图展示了7种作用于不同分代的收集器,其中用于回收新生代的收集器 包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,还有用于回收整个Java堆的G1收集器。不同收集器 之间的连线表示它们可以搭配使用。img Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点 是简单高效; ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程 版本,在多核CPU环境下有着比Serial更好的表现; Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效 利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高 效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不 高的场景; Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年 代版本; Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先, Parallel Scavenge收集器的老年代版本; CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集 器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最 短GC回收停顿时间。 G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是 JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会 产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代 或老年代。
新生代回收器:Serial、ParNew、Parallel Scavenge 老年代回收器:Serial Old、Parallel Old、CMS 整堆回收器。
G1 新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内 存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。
Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也 是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时 候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊 的用法,像是反射,就需要显式的加载所需要的类。 类装载方式,有两种 : 1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用 类装载器加载对应的类到jvm中, 2.显式装载, 通过class.forname()等方法,显式加载需要的类 Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证 程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候 才加载。这当然就是为了节省内存开销。
实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。 主要有一下四种类加载器:
1. 启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被 java程序直接引用。
2. 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。 Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找 并加载 Java 类。
3. 系统类加载器(system class loader):它根据 Java 应用的类路径 (CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来 完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取 它。
4. 用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现。
类装载分为以下 5 个步骤:
加载:根据查找路径找到相应的 class 文件然后导入;
验证:检查加载的 class 文件的正确性;
准备:给类中的静态变量分配内存空间;
解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为 一个标示,而在直接引用直接指向内存中的地址;
初始化:对静态变量和静态代码块执行初始化工作。
双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去加载 这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如 此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无 法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加 载类。当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父 类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。
今天的分享就到这里,感谢点赞收藏转发!