C语言特点,相当于是一个手动挡的汽车,挂挡比较麻烦,但是能做到更精确的控制
Java语言,相当于是一个自动挡的汽车,开起来更方便,不能像手动车那样精准控制、
内存管理:
C语言中的内存,申请之后,需要手动释放(free/delete)一旦忘记释放,就容易导致“内存泄漏”
Java中的内存,申请之后,不需要手动释放(GC)
没有垃圾回收,需要程序主动归还内存(取决于用户的素质),有了垃圾回收,相当于有了一个专门负责的人
内存申请的时机很好确定,但是释放时机是不好确定的。
比如从一个地方借到一个桌子,你可能要用来举办活动、上课。。。等等,你不确定最后那个桌子是否还需要用,就容易忘记还要还桌子。有一个GC就是相当于有一个人专门管理回收。
引入GC会有额外代价
1、需要消耗的资源
2、GC回收内存没有手动释放更及时,用完立刻手动释放,GC还要兼顾到其他资源。
3、GC会影响到正常的程序工作(影响到程序的执行效率STW(Stop the world)问题)
堆
主要回收
方法区(类对象)
GC需要回收方法区的内存,但是方法区空间小,数据失去作用的概率低
栈
不需要回收.栈上的内存何时释放时机是明确的. (线程结束,栈上的内存就全部被释放了,某个栈帧销毁(对应方法执行完就销毁),也会导致对应的局部变量被释放)
程序计数器
只是存了一个地址,不需要回收、
内存的单位是"字节",回收内存就而是按照对象的方式来回收.
每个对象,都持有了一定的内存.释放对应的对象,也就回收了对应的内存.
就类似于有人租书,书店老板需要一个人去收书拿回来,老板就会提供一份租书的名单,这个名单上面就是需要被回收,收书的人按照名单进行回收。
书店老板的判定依据:这本书的租期是否到了.
JVM中的判定依据:这个对象是否会被继续使用.
1、标记:找出这个对象是否需要回收(判段对象的生死)
a)引用计数法(Java中没有,python、php中有)
记录当前这个对象是否有引用指向(有几个引用)
每个对象都专门分配一个计数器变量. 有新的引用指向该对象,引用计数+ 1,有旧的引用指向别的对象了,就引用计数- 1
当引用为0表示该对象没有引用了. (没有引用也就无法在代码中被使用)于是这个对象就可以被回收了
b)可达性分析(Java中存在)
遍历这个对象关系的图(有向图),如果某个对象可以被遍历到,就不是垃圾(可达的).如果无法被遍历到,就是不可达.
在此用树举例子
class Node{
public int val;
public Node left;
public Node right;
public Node(int val) {
this.val = val;
}
}
public class Test3 {
public static Node build(){
Node a = new Node(1);
Node b = new Node(2);
Node c = new Node(3);
Node d = new Node(4);
Node e = new Node(5);
Node f = new Node(6);
Node g = new Node(7);
a.left = b;
a.right = c;
b.left = d;
b.right = e;
e.left = g;
c.right = f;
return a;
}
public static void main(String[] args) {
Node root = build();
root.right=null;
}
}
可达性遍历
遍历的起点不是一个而是很多起点.
把每个起点都要进行往下遍历.
以下三种起点统称为GCRoot
1.针对每个线程的每个栈帧的局部变量表(线程有很多,每个线程栈帧也有很多,每个栈帧中也会有多个变量)
2.常量池中引用的对象
3.方法区中静态变量引用的对象.
c)针对方法区对象的回收(类对象)
规则(同时具有三个条件认为该类对象就可以被回收,都是根据可达性分析)
1)该类的所有实例都已经被回收了
2)加载该类的ClassLoader也已经被回收了
3)该类对象没有在代码中被使用了(包括各种静态成员、包括反射)
按照对象的年龄(不是直接使用时间来记录,而是使用对象活过GC轮次来记录的.(GC是按照-定周期来运行)),把堆内存分成新生代老年代