Jvm包含两个子系统和两个组件。
Class loader(类加载器):根据给定的全限定名类名(java.lang.object)来装载class文件到Runtime data area(运行时数据区)的method(方法区)。
Execution engine(执行引擎):执行classes中的指令。
Native Interface(本地接口):与native libraries交互,是其他编程语言交互的接口。
Runtime data area(运行时数据区域):JVM内存
作用:
类的加载:将class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆上创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。
程序计数器:当前线程所执行的字节码的行号指示器。通过改变这个计数器的值,选取下一条需要执行的字节码指令。
Java虚拟机栈:存储局部变量表、操作数栈、动态连接、方法出口等信息。(服务JAVA方法)
本地方法栈:为虚拟机Native方法服务的,这些方法底层是C语言编写,直接与操作系统对接的方法。
堆:内存中最大的一块,被所有线程共享,几乎所有对象实例都在这里分配内存。包括静态对象。
方法区:存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。
物理地址:
堆的物理地址分配对象是不连续的。性能较慢。在GC时考虑到不连续的分配,因此有各种算法(标记清除、复制、标记压缩、分代等)
栈使用的是数据结构中的栈,先进后出,物理地址分配时连续的,性能快。
内存分别:
堆因为分配不是连续的,分配的内存在运行期才确认,大小不固定。
栈是连续的,分配的内存大小在编译器就确认,大小是固定的。
存放的内容:
堆存放对象的实例和数组,更关注数据的存储。
栈存放局部变量,操作数栈,返回结果。更关注程序方法的执行。
可见度:
堆对于整个应用程序共享、可见。
栈只对于线程可见,线程私有。生命周期和线程相同。
不再被使用的对象或变量一直被占据在内存中。理论上Java有GC垃圾回收机制,不再被使用的对象会被GC自动回收。但还是存在内存泄漏问题。
原因:长生命周期的对象持有短生命周期对象的引用就会导致内存泄漏。
Java中,由虚拟机自行执行对象的内存释放。有一个垃圾回收线程,低优先级,在虚拟机空闲或当前堆内促不足时,触发执行,扫描那些没有被任何引用的对象,并将它们添加到需要回收的集合中,进行回收。
Gabage Collection 垃圾收集,忘记或错误的内存回收会导致程序或系统的不稳定甚至崩溃。
Java提供的GC功能可以自动检测对象是否超过作用域,从而达到自动回收内存的目的。
垃圾回收的优点
垃圾回收基本原理
对于GC来说,当对象被创建后,GC就开始监控这个对象的地址、大小、及使用情况。
通常,GC采用向图的方式记录和管理堆中的对象。通过确定哪些对象是“可达的”。当GC确定一些对象为“不可达”时,GC就有责任回收这些内存空间。
可以通过System.gc(),通知GC运行,但并不保证GC一定会运行。
怎么判断对象是否可以被回收?
标记-清除法:标记无用的对象,进行清除。(其他算法几乎都是在其上进行改进)
优点:实现简单,不需要对象进行移动。
缺点:效率低,产生大量不连续的内存碎片,提高了垃圾回收的频率。
复制算法:将内存划分为两个相等的区域,每次只使用其中一个。每次遍历单个区域,将存活的对象复制到另一个区域,再清除当前区域。
优点:按顺序分配内存,运行效率高,不用考虑内存碎片
缺点:可用的内存大小只要原来的一半,对存活率高的对象频繁复制
标记-整理算法:在新生代可以使用复制算法,老年代不适合(存活频率高)。标记整理算法在标记回收对象后,将所有存活的对象压缩到内存的一段,紧凑排列,再对边界外的内存进行回收。
优点:解决了标记清理算法存在内存碎片的问题
缺点:仍需要进行局部的对象移动,一定程度上降低了效率
分代收集算法:根据对象存活周期,将内存划分为几块:年轻代、老年代、永久代。(jdk1.8后,删除了永久代,增加了元数据区)
Serial收集器(复制算法):新生代单线程收集器,标记和清理都是单线程,优点是简单高效。收集器回收时会暂停业务线程。
ParNew收集器(复制算法):新生代并行收集器,Serial收集器的多线程版本,多核CPU环境下比Serial性能更好。GC线程和业务线程并行。
Parallel Scavenge(复制算法):新生代并行收集器,追求高吞吐量,高效利用CPU。尽快完成程序的运算任务。jdk1.8默认收集器。GC线程和业务线程并行。
Serial Old收集器 (标记-整理算法):老年代单线程收集器,Serial收集器的老年代版本。收集器回收时会暂停业务线程。
Parallel Old收集器(标记-整理算法):老年代并行收集器,吞吐量优先,Parallel Scavenge的老年代版本。jdk1.8默认收集器。GC线程和业务线程并行。
CMS收集器(标记-清除算法):老年代并行收集器,以获取最短回收停顿时间为目标的收集器,高并发、低停顿,追求最短GC回收停顿时间。牺牲了吞度量来获得最短的停顿时间,适用于服务器响应速度高要求的应用上。因为是基于标记-清除算法,会出现大量内存碎片,此时CMS会临时采用Serial Old回收器进行垃圾清除,同时性能会下降。GC线程和业务线程并行。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。
G1收集器(标记-整理算法):Java堆并行收集器,由jdk1.7提供,基于标记整理-算法,回收范围为整个堆,而不再分新生代和老年代。区域上使用了分区算法。一边清理一部分区域,一边占用一部分区域,特别大的对象放Humongous区域,也不够了开始FullGC。
ZGC(颜色算法):分区更灵活,逻辑上部分带。每次找到特别满的区域进行清除。
垃圾回收器工作流程:
新生代:老年代=1:2
新生代使用复制-算法,本身也分三个区,Eden、To Survivor、From Survivor。默认8:1:1
每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄都+1,当年龄达到15(默认的)时,升级为老年代,大的对象直接放入老年代。
老年代这边,当空间占用达到某一个阈值之后,触发Full GC,此时一般使用标记整理算法。
对象优先分配到新生代的Eden区,Eden区空间不够时,进行Minor GC,还不够,则分配到老年代。
Minor GC非常频繁,回收速度也快。大对象(需要大量连续内存空间的对象)直接进入老年代。
虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验、解析和初始化,最终形成虚拟机可以直接使用的Java类型。
隐式装载:程序运行过程中,碰到new等方式创建对象时,调用类装载器加载对应类到JVM中。
显示装载:通过class.forname()等方法,显示加载需要的类。
动态加载,保证基类完全加载,其他类需要的时候才加载。
实现通过类的权限定名获取该类的二进制字节流的代码块
如果一个类加载器收到了类加载的请求,首先不会自己去加载,
而是委派给父类加载器,这样所有的加载请求都会传到顶层的启动类加载器,
当父加载无法完成加载(搜索范围中没找到)时,子加载器才去尝试加载。
启动类加载器=》扩展类加载器=》系统类加载器=》自定义类加载器。
JDK的bin目录下,自带了很多监控工具。
jconsole:用于对JVM中内存、线程和类等进行监控。
jvisualvm:内存快照、线程快照、程序死锁、监控内存变化、GC变化等。