JVM体系结构
1.Java虚拟机体系结构包括能装载字节码的类装载子系统、运行时数据区、执行引擎和本地(方法)接口。其中,运行时数据区包括方法区、堆区、栈区、程序计数器和本地方法栈模块。
(1)方法区:存储类的相关信息
A、这个类的全限定名,例如java.lang.Object
B、这个类型的访问修饰符。
C、字段信息(字段名、类型、修饰符)和方法信息(方法名、返回类型、参数数量和类型、修饰符)
D、除了常量以外的所有类的静态变量。
(2)堆区:Java程序在运行时所创建(new)的所有对象实例都放在同一个堆中,所有线程共享这个堆。
已分配的内存是通过垃圾收集器(GC)来进行回收。
(3)栈区
A.栈区中存放着每个线程的数据。存储了线程中方法调用的状态,包括局部变量、参数、返回值
B.栈中保存了每个方法的调用状态。
(4)程序计数器。总是指向下一条被执行指令的地址。
(5)本地方法栈。通过JNI调用本地方法时,会用到本地方法栈。
(6)执行引擎。用来执行字节码。
(7)类加载子系统。分为启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)、应用程序类加载器(Application ClassLoader)和用户自定义的类加载器(User Defined ClassLoader).
2.数据存储位置。
(1)new出来的对象存在堆区,而这些new出来对象的引用则存在于栈区。
(2)类似于String a=”abc”;之类的常量存在于常量池中,而常量池则存在于方法区中。
(3)静态变量存储于方法区
(4)基本数据类型,值在栈区,引用此值的引用在栈区。
垃圾回收机制
我通过堆内存来说明垃圾回收机制。
堆内存分为:年轻代、年老代和持久代。年轻代分为伊甸园和2个幸存区(Surivor)。持久代主要存放的是Java类信息或在代码中通过import引入的信息,垃圾回收流程主要涉及的是年轻代和年老代。
1.垃圾回收流程:
(1)我们new出来的对象一般在伊甸园(Eden)中申请空间,如果伊甸园满了,那么会把伊甸园中还存活的对象复制到其中的一个Survivor区中。把伊甸园中还存活的对象复制到其中的一个Survivor区中,是一个隐含的回收流程,就已经把其中无用的对象回收了。
(2)当伊甸园区和其中的一个Surivor区都满了时,会把伊甸园区和其中一个Survivor区的存活对象再复制到另外一个Survivor区中,这里也是一次隐含的回收流程。
(3)如果年轻代的空间都满了(无法从伊甸园和两个Survivor区中申请到对象),虚拟机会把年轻代中还存活的对象复制到年老代中。
(4)当年老代再满时,会启动Full GC,对年轻代、年老代和持久代进行全面回收。
2.垃圾回收的两类回收机制:
(1)轻量级回收(Minor GC)。年轻代的回收流程都属于轻量级回收,例如我们new出来的一个对象再Eden区申请空间失败,就会发出这类GC。这类回收机制,一般会用到一种效率相对较高的标记复制算法,此算法是将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这块的内存用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。即就是对无用对象不会删除,只是把标记存活的对象从一个内存区复制到另一个内存区。
(2)重量级的Full GC,在年老代被写满、持久代被写满、程序员显示地调用了System.gc()方法和通过Java命令分配堆空间的运行策略时会触发。通过System.gc()来启动Full GC时,Full GC不是在调用这个方法后就启动的。
3.判断对象可回收的依据:
JVM是通过当某个对象上没有强引用时,判断该对象就可以被回收。但是,在绝大数的场景中,当某对象上的最后一个强引用被撤去后,该对象不会被立即回收,而是会在下次启动垃圾回收机制时被回收。
利用引用计数法来判断对象上是否有强引用,当一个对象上有一个强引用时,把该对象的引用计数值(引用指向此地址)加1,反之减1,计数值为0可以被回收。但是,无法回收循环引用的对象。
因此,引入根搜索算法,此算法是从一个根节点(GC ROOT)开始,寻找它所对应的引用节点,找到这个节点后,继续寻找该节点的引用节点,以此类推,当所有的引用节点都搜索完毕后,剩下的就是没有被引用的节点,即可回收。可以作为根节点的是:
(1)虚拟机栈中引用的对象
(2)方法区中静态属性引用的对象
(3)方法区中常量引用的对象
(4)本地方法栈中引用的对象。
4.内存调优:
虽说Java虚拟机能自动回收内存,但在平时写代码时,要遵循一些要点来提升内存性能。例如:
(1)物理对象用好之后要及时的close,例如Connection,IO流
(2)大的对象用好后应当及时设置成null,以撤销强引用
(3)集合对象用好后应当及时clear
(4)尽量别频繁地使用String对象,容易产生内存碎片
(5)尽可能地使用软引用和弱引用,会提前对象被回收地时间。其中,软引用是指如果一个对象只具有软引用,而当前虚拟机堆内存空间足够,那么垃圾回收器就不会回收它,反之就会回收这些软引用指向的对象;弱引用与软引用的区别是,垃圾回收器一旦发现某内存上只有弱引用时,不管当前内存空间是否足够,都会回收这块内存。
(6)不建议重写finalize方法。此方法是Object类中的protected类型的方法,子类通过覆盖这个方法来实现回收前的资源清理工作。这个执行流程是:1)JVM通过根搜索算法判断出某对象处于可回收状态时,会判断该对象是否重写了Object类的finalize方法,如果没有则直接回收。2)如重写过finalize方法,而且未执行过该方法,则把该对象放入F-Queue队列,另一个线程会定时遍历F-Queue队列,并执行该队列中各对象的finalize方法;3)finalize方法执行完毕后,GC会再次判断该对象是否可被回收,如果可以,则进行回收,如果此时该对象上有强引用,则该对象“复活”,处于不可回收状态。但是,由于垃圾回收和遍历F-Queue队列不是同一个线程,因此,一旦重写了这个方法就有可能导致对象被延迟回收;如果这个方法再被放入错误的代码,就有可能导致该对象无法被回收。所以,一般不会重写此方法。
(7)可以通过调整命令行参数调整堆内存地性能,一般修改-Xms(设置程序启动时的初始堆大小)或-Xmx(设置程序能获得的最大堆的大小)参数。
finalize方法作用
此方法是Object类中的protected类型的方法,子类通过覆盖这个方法来实现回收前的资源清理工作。这个执行流程是:
1)JVM通过根搜索算法判断出某对象处于可回收状态时,会判断该对象是否重写了Object类 的finalize方法,如果没有则直接回收。
2)如重写过finalize方法,而且未执行过该方法,则把该对象放入F-Queue队列,另一个线 程会定时遍历F-Queue队列,并执行该队列中各对象的finalize方法;
3)finalize方法执行完毕后,GC会再次判断该对象是否可被回收,如果可以,则进行回收,如果此时该对象上有强引用,则该对象“复活”,处于不可回收状态。但是,由于垃圾回收和遍历F-Queue队列不是同一个线程,因此,一旦重写了这个方法就有可能导致对象被延迟回收;如果这个方法再被放入错误的代码,就有可能导致该对象无法被回收。所以,一般不会重写此方法。