JVM常见知识点

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() 方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不推荐大家使用,建议遗忘它。

方法区主要回收的是无用的类,那么如何判断一个类是无用的类呢?

类需要同时满足下面3个条件才能算是 “无用的类” :

  • 该类所有的对象实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
  • 加载该类的 ClassLoader 已经被回收。
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

java的类加载机制?

JDK自带有三个类加载器:bootstrap ClassLoaderExtClassLoaderAppClassLoader

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:大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。JVM参数 -XX:PretenureSizeThreshold 可以设置大对象的大小,如果对象超过设置大小会直接进入老年代,不会进入年轻代,这个参数只在 Serial 和ParNew两个收集器下有效。比如设置JVM参数:-XX:PretenureSizeThreshold=1000000 (单位是字节) -XX:+UseSerialGC

2:长期存活的对象进入老年代。在堆中分配内存的对象,其内存布局的对象头中(Header)包含了 GC 分代年 龄标记信息。如果对象在 eden 区出生,那么它的 GC 分代年龄会初始值为 1,每熬过一次 Minor GC 而不被回收,这个值就会增  加 1 岁。当它的年龄到达一定的数值时,就会晋升到老年代中,可以通过参数-XX:MaxTenuringThreshold设置年龄阀值(默认是 15 岁)

3:当 Survivor 空间中相同年龄所有对象的大小总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,而不需要达到默认的分代年龄。-XX:TargetSurvivorRatio可以指定百分比,默认百分之50

默认情况下伊甸园区、幸存区、新生代、老年代区各占多少

在默认的情况下,新生代占三分之一、老年代占三分之二。而伊甸园区占新生代的十分之八,幸存区中分为幸存区from和幸存区to各占十分之一。JVM默认有这个参数-XX:+UseAdaptiveSizePolicy(默认开启),会导致这个8:1:1比例自动变化,如果不想这个比例有变化可以设置参数-XX:-UseAdaptiveSizePolicy

老年代空间分配担保机制

年轻代每次minor gc之前JVM都会计算下老年代剩余可用空间

如果这个可用空间小于年轻代里现有的所有对象大小之和(包括垃圾对象)

就会看一个“-XX:-HandlePromotionFailure”(jdk1.8默认就设置了)的参数是否设置了

如果有这个参数,就会看看老年代的可用内存大小,是否大于之前每一次minor gc后进入老年代的对象的平均大小。

如果上一步结果是小于或者之前说的参数没有设置,那么就会触发一次Full gc,对老年代和年轻代一起回收一次垃圾,如果回收完还是没有足够空间存放新的对象就会发生"OOM"

当然,如果minor gc之后剩余存活的需要挪动到老年代的对象大小还是大于老年代可用空间,那么也会触发full gc,full gc完之后如果还是没有空间放minor gc之后的存活对象,则也会发生“OOM”

指针压缩

什么是java对象的指针压缩?

  • 1.jdk1.6 update14开始,在64bit操作系统中,JVM支持指针压缩
  • 2.jvm配置参数:UseCompressedOops,compressed--压缩、oop(ordinary object pointer)--对象指针
  • 3.启用指针压缩:-XX:+UseCompressedOops(默认开启),禁止指针压缩:-XX:-UseCompressedOops

为什么要进行指针压缩?

  • 1.在64位平台的HotSpot中使用32位指针(实际存储用64位),内存使用会多出1.5倍左右,使用较大指针在主内存和缓存之间移动数据,占用较大宽带,同时GC也会承受较大压力
  • 2.为了减少64位平台下内存的消耗,启用指针压缩功能
  • 3.在jvm中,32位地址最大支持4G内存(2的32次方),可以通过对对象指针的存入堆内存时压缩编码、取出到cpu寄存器后解码方式进行优化(对象指针在堆中是32位,在寄存器中是35位,2的35次方=32G),使得jvm只用32位地址就可以支持更大的内存配置(小于等于32G)
  • 4.堆内存小于4G时,不需要启用指针压缩,jvm会直接去除高32位地址,即使用低虚拟地址空间
  • 5.堆内存大于32G时,压缩指针会失效,会强制使用64位(即8字节)来对java对象寻址,这就会出现1的问题,所以堆内存不要大于32G为好

垃圾回收中的安全区域以及安全点是什么?

安全点 就是指代码中一些特定的位置,当线程运行到这些位置时它的状态是确定的,这样JVM就可以安全的进行一些操作,比
如GC等,所以GC不是想什么时候做就立即触发的,是需要等待所有线程运行到安全点后才能触发。
这些特定的安全点位置主要有以下几种:
  • 1. 方法返回之前
  • 2. 调用某个方法之后
  • 3. 抛出异常的位置
  • 4. 循环的末尾
大体实现思想是当垃圾收集需要中断线程的时候, 不直接对线程操作, 仅仅简单地设置一个标志位, 各个线程执行过程
时会不停地主动去轮询这个标志, 一旦发现中断标志为真时就自己在最近的安全点上主动中断挂起。 轮询标志的地方和
安全点是重合的。

Safe Point(安全点) 是对正在执行的线程设定的。
如果一个线程处于 Sleep 或中断状态,它就不能响应 JVM 的中断请求,再运行到 Safe Point 上。
因此 JVM 引入了 Safe Region。
Safe Region(安全区域) 是指在一段代码片段中, 引用关系不会发生变化 。在这个区域内的任意地方开始 GC 都是安全的,例如sleep了几秒甚至几十秒,这段时间内引用不会发生变化,正常gc不会产生影响。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值