目录
什么是Java虚拟机?
虚拟机架构设计
为什么用元空间取代永久代
双亲委派机制
分代年龄为什么是15次
GC算法
如何判断一个对象可以被回收
什么是Java虚拟机?
Java virtual machine。虚拟机,像真实的计算机一样,能够运行字节码指令。好处是屏蔽了操作系统的细节,使用Java可以一处编写,到处运行。
虚拟机架构设计
类加载子系统:将编译好的class文件加载到类加载子系统里,功能是查找并验证类文件,完成相关的内存分配和对象赋值。
运行时数据区:完成数据存储和数据交换。分为线程共享和隔离区。线程共享区包括方法区和堆区,他们程序员能够通过编写代码直接操作的内存区,隔离区包括:栈,程序计数器,本地方法栈,他们是由jvm调度的内存区。
方法区:主要功能是存储运行时常量池,字段,方法元数据和类的元数据。
堆:存储Java对象的实例,新建的类的实例都存在堆区。
栈:通过线程的方式运行来加载各种方法。
程序计算器:保存了每个线程执行方法的地址。
本地方法区:加载并运行native方法。
运行时数据区的5个内存区就能完成Java程序的逻辑执行和数据的交换。
执行引擎:即时编辑器:将字节码翻译成操作系统能够执行的CPU指令,可以通过jvm参数来设置解释执行或者编译执行。解释执行就是直接将字节码作为源程序输入执行,不必等待编译全部完成再执行,可以省去不必要的编译时间。编译执行就是由编译程序将目的代码一次性编译成目标程序,再由机器执行,执行效率更高,占用内存资源更少。
垃圾回收器:负责对运行时数据区的数据进行管理和回收,3中回收算法:复制,标记清除,标记整理。我们可以通过jvm参数设置使用哪种算法。
本地方法接口,jni,可以通过JNI查找和加载C或C++代码,操作系统的动态链接库DLL
为什么用元空间取代永久代
jdk1.8hotspot取消了永久代使用元空间实现方法区存储数据。元空间使用本地内存,不用考虑GC问题。在默认情况下元空间可以无限制使用本地内存,可以限制大小。
取代原因:1.永久代内存是有上限的,虽然可以设置大小,但是jvm加载class总数,大小是很难确定的,所以任意出现OOM问题,元空间使用本地内存,上限比较大,可以避免这个问题。
2.永久代通过full GC进行垃圾回收,也就是和老年代同时实现垃圾回收。永久代被元空间替换后,简化FULL GC。可以在不暂停的情况下释放数据,同时也提高了GC性能。
双亲委派机制
类加载机制:Java编译器将Java源文件编译成class文件,再由JVM类加载器加载class文件到内存,jvm加载完成后得到一个class对象。拿到类对象之后就可以进行实例化。
类加载器:jvm设计3个类加载器加载不同范围的jar包和class文件。
核心类库加载器bootstrap,加载Java的核心类库,Java_home\lib目录下的rt.jar,resources.jar
扩展类加载器extension,Java_home\lib\ext目录下jar,classapplication,当前应用中的classpath下的jar,class
自定义类加载器,classLoader类实现
双亲委派机制是按照类加载器的层级关系逐层进行委派。例如,我们需要加载一个class文件的时候,首先把这个class文件的查询和加载交给父加载器执行,如果父加载器不能加载,在自己加载这个class文件。
有2个好处:1保证安全性,因为这种层级关系代表一种优先级,所有类的加载优先使用bootstrap.那么对于核心类库中的类就不能破坏,比如自己写一个string,最终还是会交给bootstrap类加载器加载。在加上每个类加载器有不同的作用范围,就意味着自己写的string不能覆盖核心类库中的类。2.避免重复加载,如果已经加载过来就没有必要再次加载。
分代年龄为什么是15次
堆内存分为young generation(Eden space,survivor space),old generation。使用new创建对象的时候,jvm会在Eden space 分配一块内存来存储这个对象。Eden space内存不足时会触发young GC进行对象回收,存在引用关系的对象,jvm把他们移动到survivor space。
survivor space 分为from 和 to 区,刚从Eden space 转移过来的对象被分配到 from区,每经历一次youngGC这些没有别回收的对象在from和to区来回移动,每移动一次,对象的GC年龄加1.默认情况下当分代年龄到达15时候,jvm就移动对象到old generation。
对象的GC年龄存储在对象头里,一个对象在jvm内存里由3个部分组成,对象头,实例数据,对齐填充。对象头有4位存储GC年龄,4位存储最大数15.jvm提供参数设置GC年龄大小,不能超过15,若一个对象触发了15GC还没有回收,就移动到老年代。动态年龄判断方法决定是否移动到老年代。
GC算法
标记清除:将存活对象打上标记,没有标记的对象就是垃圾对象需要被垃圾回收器回收。缺点:产生较多的内存碎片,随着时间推移,这些碎片使得系统无法再大量分配连续的内存空间。导致频繁触发GC操作。
标记复制:把内分为2等分,每次只使用其中一份,等待这部分使用的内存满了之后,标记存活对象,然后把存活对象复制到另一部分闲置的内存里,留在第一部分内存里的对象全部回收。那么空闲下来的内存空间成为了使用状态,而原来使用中的内存空间闲置下来继续使用。之后重复这个循环。缺点:我们实际使用的内存空间只有50%,而另外一半空闲的。浪费内存空间。如果大量复制对象,垃圾回收耗时就比较长,适用于存活对象少垃圾对象多的情况。
标记整理:标记出存活的对象,再把所有存活对象整理到内存空间的另一端,而没有被标记的对象可以被覆盖或者释放。缺点:和标记清除算法差不多,只不过增加了把存活对象移动到一起的操作。
Java对象基本是临时对象,很快被回收,jvm使用了分代设计,根据对象在内存里的存活时间,分为年轻代,老年代和永久代。年轻代使用标记复制算法,每次复制的时候存活下来的对象较少。老年代是经历过几次垃圾回收的对象,jvm认为他们可以继续存活下去,采用标记清除算法。永久代表示一直会存活的对象,只有触发fullGC时候才会被回收。所以永久代创建对象过多容易内存溢出。
如何判断一个对象可以被回收
最重要的是判断这个对象是否还在被使用,只有没有被使用的对象才能被回收。
引用计算器:给每个对象添加一个引用计数器,记录指向当前对象的引用次数,一旦引用次数变成0,就可以回收了。
可达性分析:首先确定一系列肯定不能回收的对象,作为GC ROOT对象,例如,虚拟机栈引用的对象,本地方法栈引用的对象,然后以GC ROOT对象作为起始点向下搜索,寻找他的直接和间接引用对象。在遍历完成后,发现有一些对象不可达,这样的对象可以被回收。这个过程需要暂停所有用户线程。