1.1组成
JVM运行时数据区大致分为五个区域:方法区、栈、本地方法栈、堆、程序计数器
一个进程对应一个jvm实例,一个运行时数据区,又包含多个线程,这些线程共享了方法区和堆,每个线程包含了程序计数器、本地方法栈和虚拟机栈。
程序计数器:记录的是正在执行的虚拟机字节码指令的地址,通过改变程序计数器,java程序才能按顺序、循环、跳转等流程执行各个方法。该区域是所有区域中唯一没有定义内存溢出错误的区域。因为CPU需要不停的切换各个进程,切换回来后就得知道要从哪继续执行 JVM的字节码解释器就需要改变那PC寄存器的值来明确下一条应该执行什么样的字节码指令。
虚拟机栈:java为每个方法保存状态信息的区域,这里存放的是每个方法中的局部变量(8种基本数据类型,对象的引用地址)、方法出口、动态链接等,著名的栈溢出错误就是在这里发生。
本地方法栈:java可以执行非java函数,这些函数的状态信息就保存在这个区域,因此这个区域也有可能发生栈溢出。(native方法)
堆:一块线程共享的存放对象实例和数组的内存区域,线程安全问题的根本原因,也是整个内存区域中最大的一块。
方法区:存储已被加载的类信息(构造方法,接口定义)、常量(final)、静态变量(static)等,著名的常量池就位于这里。
1.2 类加载机制
当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。
加载:将类的class文件读入到内存,创建一个类对象的过程,加载的方法有三种:
new的方式加载、调用类反射的方法加载、调用类加载器的加载方法加载,其中使用类加载器加载的对象不会执行其中的静态语句块。
java采用双亲委派机制来使用加载器,
(什么叫双亲委派机制 :
如果一个类加载器收到类加载的请求,它首先不会自己尝试去加载这个类,而是把这个请求委派给父加载器去完成。每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求,子加载器才会尝试自己去加载。
为什么使用双亲委派机制
1. 为了保证Java程序的安全,如java.lang.Object是所有类的父类,如果用户自己编写了一个同样限定名的类,放在程序的classpath中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也会变得一片换乱。所以是为了避免用户自己编写的类动态替换Java的一些核心类。如Object,String
2. 避免类的重复加载,因为JVM中区分不同类,不仅仅是根据类名,相同的class文件被不同的ClassLoader加载就是不同的两个类。
)
类加载完成后,java会对类进行验证,检验类的内部结构是否正确,诸如数组越界这类错误就是在这里发生的,这里也是整个加载过程最费时的部分。
验证完成后,最后就是真正的初始化,java会先初始化静态部分,再初始化实例部分,在这基础上java又会优先初始化父类对象,最后才是子类对象。
整个类的加载到此就结束了。
1.3Java有4种类加载器
Bootstrap ClassLoader(启动类加载器):加载<JAVA_HOME>\lib路径下的类库,通常是rt.jar
Extension ClassLoader (扩展类加载器) : 加载<JAVA_HOME>\lib\ext路径下的类库
Application ClassLoader(应用程序类加载器):加载用户类路径(classpath)(没有指定的话通常指应用程序的当前目录)上的类库。
User ClassLoader(用户自定义类加载器):继承ClassLoader,加载指定路径的class文件
2 GC(重点)
JVM内存按回收机制可分为年轻代和老年代,年轻代分为eden区(伊甸)和多个幸存区,老年代则不分区。无论是YGC还是Full GC,都会使java线程暂停,但是YGC暂停的事件极短,因此基本是针对减少Full GC的方向优化。 可以通过参数指定分配的最大堆大小、初始堆大小、年轻代大小、比值、指定并发收集
器、并行收集器等等。
2.1 YGC
新的对象会存入eden区,当eden区满了放不下的时候,会对年轻代的内存进行垃圾回收,eden中有用的对象移到幸存区,清空eden区,称为YGC。
2.2 Full GC
某个对象经过多次YGC后依然存活,会移植到老年代。当老年代满了放不下的时候,就会触发FullGC,对整个内存进行一次垃圾回收。
无论是YGC还是Full GC,都会使java线程暂停,但是YGC暂停的事件极短,因此基本是针对减少Full GC的方向优化。
可以通过参数指定分配的最大堆大小、初始堆大小、年轻代大小、比值、指定并发收集器、并行收集器等等。
2.3 垃圾回收机制(算法原理)
引用计数法:对象每被引用一次就+1,为0时回收,速度很快但是无法识别循环引用
复制清除法:将内存分为两块,其中一块写满后,遍历对象标记有用的对象复制到另一块,然后把这一块清理,这样复制的内容很少而且内存始终连续,缺点是始终需要有一块内存空出来用于复制。(幸存区中没有对象的定义为幸存区to. 有对象的定义为幸存区from)
标记清除法:遍历所有对象,标记没被引用的,然后统一清除。缺点是效率低、清理后内存不连续。
标记整理法:遍历出有用的对象,将这些对象全都向一端移动,然后清理其它空间,一样能腾出连续的内存,但是移动对象的成本比复制大得多。
GC采用分代收集法:年轻代采用复制清除法,每当eden满时,就遍历出eden和幸存者1区的有用对象复制到幸存者2区,然后清空重新写起。因此无论何时一定有一个幸存者区是空的。 老年代由于有用的对象很多所以复制成本高,采用标记整理法减少复制。