浅析OutofMemoryError与StackOverFlowError
前倾回顾----->>>
我们在初学java代码时,知道了java对异常的处理 方式,这样有助于增强程序的健壮性;
java中的异常
- 编译时异常 checked Exception
即编译期间就可以发现可能存在异常的情况比如IOException,ClassNotFoundException等
- 运行时异常running Exception
即在运行时才会发现的异常情况,比如NullpointerException,ClassCastException等
- error
error,其实它更像是错误,在本地代码层面无法被处理,error发生在虚拟机层面;
Exception与Error的区别:
1,都继承自Throwable类
public class Exception
extends Throwable
public class Error
extends Throwable
2,Exception可以被捕获,但是error不行;
下面重点说一下:
OutofMemoryError与StackOverFlowError
- OutofMemoryError
什么时候会发生OutofMemoryError?
首先我们来看下面的代码:
class OutMemoryDemo {
public void printInfo(String str) {
int parseInt = Integer.parseInt(str);
System.out.println(parseInt);
}
}
public class ReviewDemo {
public static void main(String[] args) {
OutMemoryDemo ood = new OutMemoryDemo();
ood.printInfo("123456");
}
}
假设为str开辟空间后堆内存已经满了,这时候又来了一个新对象但是现在无法为他开辟空间,于是垃圾收集器开始想办法收集一些垃圾如果收集的垃圾容量小于新对象的大小,同时jvm也无法扩展堆内存的容量时(一般堆内存大小是动态的但大小还是受限的)那么就会发生 OutOfMemoryError 错误;
简单来说就是----->>>
当堆空间满的时候,这时候来了新对象,又要开辟空间,发现这时候堆满了,无法开辟空间了,垃圾回收之后堆空间仍然不够;
此时我们应该考虑,为什么会发生内存溢出?
1,是堆内存真不够大吗?
2,有没有可能发生了内存泄漏(即某个/些没有被引用的对象一直没有被垃圾收集器回收 )
- StackOverFlowError
什么时候会发生栈溢出错误呢?
当请求的栈深度超出了jvm的最大栈深就会抛出StackOverFlowError;
比如 死循环,或者递归层数太多的调用;
举个很简单的例子,
在父类构造中new 一个子类对象
class OutMemoryDemo {
public OutMemoryDemo(){
new StackOverDEmo();
}
public void printInfo(String str) {
int parseInt = Integer.parseInt(str);
System.out.println(parseInt);
}
}
class StackOverDEmo extends OutMemoryDemo{
public StackOverDEmo(){
super();
}
}
public class ReviewDemo {
public static void main(String[] args) {
new StackOverDEmo();
}
}
随着java版本的升级,垃圾回收机制也在不断的改进与发展;
java中常见的垃圾回收机制
- 引用 - 计数算法
JVM 会为每个对象保留一个引用计数,当每次引用对象的时候,引用计数器的值就 + 1,当引用失效的时候,引用计数器的值就 - 1。而垃圾收集器只会回收引用计数器的值为 0 的情况这种方式无法解决对象之间相互引用的问题;
相互引用---->>
class G{
public H h;
public G() {
System.out.println("GGGG");
}
@Override
public String toString() {
return "G{GGGG}";
}
}
class H{
public G g;
public H() {
System.out.println("HHHH");
}
}
public class Gabbage {
public static void main(String[] args) {
G g= new G();
H h= new H();
g.h=h;
h.g=g;
WeakReference<G> weakReference= new WeakReference<>(g);
System.gc();
G g1 = weakReference.get();
System.out.println(g1.toString());
}
}
- 标记清除
即程序无法到达的每个对象都是垃圾,可以进行回收。
标记-清除收集具有如下几个阶段—>>在第一阶段中所有可回收的的内容都能够被垃圾收集器进行回收。如果一个对象被判定为是可以回收的对象,那么这个对象就被放在一个回收队列中(即移动到一个新空间中防止堆内存碎片化),并在稍后会由一个虚拟机自动建立的、低优先级的 finalizer 线程去执行它。
对象被创建并初始化,对象在运行时被使用,然后离开对象的作用域,对象会变成不可达并会被垃圾收集器回收;
java为我们提供了强软弱虚四种引用方式,其中强引用是不会被来及回收器回收的,剩下三种是符合垃圾回收器回收的引用对象;
软可达:软可达就是我们只能通过软引用才能访问的状态,软可达的对象是由 SoftReference 引用的对象,并且没有强引用的对象。软引用是用来描述一些还有用但是非必须的对象。垃圾收集器会尽可能长时间的保留软引用的对象,但是会在发生 OutOfMemoryError 之前,回收软引用的对象。如果回收完软引用的对象,内存还是不够分配的话,就会直接抛出 OutOfMemoryError。
软引用常常用于缓存;
弱可达:弱可达的对象是 WeakReference 引用的对象。垃圾收集器可以随时收集弱引用的对象,不会尝试保留软引用的对象。
幻象可达:幻象可达是由 PhantomReference 引用的对象,幻象可达就是没有强、软、弱引用进行关联,并且已经被 finalize 过了,只有幻象引用指向这个对象的时候。