目录
反射
垃圾回收机制常见算法
搜索算法
回收算法:
JVM内存结构和内存分配
java内存模型
java内存分配
堆和栈区别
引用类型有哪些
强引用
软引用
弱引用
虚引用
类加载器
类加载器的种类
类什么时候被初始化
类初始化的步骤
JVM加载class
如何获取一个类对象
GC机制
为什么有GC
GC回收哪些内存
什么时候回收垃圾
有GC机制为什么还会有内存泄漏
内存溢出
原因
解决方案
String str = "abc"
对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
步骤:先获取要反射类的字节码-----1.Class.forName(className) 2.类名.class 3.this.getClass()
将字节码中的方法,变量,构造函数等映射成相应的 Method、Filed、Constructor 等类
回收对象前首先必须发现那些无用的对象,常见搜索算法如下:
引用计数器算法(已废弃):给对象设置计数器,被引用时加一,引用失效减一,为0时判定为垃圾。
根搜索算法:通过一些“GC Roots”对象作为起点,从这些节点开始往下搜索,搜索通过的路径成为引用链(Reference Chain),当一个对象没有被 GC Roots 的引用链连接的时候,说明这个对象是不可用的。
标记-清除算法(Mark-Sweep)(DVM 使用的算法):标记阶段确定要回收的对象并做标记,清除阶段紧随标记阶段,将标记阶段确定不可用的对象清除。
复制算法(Copying):把内存分成大小相等的两块,每次使用其中一块,当垃圾回收的时候,把存活的对象复制到另一块上,然后把这块内存整个清理掉。
标记-整理算法(Mark-Compact):当垃圾回收的时候,把存活对象往内存的一端移动,然后直接回收边界以外的内存
分代收集(Generational Collection):根据对象的存活时间把内存分为新生代和老年代,根据各代对象的存活特点,每个代采用不同的垃圾回收算法。新生代采用复制算法,老年代采用标记—整理算法。
内存分为方法区、java栈和java堆
1、方法区是静态分配的,编译器将变量绑定在某个存储位置上,而且这些绑定不会在运行时改变。常数池,源代码中的命名常量、 String 常量和 static 变量保存在方法区。
2、栈:后进先出。一个栈的空间可能是连续的,也可能是不连续的。保存基本数据类型和对象的引用。
3、堆:以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。java对象内存总是在堆中分配的。
1、基础数据类型直接在栈空间分配
2、方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收
3、引用数据类型,需要用 new 来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量
4、方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完后从栈空间回收
5、局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆
空间区域等待 GC 回收
6、方法调用时传入的实际参数,先在栈空间分配,在方法调用完成后从栈空间释放
7、字符串常量在 DATA 区域分配 , this 在堆空间分配
8、数组既在栈空间分配数组名称, 又在堆空间分配数组实际的大小
栈:1、申请方式:系统自动分配。
2、申请后的响应:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
3、申请大小限制:栈是向低地址扩展的数据结构,是一块连续的内存的区域。栈的容量是系统预先规定好的,申请空间不能超过栈的剩余空间,因此,能从栈获得的空间较小。
4、申请效率:系统自动分配,速度较快
5、存储内容:函数调用时,第一个进栈的是函数调用语句的下一条可执行语句的地址,然后是函数的各个参数,然后是函数中的局部变量。静态变量是不入栈的。本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
6、数据结构层面:先进后出的数据结构
堆:1、手动申请并指明大小。
2、 申请后的响应:操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链。
3、申请大小限制:堆是向高地址扩展的数据结构,是不连续的内存区域。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
4、申请效率:new 分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便
5、存储内容:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排
6、数据结构层面:满足优先队列的数据结构
堆栈都是java用来在随机存取存储器(RAM)存放数据的地方。
栈存取速度快,缺乏灵活性,数据可共享。堆存取速度慢,可动态分配内存。
对象的引用分为四种级别,这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用
对象位于head中,heap 中对象有强可及对象、软可及对象、弱可及对象、虚可及对象和不可到达对象。应用的强弱顺序是强、软、弱、和虚。
最普遍的引用,一个对象拥有强引用,那么垃圾回收器绝不会回收它。----所以强可及对象是全局变量时需要在确定不使用它时赋值为null,不然不能回收。
public static void main(String[] args) {
String abc = new String("abc"); //强引用 abc是强对象
SoftReference softAbc = new SoftReference<>(abc); //软引用
WeakReference weakAbc = new WeakReference<>(abc); //弱引用
//此时abc有三个引用,仍然是强可及对象
abc = null; //强可及对象变为软可及对象
softAbc.clear(); //变为弱可及对象
}
如果一个对象只具有软引用,那么如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。-----内存不足才会清理。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收, Java 虚拟机就会把这个软引用加入到与之关联的引用队列中。
一个对象只具有弱引用,那该类就是可有可无的对象,只要该对象被 gc 扫描到了随时都会把它干掉,生命周期更短。------不管内存是否够,扫描到就清除。
可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收, Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃
圾回收的活动。
虚引用必须和引用队列(ReferenceQueue)联合使用。如果虚引用所引用的对象被垃圾回收, Java 虚拟机就会把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
1、根类加载器 C++写的 ,看不到源码
2、扩展类加载器 加载位置 : jre\lib\ext 中
3、系统(应用)类加载器(System\App) 加载位置 : classpath 中
4、自定义加载器(必须继承 ClassLoader)
1、创建类的实例,也就是 new 一个对象
2、访问某个类或接口的静态变量,或者对该静态变量赋值
3、调用类的静态方法
4、反射(Class.forName("com.lyj.load"))
5、初始化一个类的子类(会首先初始化子类的父类)
6、JVM 启动时标明的启动类,即文件名和类名相同的那个类
1、如果这个类还没有被加载和链接,那先进行加载和链接
2、假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接父类(不适用于接口)
3、假如类中存在初始化语句(如 static 变量和 static 块),那就依次执行这些初始化语句
JVM 中类的装载是由类加载器(ClassLoader)和它的子类来实现的,类加载器负责在运行时查找和装入类文件中的类。
当 Java 程序需要使用某个类时,JVM 会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class 文件,然后产生与所加载类对应的 Class 对象。加载完成后, Class 对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后 JVM 对类进行初始化。
1、类型.class String.class
2、对象.getClass() "abc".getClass()
3、Class.forName(),例如: Class.forName(“java.lang.String” )
安全性考虑
减少内存泄漏
减少程序员工作量
内存运行时 JVM 会有一个运行时数据区来管理内存。它主要包括 5 大部分:程序计数器(Program CounterRegister)、虚拟机栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap).
程序计数器、虚拟机栈、本地方法栈是每个线程私有的内存空间,随线程而生,随线程而亡,无需考虑内存回收的问题。
GC 主要进行回收的内存是 JVM 中的方法区和堆。
见上文垃圾回收机制常见算法。
总而言之,对于堆中的对象,主要用可达性分析判断一个对象是否还存在引用,如果该对象没有任何引用就应该被回收。
对于方法区中的常量和类,当一个常量没有任何对象引用它,它就可以被回收了。而对于类,如果可以判定它为无用类,就可以被回收了。
理论上 Java 因为有垃圾回收机制(GC)不会存在内存泄露问题,但实际开发中,可能会存在无用但可达的对象,这些对象不能被 GC 回收,因此也会导致内存泄露的发生。
1、内存中加载的数据量过于庞大,如一次从数据库取出过多数据;--------尽量分页查询数据库
2、集合类中有对对象的引用,使用完后未清空,使得 JVM 不能回收;
3、代码中存在死循环或循环产生过多重复的对象实体;
4、使用的第三方软件中的 BUG;
5、启动参数内存值设定的过小;
1、修改 JVM 启动参数,直接增加内存
2、检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误
3、对代码进行走查和分析,找出可能发生内存溢出的位置
1、先定义一个名为 str 的对 String 类的对象引用变量: String str;
2、在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着创建一个新的 String 类的对象 o,并将 o 的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象 o。如果已经有了值为"abc"的地址,则查找对象 o,并返回 o 的地址。
3、将 str 指向对象 o 的地址。
即:创建了一个指向 String 类的对象的引用变量 str,这个对象引用变量指向了某个值为"abc"的 String 类