内存泄漏和内存溢出的理解
内存泄漏是指程序在运行过程中,一些不再被使用的对象仍然被引用,导致无法被垃圾回收,可用的内存逐渐减少。虽然Java程序中有垃圾回收机制,但是仍然有部分对象被不再使用的引用持有。
内存泄漏的原因可能有一下几种:
- 被静态集合(HasMap,ArrayList等)使用后未销毁
- 未停止的线程还有对该对象的引用
- 未取消对事件源的监听,导致持续被引用
内存溢出是指JVM在申请内存时,无法找到足够的内存的情况。常发生在堆中不足以存放新创建的对象时。
内存溢出常见的原因有:
- 被静态数据结构长时间持有
- 大量对象创建
- 方法不断递归调用导致栈溢出
JVM中有哪几种内存溢出的情况
- 堆内存溢出:可能是由于大量对象的创建导致堆内存不足,从而导致溢出
- 栈溢出:方法循环调用,没有出口,不断地压栈,导致栈溢出
- 元空间溢出:可能是由于系统代码太多,或者引入的第三方包或者类库太多,导致溢出
- 直接内存溢出
创建对象的过程
- 类加载检查:首先根据new关键字的参数,去常量池中寻找该参数所对应的类,并检查是否被解析,加载,初始化过,如果没有,先去执行类加载过程
- 分配空间:当类加载检查完毕后我们就可以知道该对象的内存,我们就可以在堆中为其划分一块内存
- 初始化0值:将分配到的内存空间全部初始化为0值,这样可以在该对象没有被初始化时就可以访问该字段
- 进行必要的设置,比如对象头等
- 执行init方法:从Java虚拟机角度来看,已将完成了对象的创建,但是从Java代码的层面来看,对象的创建才刚刚开始,也就是执行到构造方法这一步了
对象的生命周期
- 创建:通过new关键字在堆中为其分配一块内存,完成对象的创建
- 使用:在Java代码中调用该对象的字段或方法。
- 销毁:当垃圾回收器检测到该对象不再被引用时,会触发垃圾回收机制,释放该对象所占用的内存
类加载器有哪些
- 启动类加载器:它是由c++编写的,是类加载器的最顶层,负责加载Java的核心类库。它在Java程序中不能被直接引用
- 扩展类加载器:它是由Java语言实现的,它负责加载Java扩展目录下的jar包和类库,它的父类是启动类加载器并且它是由启动类加载器加载的
- 应用程序类加载器:它也是由Java语言实现的,它负责加载用户类路径下的类库。它是我们编写Java程序默认的类加载器。它的父类是扩展类加载器。
- 用户自定义类加载器:用户可以根据自身需求去自定义完成类加载器,这体现了Java程序的灵活性和扩展性。
什么是双亲委派原则
双亲委派原则是指当一个类加载器接收到类加载请求时,会将该请求委派给其父类,每一层都是这样,只有当父类表示自己不能加载该类时,子类才会尝试去加载。
双亲委派原则的作用
- 保证类的唯一性:由于双亲委派原则会将类加载请求委派给父类,因此最终都会传递给启动类加载器,这样避免了不同类加载器加载相同类的情况。同时也避免了用户自定义类覆盖Java核心类库
- 安全性:启动类加载器只会加载信任路径下的类库,因此这样可以防止假冒核心类库。
- 支持隔离和层次划分:双亲委派原则支持不同层次的类加载器服务不同类加载请求。比如程序类加载器加载用户代码,扩展类加载器加载扩展程序,启动类加载器加载核心类库。这样层次更加分明有助于实现沙箱安全。
- 简化加载流程:通过委派将类加载请求交给正确的类加载器加载,减少了每个类加载器加载的数量,简化了加载流程。
类加载过程
- 加载:通过类的全限定名,获取该类的二进制字节流文件,并将该二进制字节流文件所代表的静态数据结构转化为方法区运行时的数据结构。
- 连接:连接又分为三个阶段
- 验证:验证二进制字节码是否符合虚拟机的需求,并且保证虚拟机不被损坏,和二进制字节码所对应信息的正确性。
- 准备:为类的静态字段分配内存并初始化默认值
- 解析:虚拟机将常量池中的字符流转化为字节流。
- 初始化:这是类加载的最后一个阶段,简单来说就是执行类的构造器方法。但是这个构造器方法不是用户定义的,而是由虚拟机自动生成的