- 内存溢出。 就是内存不够,程序所能容纳的最大内存小于需要放入的内存大小
- 内存泄露。已经分配的内存由于某些原因无法回收,导致可用内存越来越小。
/**
* java.lang.OutOfMemoryError: Java heap space
*/
public static void oom() {
List<Object> list = new ArrayList<>();
while (true) {
list.add(new Object());
}
}
/**
* java.lang.OutOfMemoryError: Java heap space
*/
public static void oom2() {
String[] a = new String[1111111111];
}
/**
* java.lang.StackOverflowError
*/
public static void stackOOM() {
stackOOM();
}
- 句柄方式
栈中的变量指向堆中的句柄池,再指向对象实例。
优点:对象位置发生变动后,如gc,不需要改变栈中指向的位置。
缺点:中间多了一步操作
- 直接指针
栈中的变量指向对象类型数据的指针。
优点:查询对象速度快
缺点:就是句柄方式的优点
- 检查对象是否合法,是否已经初始化
- 分配内存。 采用指针碰撞法或者空闲列表。
指针碰撞法::假设Java堆的内存是绝对规整的,所有用过的内存都放一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅把那个指针向空闲空间那边挪动一段与对象大小相等的距离
空闲列表::如果不规整,那么就需要维护一个空闲列表,在分配时候从列表中找到一块足够大的空间划分给对象使用
- 内存初始化。 给对象中的属性赋默认值
- 设置对象的一些信息。例如h这个对象是哪个类的实例(即所属类)、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息,这些信息都存放在对象的对象头中
- 对象初始化。根据传入的属性值给对象属性赋值
- 在线程 栈中新建对象引用 ,并指向堆中刚刚新建的对象实例。
JDK、JRE、JVM联系区别
- JDK:JDK是java的核心,包含了java运行环境,java工具和java基础类库。主要包含bin、include、lib、jre文件夹。JDK包含JRE,JRE包含JVM。
- JRE:JAVA运行环境,包含java标准实现以及java核心类库。JRE是一个运行环境而不是开发环境,所以不包含任何开发工具。
- JVM:JAVA虚拟机,java运行环境。它是一个虚构出来的计算机,java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。 这就是Java的能够“一次编译,到处运行”的原因。
联系:
JVM无法单独搞定class执行,解释class需要java类库的帮助。在JDK目录下有lib和bin,可以认为bin就是jvm,lib就是JVM所需要的类库,JVM和lib合起来就是JRE。总体来说,我们使用了jdk开发出了java程序之后,通过编译程序将程序编译为java字节码,在jre上运行这些字节码,jvm解析这些字节码,映射到CPU指令集或OS的系统调用。
区别
- JDK和JRE区别:在bin文件夹下会发现,JDK有javac.exe而JRE里面没有,javac指令是用来将java文件编译成class文件的,这是开发者需要的,而用户(只需要运行的人)是不需要的。JDK还有jar.exe, javadoc.exe等等用于开发的可执行指令文件。这也证实了一个是开发环境,一个是运行环境。
- JRE和JVM区别:JVM并不代表就可以执行class了,JVM执行.class还需要JRE下的lib类库的支持,尤其是rt.jar。
Java 中会存在内存泄漏吗,请简单描述
长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景,通俗地说,就是程序员可能创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收的,这就是java中可能出现内存泄露的情况,例如,缓存系统,我们加载了一个对象放在缓存中(例如放在一个全局map对象中),然后一直不再使用它,这个对象一直被缓存引用,但却不再被使用。
深拷贝和浅拷贝
浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。
深复制:在计算机中开辟一块新的内存地址用于存放复制的对象。