深入理解JVM(三)类的使用方式、助记符、四种常用引用级别及软引用的应用

类的使用方式

类的初始化:JVM只会在首次主动使用一个类或接口时,初始化它们。

主动使用

主动使用有以下方式

1. 使用new创建对象

package init;

public class Test1 {
    static{
        System.out.println("Test1……");
    }
    public static void main(String[] args) {
        new Test1();

    }
}

执行结果:
Test1……

2. 使用(包括取值和赋值)类的静态属性(成员变量、方法)

package init;
class MyTest{
    static int a = 12;
    static {
        System.out.println("MyTest……");
    }
    static void method(){
        System.out.println("method……");
    }

}
public class Test3 {
    public static void main(String[] args) {
        System.out.println(MyTest.a);
    }
}


执行结果:
MyTest……
12

package init;
class MyTest{
    static int a = 12;
    static {
        System.out.println("MyTest……");
    }
    static void method(){
        System.out.println("method……");
    }

}
public class Test3 {
    public static void main(String[] args) {
        MyTest.method();
    }
}

执行结果:
MyTest……
method……

注意:

  1. 使用static和final同时修饰的变量是不初始化类。如下
package init;

import java.util.Random;

class Test{

    public static final int b = 12;
    public static final int c = new Random().nextInt();
    static {
        System.out.println("Test……");
    }
}

public class Test2 {

    public static void main(String[] args) {
        System.out.println(Test.b);//只调用Test.b 结果为12
    }
}

  1. 但是如果常量的值是一个随机值,则常量所在类会被初始化。这是因为c是随机数生成,是要进行计算的,而在类的加载和链接阶段是无法对其进行计算的,所以得进行初始化才能得到准确的值。
package init;

import java.util.Random;

class Test{

    public static final int b = 12;
    public static final int c = new Random().nextInt();
    static {
        System.out.println("Test……");
    }
}

public class Test2 {

    public static void main(String[] args) {
        System.out.println(Test.c);
    }
}

执行结果:
Test……
-673947267

可以查看下面Test2的字节码文件,更容易帮助理解。可以发现Test.c是在 Test2的main的方法中的。并且此时的值还未确定下来。而是在进行初始化才能得到准确的值

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package init;

public class Test2 {
    public Test2() {
    }

    public static void main(String[] args) {
        System.out.println(Test.c);
    }
}

3.使用Class.forName()执行反射时使用的类

package init;
class B{
    static {
        System.out.println("B……");
    }
}
public class A {
    public static void main(String[] args) throws ClassNotFoundException {
        Class.forName("init.B");
    }
}

执行结果:
B……

4.初始化一个子类,该子类的父类也会被初始化

Father.java

package init;

public class Father {
    static {
        System.out.println("Father……");
    }
}

Son.java

package init;

public class Son extends  Father{
    public static void main(String[] args) {
        new Son();
    }
}

执行结果:
Father……

5.动态语言在执行时所涉及的类 也会 被 初始化(动态代理)

被动使用

除了主动以外,其他都是被动使用

package init;
class BD
{
    static {
        System.out.println("BD...");
    }
}

public class BeiDong {
    public static void main(String[] args) {
        BD[] bds = new BD[3];

    }
}

以上代码,不属于主动使用类,因此不会被初始化。

主动使用中静态成员的问题

package mystatic;

class Father{
    static int i = 12;
    static {
        System.out.println("Father……");
    }

}
class Son extends Father{
    static {
        System.out.println("Son……");
    }
}
public class Test {
    public static void main(String[] args) {
        System.out.println( Son.i );
    }
}

执行结果:
Father……
12
因为Son中对i没有定义,i是Father的静态变量。所以不会对Son进行类的初始化。

package mystatic;


class Father{
    //static int i = 12;
    static int i = (int) Math.random()*1000;
    static {
        System.out.println("Father……");
    }

}
class Son extends Father{
    static {
        System.out.println("Son……");
    }
}
public class Test {
    public static void main(String[] args) {
        System.out.println( Son.i );
    }
}

执行结果:
Father……
0
static int i = (int) Math.random()*1000;没有加final字段,效果同上。

package mystatic;


class Father{
    //static int i = 12;
    //static int i = (int) Math.random()*1000;
    static final int i = (int) Math.random() * 1000;
    static {
        System.out.println("Father……");
    }

}
class Son extends Father{
    static {
        System.out.println("Son……");
    }
}
public class Test {
    public static void main(String[] args) {

        System.out.println( Son.i );
    }
}

执行结果:
Father……
0

常量的值是一个随机值,则常量所在类会被初始化。

package mystatic;


class Father{
    //static int i = 12;
    //static int i = (int) Math.random()*1000;
    static final int i = (int) Math.random() * 1000;
    static {
        System.out.println("Father……");
    }

}
class Son extends Father{
    static int i = 102;
    static {
        System.out.println("Son……");
    }
}
public class Test {
    public static void main(String[] args) {

        System.out.println( Son.i );
    }
}

执行结果:
Father……
Son……
102

i是son的静态变量所以会 初始化son。而father是son的父类。所以father也会被初始化。

助记符

反编译,进入到对应class目录中。使用javap命令进行反编译:

javap -c class文件名

一些符号:

