GC如何判断对象可以被回收
判断是否为垃圾的方式有两种
- 引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收,
- 可达性分析法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GCRoots 没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。
java中使用的是可达性算法分析,为什么不适用引用计数法呢?
引用计数法,可能会出现 A 引用了 B , B 又引用了 A ,这时候就算他们都不再使用了,但因为相互
引用 计数器 =1 永远无法被回收。
那为什么python会使用引用计数法?
引用计数最大的好处是回收及时:一个对象的引用计数归零的那一刻即是它成为垃圾的那一刻,同时也是它被回收的那一刻。而这正式 mark-sweep 等 tracing GC 算法的劣势:一个对象成为垃圾之后,直到被下一轮 GC 清理掉之前,还要在内存中留存一段时间(floating garbage)。Python 的 GC 设计是,对于内部不包含指向其他对象的引用的对象(如字符串、数值类型等),采用引用计数,因为这些对象根本不可能产生循环引用。对于 List、Map 等可能产生循环引用的对象,则采用 mark-sweep。所以我的理解是,Python 的 GC 设计一定程度上综合了两类 GC 算法
GC Roots 的对象有:
虚拟机栈 ( 栈帧中的本地变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中 JNI( 即一般说的 Native 方法 ) 引用的对象
可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会。对象被系统宣告死亡至少要经历两次标记过程:第一次是经过可达性分析发现没有与GC Roots 相连接的引用链,第二次是在由虚拟机自动建立的Finalizer 队列中判断是否需要执行 finalize() 方法。
当对象变成 (GC Roots) 不可达时, GC 会判断该对象是否覆盖了 finalize 方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize 方法,将其放入 F-Queue 队列,由一低优先级线程执行该队列中对象的finalize 方法。执行 finalize 方法完毕后, GC 会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“ 复活 ”每个对象只能触发一次finalize() 方法由于finalize() 方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不推荐大家使用,建议遗忘它。
java的类加载机制?
JDK自带有三个类加载器:bootstrap ClassLoader、ExtClassLoader、AppClassLoader
BootStrapClassLoader 是 ExtClassLoader 的父类加载器,默认负责加载 %JAVA_HOME%lib 下的 jar 包和class文件。
ExtClassLoader 是 AppClassLoader 的父类加载器,负责加载 %JAVA_HOME%/lib/ext 文件夹下的 jar 包和 class类。
AppClassLoader 是自定义类加载器的父类,负责加载 classpath 下的类文件。系统类加载器,线程上下文加载器
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 1检查该类是否已经加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 2、有上级的话,委派上级 loadClass
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 3. 如果没有上级了(ExtClassLoader),则委派 BootstrapClassLoader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
long t1 = System.nanoTime();
// 4. 每一层找不到,调用 findClass 方法(每个类加载器自己扩展)来加载
c = findClass(name);
// 5、记录耗时
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
什么是STW
STW: Stop-The-World。是在垃圾回收算法执行过程当中,需要将JVM内存冻结的一种状态。在STW状态下,JAVA的所有线程都是停止执行的-GC线程除外,native方法可以执行,但是,不能与JVM交互。GC各种算法优化的重点,就是减少STW,同时这也是JVM调优的重点。
JVM中哪些内存是线程共享的,哪些是非共享的?
堆区和方法区是线程共享的,程序计数器、栈、本地方法栈是非共享的
对象一定是存放在堆中的吗?
不一定,jvm实际上对整个运行过程会有一个优化
分析对象的动态作用域,当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递给其他方法,称为方法逃逸。甚至还有可能被外部线程访问到,比如赋值给类变量或可以在其他线程中访问到的实例变量,称为线程逃逸。
如果能证明一个对象不会逃逸到方法或线程之外,也就是别的方法或线程无法通过任何途径访问到这个对象,就可以为这个变量进行一些高效的优化:如:栈上分配、同步消除、标量替换等。
而其中栈上分配:
如果确定一个对象不会逃逸出方法之外,那让这个对象在栈上分配内存将是一个不错的主意,对象所占用的内存空间就可以随栈帧出栈而销毁;
一个对象如何进入老年代?
主要有下面三种方式:大对象,长期存活的对象,动态对象年龄判定
1:大对象直接进入老年代。比如很长的字符串,或者很大的数组等,参数 -XX:PretenureSizeThreshold=10设置,超过这个参数设置的值就直接进入老年代
2:长期存活的对象进入老年代。在堆中分配内存的对象,其内存布局的对象头中(Header)包含了 GC 分代年 龄标记信息。如果对象在 eden 区出生,那么它的 GC 分代年龄会初始值为 1,每熬过一次 Minor GC 而不被回收,这个值就会增 加 1 岁。当它的年龄到达一定的数值时,就会晋升到老年代中,可以通过参数-XX:MaxTenuringThreshold设置年龄阀值(默认是 15 岁)
3:当 Survivor 空间中相同年龄所有对象的大小总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,而不需要达到默认的分代年龄。
默认情况下伊甸园区、幸存区、新生代、老年代区各占多少
在默认的情况下,新生代占三分之一、老年代占三分之二。而伊甸园区占新生代的八分之三,幸存区中分为幸存区from和幸存区to各占十分之一