Java反射原理
反射是Java中一种很关键的技术,许多框架例如Spring中的IOC容器就是利用反射来实现的。
利用反射技术,我么你可以通过一个写着全限定类名的字符串来获取一个类属性。
反射可以让我们调用类中的私有方法和私有属性
JVM垃圾回收
垃圾判断算法:
引用计数法
为每一个对象添加一个计数器,每被其他对象引用一次计算器加1,引用结束计数器减1。
无法解决循环引用的问题。
可达性分析法:
选取一个GC ROOT对象作为搜索起始点,通过引用向下搜索,所走过的路径成为引用链。通过对象是否有达到引用链的路径来判断对象是否可以被回收。
可以作为GC ROOT的对象:虚拟机栈中引用的对象,方法区中类静态属性引用的对象,方法区中常量引用的对象,本地方法栈中引用的对象
JVM中的三种GC:
Minor GC:只回收年轻区。
Major GC:只回收老年区。
Full GC:同时回收年轻区和老年区。
堆内存的分配:
Java中的堆内存按照存活年龄可以分为两部分,新生区和老年区。
其中新生区又可以分为Eden区和Survivor区两部分,这两部分的默认比为8:2,其中Survivor区又会被分成两个大小相等的区,成为From区和To区。所以三部分的比默认为8:1:1。
如果Eden区中的对象存活周期较长,可以适当增加Survivor区的比例。
每次进行Minor GC时,系统会将Eden和From区中存活的对象复制到To区中,随后交换From区和To区。如果Survivor区中的对象在经历16个Minor GC之后还存活,则会被移动到Old区中。
垃圾回收算法:
标记清理算法:
发现一个可回收对象之后将其进行标记,随后将其回收,但会产生内存碎片问题
复制算法:
每次垃圾检测之后,将还存活的对象拷贝到一块新的内存上去,随后将原内存全部回收。
该方法在存活对象较少时使用,Java中的新生代Eden区使用此算法。
标记整理算法:
在垃圾判断结束后让所有存活对象向前移动一段距离,随后回收边界之外的内存
Java的老生代Old区中利用此算法
分代收集算法:
复制算法和标记整理算法的结合。Java虚拟机将对象分为两类,新生代和老生代,分别采取不同的垃圾回收算法。
大部分对象都会在Eden区中分配,如果Eden区内存耗尽,虚拟机会发起一次Minor GC回收对象。在每次垃圾回收之后,大部分Eden中的对象都会被回收,存活的对象会被送到Survivor的From区。如果From区的内存不够,对象会被直接送到Old区。
Survivor区相当于是Eden区和Old区的一个缓冲,Survivor中又分为From和To两个区,每次垃圾回收之后Eden区存活的对象都会被送到From区,在From区中的对象跟据年龄值的不同会被分配到不同的区。Survivor区中的对象在经历16次Minor GC后会被送到Old区。Survivor区相当于Eden区和Old区的一个缓冲。
JVM内存模型
根据JVM规范,JVM内存共分为虚拟机栈,本地方法栈,堆,方法区,程序计数器 5个部分。
Native关键字:被Native关键字修饰的方法叫做本地方法,本地方法意味着和平台有关,可移植性不高,Native方法在JVM的本地方法栈中运行。
程序计数器:
线程私有,每个线程都有一个程序计数器,记录着线程执行到的指令地址,如果当前方法为Native方法,其值为null
虚拟机栈:
线程私有,每个方法在执行的时候会创建一个栈帧,里面存放着局部变量,操作数,动态链接,方法返回地址等。每个方法从调用到执行完毕,对应着一个栈帧在虚拟机栈中的入栈和出栈。
本地方法栈:
线程私有,本地方法栈和虚拟机栈类似,主要为虚拟机使用到的Native方法服务,也会抛出StackOverflow和OutOfMemoryError。
Java堆:
线程共享,被所有线程共享的一块内存区域,在虚拟机启动的时候创建,用于存放对象实例。当堆中没有内存可以分配给实例,也无法再扩展时,就会抛出OutOfMemoryError错误。
老年代:2/3的堆空间
年轻代:1/3的堆空间
eden区:8/10 的年轻代
survivor0: 1/10 的年轻代
survivor1:1/10的年轻代
方法区:
线程共享,被所有方法线程共享的一块内存区域,用于存储已经被虚拟机加载的类信息,常量,静态变量等等。
类的加载过程
类加载的时机:
所有类或接口在首次主动调用时进行类加载。
调用包括利用new关键字创建对象,访问静态方法,访问静态变量。以及创建子对象。一个类在进行加载时,如果继承了父类且父类没有被加载,则会优先加载父类。
类的加载就是读取.class文件中的内容,并将类信息存放到内存中的方法区中。
加载步骤:
- 加载:查找并导入.class 文件
- 链接:链接又可以分为检查(验证.class文件的正确性),准备(为静态变量分配内存)和解析(将符号引用转化为直接引用)三部分
- 初始化:对静态变量,静态方法进行初始化
内存泄漏和内存溢出
内存泄漏:
定义:
内存泄漏是指在一个对象获得了系统分配的内存之后,在使用结束时没有进行归还内存的操作,导致这一块内存既不能被原有对象使用,也不能分配给新的对象。此时就称内存泄漏。
原因:
- Java中有垃圾回收机制,会将无用的对象进行回收处理释放空间,但有些情况还是会造成内存泄漏。
- 静态集合类。Java中的静态对象的生命周期是与程序的生命周期相同的,如果一个静态集合类中包括短生命周期的对象,就会导致这些短生命周期的对象不会被回收,造成内存泄漏。
- 流和连接。各种输入输出流和数据库连接,网络连接,I/O连接等在使用后如果没有进行close操作,就无法被回收,造成内存泄漏。
- 变量不合理的作用域。如果一个变量的作用域大于它实际使用的区域时,就会造成变量使用后依旧存在,造成内存泄漏。
- 内部类持有外部类。如果一个内部类返回一个外部类对象,就会产生一种类似循环引用的现象,从而产生内存泄漏。
- 改变哈希值。如果一个对象已经存到哈希表中,但它的内容却发生了改变,就会导致哈希表中的原对象不会再被访问到,却会一直存在,造成内存泄漏。
- 长生命周期的对象持有短生命周期对象的引用,例如静态变量,单例等等。
内存溢出:
当系统中剩余可用的内存不能满足对象需要分配的内存时,就会发生内存溢出。内存泄漏的堆积最终就会导致内存溢出。
双亲委派机制
在JVM加载类信息时使用的机制:
简单点说就是在加载一个类信息前,系统会从低到高逐级访问类加载器,查询该类是否已经被加载,如果已经被加载则不用再重复加载。如果查询到最高级仍然没有被加载,就会从高到底依次查询是否可以加载此类,如果可以的话就对此类进行加载,如果查询到最低级的类加载仍然不能加载此类,就会抛出ClassNotFound异常。
Java中的类加载器分为四个层级:
从高到低依次是:
BootstrapClassLoader(启动类加载器):负载加载系统的核心类,Java.*,以及构造ExtClassLoader和AppClassLoader。C++实现,用户不可直接调用。
ExtClassLoader(标准扩展类加载器):Java编写,负责加载一些扩展的jar
AppClassLoader(系统类加载器):加载程序中的类
CustomClassLoader(用户自定义类加载器):用户自定义的类加载器,可以加载指定目录。
双亲委派机制的优点:
避免重复加载,每次加载前都遍历一次该类是否已经被加载。
避免恶意代码,因为核心类已经被虚拟机自动加载过。