aload_0: 装载一个引用类型
invokespecial:init。private,super.method() :< init >存放的是初始化代码的位置
getsattic:获取静态成员
bipush: 数据范围是-128-127之间(8位带符号的整数),放到栈顶
sipush: 数据范围大于127(16位带符号的整数),放到栈顶
无论数据类型定义的是int、short等,只要数据范围是-128-127那么对应反编译指令都是bipush,否则是sipush
注意:
-1 到5不是 bipush。而分别是 iconst_m1 、iconst_0 iconst_1 iconst2…… iconst_5

ldc: int float String常量,放到栈顶
ldc2_w: long double 常量,放到栈顶

JVM四种常用引用级别

GC会回收哪些对象呢? 这就要根据引用级别来判断了。
JMV中存在四种引用级别,其强弱关系为:强引用 > 软引用 > 弱引用 > 虚引用

各种引用的出处:
强引用:用new
软引用、弱引用、虚引用:Reference

强引用

Object obj = new Object();

强引用对象new Object在以下情况失效

  1. 生命周期结束(作用域失效)
public void method(){
Object obj = new Object();
}//当执行完method方法后,强引用所指向的 引用对象 new Object() 就会等待GC的回收
  1. 引用被置为null,则引用对象等待GC的回收
obj = null;//此时,没有任何引用指向引用对象new Object() ,则引用对象会 等待GC的回收

除了上述两种情况,其他情况即使JVM OutOfMemory也不会回收强引用对象。

软引用

根据JVM的内存情况:如果内存充足,GC不会随便地回收软引用对象;如果JVM内存不足,则GC就会主动回收引用对象。

package ref;

import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;

class SoftObject{

}
public class SoftRefDemo {

    public static void main(String[] args) throws InterruptedException {
        //装饰模式

        SoftReference<SoftObject> softRef = new SoftReference<SoftObject>(new SoftObject());

        new Thread(()->{
           while(true){
               //线程休眠 让  softRef有机会同步
               try {
                   Thread.sleep(10);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               if( softRef.get()== null){// 指向指向SoftObject对象的软引用对象
                   System.out.println("软引用对象已经被回收");
                   System.exit(0);
               }
           }
        }).start();

        List<byte[]> list = new ArrayList<>();
        //不断添加数组使得JVM内存溢出
        while( true ){

            Thread.sleep(100);
            list.add(new byte[1024 * 1024]);
        }

    }
}

执行结果:
Exception in thread “main” 软引用对象已经被回收

弱引用

回收时机: 只要GC执行,就将弱引用对象进行回收。

package ref;

import java.lang.ref.WeakReference;

public class WeakReferenceDemo {
    public static void main(String[] args) throws Exception {

        WeakReference<Object> weakRef = new WeakReference<>(new Object());
        //weakRef->Object

        System.out.println( weakRef.get()==null   ? "已被回收":"没被回收"  );

        System.gc();//建议GC执行一次回收(存在概率)
        Thread.sleep(100);

        System.out.println( weakRef.get()==null   ? "已被回收":"没被回收"  );

    }
}

执行结果:
未被回收
已被回收

虚引用

虚引用对应类java.lang.ref.PhantomReference< T >
是否使用虚引用,和引用对象本身没有任何关系;无法通过虚引用来获取对象本身。
虚引用对象的get() 返回值是null。所以虚引用不会单独使用,一般和引用队列一起使用。

意义;
当gc回收一个对象时,如果gc发现此对象还有一个虚引用就会将虚引用放入引用队列中,当虚引用对象出队之后才去回收该对象。因此,我们可以使用虚引用+引用对象实现,对象呗gc回收之前
执行一些额外操作。

package ref;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

class MyObject {

}

public class PhantomReferenceDemo {


    public static void main(String[] args) throws Exception {
        MyObject obj = new MyObject();
        //引用队列
        ReferenceQueue queue = new ReferenceQueue();

        //虚引用+引用队列
        PhantomReference<MyObject> phantomRef = new PhantomReference<>(obj, queue);

        //让gc执行一次回收操作
        obj = null;
        System.gc();
        Thread.sleep(30);
        System.out.println("GC执行...");

        //GC-> 虚引用->入队->出队->     obj
        System.out.println(queue.poll());


    }
}


执行结果:
GC执行…
java.lang.ref.PhantomReference@10f87f48

特殊情况: 如果虚引用对象,重写了finalize(), 那么JVM会延迟虚引用入队的时间。

最终引用(了解即可)

在Java中存在Finalizer(final class Finalizer extends FinalReference)帮我们自动回收一些不需要的对象。jvm不能操作 直接内存(非jvm操作的内容)时,而恰好 此区域的内容 又忘了关闭,此时Finalizer就会将这些内存进行回收。

使用软引用实现缓存的淘汰策略

package ref;

import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;

class Item{

}
public class SoftDemo {
    static Map<String, SoftReference<Item>> caches = new HashMap<>();
    //set从db获取数据到缓存中
    void setCaches(String id, Item item){
        caches.put(id, new SoftReference<Item>(item));
    }
    
    //get从缓存中获取数据到java代码中
    Item getCaches(String id){
        SoftReference<Item> soft =  caches.get(id);
        //soft为空则表示被GC回收了 
        //否则直接调用soft.get() 获取对象本身
        return soft == null ? null : soft.get();
    }

    public static void main(String[] args) {

    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值