Java平台与垃圾回收机制,java平台垃圾回收
为了便于管理,JVM在执行Java程序的时候,会把他所管理的内存划分为多个不同的区域.
①class文件
class文件时Java程序编译后产生的中间代码,这些中间代码将会被JVM解释执行.
②类装载器子系统
JVM有两种类装载器,分别是启动类装载器和用户自定义类装载器.其中,启动类装载器是JVM实现的一部分;用户自定义类装载器则是Java程序的一部分,必须是ClassLoader的子类,常用的类加载器主要有以下几种:
a. BootStrap ClassLoader.这是JVM的根ClassLoader,它是用c++语言实现的,当JVM启动时,初始化此ClassLoader,并由此ClassLoader完成$JAVA_HOME中jre/lib/rt.jar(SUN JDK的实现)中所有class文件的加载,这个jar中包含了Java规范定义的所有接口以及实现.
b. Extension ClassLoader. JVM用此ClassLoader来加载扩展功能的一些jar包.
c. System ClassLoader. JVM用此ClassLoader来加载启动参数中指定的Classpath中的jar包以及目录,在Sun JDK中,ClassLoader对应的类名为AppClassLoader
d. User-Defined ClassLoader. User-Defined ClassLoader是Java开发人员继承ClassLoader抽象类自行实现的ClassLoader,基于自定义的ClassLoader可用于加载非Classpath中的jar以及目录.
③ 方法区
方法区用来存储被虚拟机加载的类信息,常量,静态变量,和编译器编译后的代码(.class文件)等数据.在类加载class文件时,这些信息将会被提取出来,并存储到方法区中.这个区域是所有线程共享的区域,所以它被设计为线程安全的.
方法区中还存放了运行时的常量池,例如字符串常量池.
④堆
堆是虚拟机启动的时候创建的被所有线程共享的区域.这块区域主要用来存放对象的实例,通过new操作创建出来对象的实例都存储在堆空间中. 因此堆是垃圾回收器管理的重点区域.
⑤虚拟机栈
栈是线程私有的区域 , 每当有新的线程创建时,就会给它分配一个栈空间,当线程结束后,栈空间就会被回收.因此,栈与线程拥有相同的声明周期.
栈主要用来实现Java语言中方法的调用和执行,每个方法在被执行的时候,都会创建一个栈帧用来存储这个方法的局部变量 , 操作栈 , 动态链接和方法出口等信息.当进行方法调用时,通过压栈与弹栈操作进行栈空间的分配与释放.当一个方法被调用的时候,会压入一个新的栈帧到这个线程的栈中,当方法调用结束后,就会弹出这个栈帧,从而回收调用这个方法使用的栈空间.
⑥程序计数器
程序计数器也是线程私有的资源,JVM会给每个线程创建单独的程序计数器.它可以被看做是当前线程执行的字节码的行号指示器,解释器的工作原理就是通过改变这个计数器的值来确定下一条需要被执行的字节码指令,程序控制的流程(循环,分支,异常处理,线程恢复)都是通过这个技术器来完成的.
(7)本地方法栈
与虚拟机栈作用类似,只不过本地方法栈是为虚拟机使用的Native(本地)方法服务
(8)执行引擎
执行引擎主要负责执行字节码.
(9)垃圾回收器
回收程序中不再使用的内存.
垃圾回收机制的原理是什么?
Java语言提供了垃圾回收器来自动检测对象的作用域,实现自动地把不再被使用的存储空间释放掉.具体而言,垃圾回收机制主要负责完成3项任务:分配内存,确保被引用对象的内存不被错误地回收,回收不再被引用的对象的内存空间.
垃圾回收器使用有向图来记录和管理堆内存中的所有对象.通过这个有向图就可以识别哪些对象是可达的,哪些对象是不可达的,所有不可达对象都是可被垃圾回收器回收的.
Java开发人员可以通过调用System.gc()方法来通知垃圾回收器运行,当然,JVM也并不会保证垃圾回收器马上就会执行.由于gc()方法的执行会停止所有的响应,去检查内存中是否有可回收的对象,这会对程序的正常运行以及性能造成极大的威胁,所以,实际编程中,不推荐频繁使用gc方法.
如何使JVM的堆, 栈 和持久带发生内存溢出?
①不断的用new实例化对象并且一直保持对这些对象的引用,实例化足够多的实例就会导致堆溢出
如:
List list = new ArrayList();
while(true)
l.add(new Object());
② 栈溢出 – 无限递归调用
③ 持久代.
当一个类第一次被访问的时候,JVM需要把类加载进来,而类加载器就会占用持久代的空间来存储classes信息,持久带中的信息主要包括:类方法,类名,常量池,和JVM使用的内部对象等.当JVM需要加载一个新类的时候,如果持久代中没有足够的空间,此时就会抛出Java.Lang.OutOfMemoryError:PermGen Space异常.
所以,当代码加载足够多类的时候就会导致持久代溢出.(并不是所有的Java虚拟机都有持久代的概念)
Java堆被划分为新生代和老生代,有什么区别?
根据对象的生命周期的长短把对象分成不同的种类:新生代,老生代和持久代. 进行分代垃圾回收.
分代垃圾回收算法的主要思路:把堆分为两个或者多个子堆,每一个子堆被视为一代.在运行的过程中,优先收集哪些年幼的对象,如果一个对象经过多次收集仍然存活,那么就可以把这个对象移到高一级的堆里,减少对其扫描的次数.
新生代 : 包括3部分. 1个Eden区和2个Survivor区.
Eden区主要用来存储新建的对象,
Survivor区是大小相等的两块区域,在使用"复制"回收算法是,作为双缓存,起内存整理的作用.Survivor区始终保持一个是空的.
老生代:存储生命周期较长的对象,超大的对象(无法在新生代分配的对象)
持久代:存放代码,字符串常量池和静态变量等.SunJDK把方法区实现在了永久代.
minorGC : 主要用来对新生代进行垃圾回收,把Eden中不能被回收的对象放到空的Survivor区,另一个Survivor区不能被垃圾回收器回收的对象也会放到这个Survivor区中,这样能保证有一个Survivor区是空的 . 如果在这个过程中,Survivor区也满了,或者有些对象已经存在非常长的实际,这些对象就会被放到老生代中. 如果老生代也满了,就会触发fullGC
fullGC: 用来清理整个堆空间,包括新生代和老生代.fullGC()会造成很大的内存开销
如何避免fullGC()?
①避免调用System.gc()方法,因为它会触发fullGC
②老生代空间不足时会触发fullGC,所以应尽量做到让对象在MinorGC阶段被回收,不要创建过大的对象及数组.
或者根据实际情况增大Survivor区,老生代空间或调低触发并发GC的概率.
③ 永久代满.可以增大永久代的空间(MaxPermSize=16m;)
注意:Java8中移除了永久代,新加了一个称为元数据的native内存区,所以大部分的类的元数据都在本地内存中分配.
相关文章暂无相关文章