java虚拟机

1、字节码的加载过程

在这里插入图片描述

1.1、加载过程


字节码文件通过类加载器,链接,初始化,完成加载

加载完成之后有组织的在内存里有
在这里插入图片描述
每个线程有一个PC Register程序计数器,然后就是栈区间,每个线程有一个栈内存,栈内存里又分栈帧,详见并发编程。
本地方法栈(native method stack),方法区(类的信息,常量,方法信息),堆区(最大的空间,创建的对象放在堆区)
在这里插入图片描述
class file ->JVM->DNA元数据模板(放在方法区)->通过构造方法,创造实例放在堆内存

执行引擎
分为解释器,即时编译器,辣鸡回收器
在这里插入图片描述

1.2、类的加载

在这里插入图片描述

1.3、类的链接

三个阶段,验证----准备----解析

链接的验证
是为了保证字节码文件语法上的正确性,以防止危害到JVM的安全
比如说java字节码文件都是以特定的标识开始的语法,查看字节码文件可以发现在这里插入图片描述
在这里插入图片描述
都是以cafebabe开始的

链接的准备阶段
在这里插入图片描述
为类变量附上初始值,Integer赋值为0,布尔类型赋值为false,在最后的类初始化完成真正的赋值
在这里插入图片描述
解析阶段

在这里插入图片描述

1.4、初始化阶段在这里插入图片描述

构造器方法和构造方法不一样,构造器方法在每一个类中都有,他会显示的给类变量和静态代码块里的变量赋值,可以查看字节码文件解释后的clinit方法
在这里插入图片描述在这里插入图片描述
类的构造方法就是方法区里面的init,在这里插入图片描述
init时会首先调用父类的构造方法,Object.
在类的clinit方法要被多个线程访问时,只能同步访问,加锁了,只能一个线程访问一次初始化

类加载器的分类
引导类加载器、扩展类加载器、系统类加载器
从与ClassLoader的关系来说分为两类
引导类加载器和自定义加载器
因为扩展加载器和系统加载器都继承ClassLoader
在这里插入图片描述

public class ClassLoaderTest {
    public static void main(String[] args) {
        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //获取其上层  扩展类加载器
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@610455d6

        //获取其上层 获取不到引导类加载器
        ClassLoader bootStrapClassLoader = extClassLoader.getParent();
        System.out.println(bootStrapClassLoader);//null

        //对于用户自定义类来说:使用系统类加载器进行加载
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //String 类使用引导类加载器进行加载的  -->java核心类库都是使用引导类加载器加载的
        ClassLoader classLoader1 = String.class.getClassLoader();
        System.out.println(classLoader1);//null

    }
}


"C:\Program Files\Javaa\jdk1.8.0_131\bin\java.exe" 
sun.misc.Launcher$ExtClassLoader@1540e19d
null
sun.misc.Launcher$AppClassLoader@18b4aac2
null

Process finished with exit code 0



在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

import sun.security.ec.CurveDB;

import java.net.URL;
import java.security.Provider;

/**
 * 虚拟机自带加载器
 */
public class ClassLoaderTest1 {
    public static void main(String[] args) {
        System.out.println("********启动类加载器*********");
        //获取引导类加载器可以加载的文件路径
        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        //获取BootStrapClassLoader能够加载的api路径
        for (URL e:urls){
            System.out.println(e.toExternalForm());
        }

        //从上面的路径中随意选择一个类 看看他的类加载器是什么
        //Provider位于 /jdk1.8.0_171.jdk/Contents/Home/jre/lib/jsse.jar 下,引导类加载器加载它
        ClassLoader classLoader = Provider.class.getClassLoader();
        System.out.println(classLoader);//null

        System.out.println("********拓展类加载器********");
        //获取扩展加载器可以加载的文件路径
        String extDirs = System.getProperty("java.ext.dirs");
        for (String path : extDirs.split(";")){
            System.out.println(path);
        }
        //从上面的路径中随意选择一个类 看看他的类加载器是什么:拓展类加载器
        ClassLoader classLoader1 = CurveDB.class.getClassLoader();
        System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@4dc63996
    }
}

classLoader的继承关系
在这里插入图片描述
获得类加载器的几种方式,通过线程获取,通过大的clazz实例获取,获取系统的classLoader
在这里插入图片描述

双亲委派机制

package com.dsh.jvm.classloader;

public class StringTest {
    public static void main(String[] args) {
        String s = new String();
        System.out.println("hello jvm");
    }
}

问题,假如我们手动建立一个String类,那么类加载器会不会加载我们手动建的类
不会,因为有双亲委派机制,不会马上加载而是委托给父类加载器加载,如果父类加载器还有父类加载器则继续向上委托
。这样就避免了由appClassLoaderz加载我们自己的String类,而是由引导类加载器加载java.lang.String 这样可以防止别人的攻击。如果父类无法加载,那么就由子类加载器加载
在这里插入图片描述
加载JDBC.jar的过程
在这里插入图片描述
由于SPI类型类属于rt包,所以可以由引导类加载器加载,但是里面的一些接口属于第三方的类,于是反向委托
双亲委派机机制的优势
在这里插入图片描述
沙箱安全机制
自定义的String类里面没有main方法,从而保护了java核心代码在这里插入图片描述

两个类相等需要他们的包类名和类加载器完全一样,才可以判断相等

如果一个类由用户自定义加载器加载,那么当他被保存在方法区会带上他的加载类的信息

类的主动使用和被动使用
区别在于,加载过程中的最后一步初始化是否执行,因为初始化需要执行clinit
主动使用的加载方式
在这里插入图片描述
运行时数据区与线程进程的关系
一个JVM对应一个进程,一个进程只有一组红色区,而一个进程对应多个线程,一个线程有一个灰区,线程共用红区
在这里插入图片描述

阿里手册的运行时数据区
在这里插入图片描述

程序计数器
在这里插入图片描述
PC寄存器存储指向栈帧某行指令的地址,执行引擎通过PC寄存器里面的地址去栈帧里取指令,如果执行的是本地方法,那么地址为undefined,没有垃圾回收也没有OOM
红色框里即为PCRegister里面的地址
在这里插入图片描述
为什么需要PC寄存器记录执行指令的地址
因为线程会不断地切换,下次切回来需要知道执行到哪儿了,保证中断和恢复

运行时数据区的栈和堆
栈解决程序运行时得问题,堆解决的是数据的存储 问题
虚拟机栈
一个线程拥有一个栈,一个方法有一个栈帧

public class StackTest {

    public static void main(String[] args) {
        StackTest test = new StackTest();
        test.methodA();
    }

    public void methodA(){
        int i = 10;
        int j = 20;
        methodB();
    }

    public void methodB(){
        int k = 30;
        int m = 40;
    }

}


虚拟机栈的基本概述
在这里插入图片描述
栈存在OOM但是不存在垃圾回收,可以给栈设置固定的存储大小,但是这个当线程申请比固定大小更大的存储空间的时候,会抛出一个StackOverFlowError,如果不固定大小,动态扩展,当内存溢出的时候就会抛出OOM。
设置固定的栈大小
在这里插入图片描述
在这里插入图片描述
如果有返回值,那么会把返回值传给上一个栈帧,利用dreturn,和ireturn(int return)。如果有异常,也会往下面的栈帧一个个抛出直到有方法处理他的异常,假如没有处理那么这个线程就会非正常结束
在这里插入图片描述在这里插入图片描述
其中方法返回地址,动态链接,附加信息,被称为帧数据

1、局部变量表
在这里插入图片描述

package com.dsh.jvm.runtimedata;

/**
 * 局部变量表
 */
public class LocalVariablesTest {

    private int count = 1;

    public static void testStatic(){
        //编译错误,因为this变量不存在与当前方法的局部变量表中!!!
//        System.out.println(this.count);
    }

    public static void main(String[] args) {
        LocalVariablesTest test = new LocalVariablesTest();
        int num = 10;
        test.test1();
    }

    private void test1() {
        int i = 20;
        System.out.println("test1");
        this.count = 2;
        test2();
    }

    private void test2() {
        int a = 0;
        {
            int b = 0;
            b = a+1;
        }
        //变量c使用之前以及经销毁的变量b占据的slot位置
        int c = a+1;
    }

}

反编译之后每个方法有两个和局部变量有关系
在这里插入图片描述
在这里插入图片描述
main方法里有三个参数,一个是形参String,一个是对象的引用,一个是num
下面用jclasslib查看在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
指令位置是变量作用的开始位置 ,可以去代码行号和指令对应关系里面去找作用范围
在这里插入图片描述在这里插入图片描述在这里插入图片描述
构造器和实例方法(非静态方法)能使用this的原因是在index0处存储了this,double,index占据两个位置在这里插入图片描述
关于变量槽的重复利用

jclasslib查看后
在这里插入图片描述
发现b的作用域从4+4,加起来没有13,说明没有在整个方法作用,当其过了作用域范围这个时候又要定义c了,于是就发生了slot的可重用
在这里插入图片描述
类变量和局部变量的区别
类变量由准备阶段和初始化阶段完成赋值,而局部变量没有默认赋值,必须要显示赋值在这里插入图片描述在这里插入图片描述
操作数栈
栈:可以使用数组和链表实现
在这里插入图片描述在这里插入图片描述在这里插入图片描述
bipush(由于数值大小为byte,假如800就是ipush)往操作数栈压入byte值,istore_1操作数出栈,把他存入局部变量表,bipush压入操作数栈,然后istore_2存入局部变量表,iload_1取出局部变量表的1位置,入栈操作数栈,iload_2同理,iadd,出栈,相加入操作数栈,出栈存储在局部变量表。
执行引擎翻译指令变成机器指令,操纵操作数栈
在这里插入图片描述

    public void testAddOperation(){
        //byte short char boolean flaot: 都以int型来保存
        byte i = 15;
        int j = 8;
        int k = i+j;
    }

当一个方法返回值的时候,会把返回值压入当前栈aload_0

动态链接
运行时常量池是
在加载过程中,会把class二进制流中的静态数据(全局变量,其他方法),加载到方法区的运行时常量池中

在这里插入图片描述
从字节码的角度
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

方法的调用
分为动态链接和静态链接,这个和他绑定的时间有关系,假如在编译期确定的是静态链接和早期绑定,假如是编译期不能确定的是晚期绑定,绑定就是一个字段、一个类、一个方法在符号引用被替换成直接引用的过程,仅仅发生一次
晚期绑定,比如在这里插入图片描述
Animal可能是猫,或者其他继承了他的子类,所以是晚期绑定在这里插入图片描述在这里插入图片描述在这里插入图片描述
虚函数,调用当前类或者子类(类的多肽性质)函数方法
在这里插入图片描述在这里插入图片描述在这里插入图片描述

方法重写的本质和虚方法表在这里插入图片描述
但是每次依次向上找父类很浪费性能,所以为虚方法建立虚方法表
在这里插入图片描述
实现和重写的方法指向自己,其他的根据操作数栈地址的实际类型找到谁就是谁,不用一层一层找了。
在这里插入图片描述

方法返回地址
当栈帧正常结束是会返回一个返回地址,地址值是当前栈帧保存的PCRegister的地址在这里插入图片描述
非正常结束会有一个异常表
在这里插入图片描述
表示从第四行到第八行假如出现异常那么直接跳到11行处理,针对IOException

本地方法
在这里插入图片描述

为什么需要用本地方法

  • 用来与java外环境语言交互
  • 用来与操作系统交互
  • sun.java 解释器 底层采用了c的实现

在这里插入图片描述
在这里插入图片描述

2、堆

在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述
方法执行完出栈,在堆里的对象并不会立刻被回收,因为会增加gc的频率,影响用户线程
在这里插入图片描述
上图是创建对象,堆空间创建实例变量在这里插入图片描述在这里插入图片描述在这里插入图片描述
逻辑上包括元数据区,但是设置堆空间大小并没有包括它

设置堆空间大小和获得堆空间大小的代码和设置方法

public class XmsXmx {
    public static void main(String[] args) {
        long Xms=Runtime.getRuntime().totalMemory()/1024/1024;
        long Xmx=Runtime.getRuntime().maxMemory()/1024/1024;
        System.out.println("开始内存:"+Xms*64);
        System.out.println("最大内存:"+Xmx*4);
    }
}

设置方法
在这里插入图片描述在这里插入图片描述
打印出来的内存大小会小于实际设置的内存大小,因为,新生代的survivor0和survivor1区只能存在一个,因此计算的新生区少了一个survivor区在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述

在这里插入图片描述在这里插入图片描述

当-Xmn和-XX:NewRatio都被设置了的时候,以-Xmn为准
在这里插入图片描述在这里插入图片描述
enden园区满了之后会进行Y/Minor gc,然后会把还要用的对象放入survivor,等eden再满的时候,会重复上次操作,但是会把还要用的对象包括之前的survivor放在一个新的survivor,如此反复知道age的数字达到了16,就会发生promotion上升到Old。。
假如survivor满了,会对survivor进行GC,并且直接promotion

在这里插入图片描述

特殊情况
在这里插入图片描述
代码举例

import java.util.ArrayList;
import java.util.Random;

public class HeapOOM {
    byte[] buffer=new byte[new Random().nextInt(1034*1024)];

    public static void main(String[] args) {
        ArrayList<HeapOOM> arrayList=new ArrayList<>();
        while (true){
            arrayList.add(new HeapOOM());
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在这里插入图片描述
STW gc的时候会暂停用户线程
在这里插入图片描述
在这里插入图片描述
问题:在enden和survivor放to区放对象的时候是谁先谁后
在这里插入图片描述
测试用-XX:PrintGCDetails 监测辣鸡回收

import java.util.ArrayList;
import java.util.List;

public class _77Gc {
    public static void main(String[] args) {
        int i=0;
        try {
            List<String> strings=new ArrayList<>();
            String a="liuxiangniubi";
            while (true){
                a=a+a;
                strings.add(a);
                i+=1;
            }
        }catch (Throwable e){
            System.out.println(i);
            e.printStackTrace();
        }
    }
}

在这里插入图片描述在这里插入图片描述在这里插入图片描述
大对象eden区容纳不下,直接放进老年区。在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述在这里插入图片描述
打开逃逸分析和关闭逃逸分析的区别-XX:+DoEscapeAnalysis

package com.atguigu.java2;

/**
 * 栈上分配测试
 * -Xmx1G -Xms1G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails
 * @author shkstart  shkstart@126.com
 * @create 2020  10:31
 */
public class StackAllocation {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();

        for (int i = 0; i < 10000000; i++) {
            alloc();
        }
        // 查看执行时间
        long end = System.currentTimeMillis();
        System.out.println("花费的时间为: " + (end - start) + " ms");
        // 为了方便查看堆内存中对象个数,线程sleep
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
    }

    private static void alloc() {
        User user = new User();//未发生逃逸
    }

    static class User {

    }
}

关闭的时候
在这里插入图片描述
在这里插入图片描述
当减小内存分配,关闭逃逸会进行垃圾回收
在这里插入图片描述
开启逃逸则不会进行辣鸡回收
在这里插入图片描述
充分说明了在栈上进行对象的内存分配的好处,可以减少辣鸡回收

当JIT发现锁的对象没有发生逃逸不会被其他线程访问的时候,会进行自动的锁消除

在这里插入图片描述
标量替换 -XX:-EliminateAllocations
在这里插入图片描述

package com.atguigu.java2;

/**
 * 标量替换测试
 *  -Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations
 * @author shkstart  shkstart@126.com
 * @create 2020  12:01
 */
public class ScalarReplace {
    public static class User {
        public int id;
        public String name;
    }

    public static void alloc() {
        User u = new User();//未发生逃逸
        u.id = 5;
        u.name = "www.atguigu.com";
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            alloc();
        }
        long end = System.currentTimeMillis();
        System.out.println("花费的时间为: " + (end - start) + " ms");
    }
}

/*
class Customer{
    String name;
    int id;
    Account acct;

}

class Account{
    double balance;
}


 */

在这里插入图片描述在这里插入图片描述

3、方法区

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

package com.atguigu.java1;

/**
 * 结论:
 * 静态引用对应的对象实体始终都存在堆空间
 *
 * jdk7:
 * -Xms200m -Xmx200m -XX:PermSize=300m -XX:MaxPermSize=300m -XX:+PrintGCDetails
 * jdk 8:
 * -Xms200m -Xmx200m -XX:MetaspaceSize=300m -XX:MaxMetaspaceSize=300m -XX:+PrintGCDetails
 * @author shkstart  shkstart@126.com
 * @create 2020  21:20
 */
public class StaticFieldTest {
    private static byte[] arr = new byte[1024 * 1024 * 100];//100MB

    public static void main(String[] args) {
        System.out.println(StaticFieldTest.arr);

//        try {
//            Thread.sleep(1000000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
    }
}

在这里插入图片描述在这里插入图片描述
在方法区里面保留了类加载器的信息(记录了加载过哪些类)
下面是字节码反编译要放加载进方法区里面的内容
在这里插入图片描述在这里插入图片描述
在这里插入图片描述在这里插入图片描述
一般的变量,准备的时候隐士赋值,在初始化的时候显示赋值, static final变量在编译的时候就赋值了
初始化的执行顺序

父类静态代码块
子类静态代码块
父类代码块
父类构造方法
子类代码块
子类构造方法!

理论支持:
Java中的静态变量和静态代码块是在类加载的时候就执行的,实例化对象时,先声明并实例化变量再执行构造函数。如果子类继承父类,则先执行父类的静态变量和静态代码块,再执行子类的静态变量和静态代码块。同样,接着再执行父类和子类非静态代码块和构造函数。
注意:(静态)变量和(静态)代码块的也是有执行顺序的,与代码书写的顺序一致。在(静态)代码块中可以使用(静态)变量,但是被使用的(静态)变量必须在(静态)代码块前面声明。

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
编译后字节码里的常量池会在加载完存储在方法区,但是此时类的引用并不是字面量的符号地址,而是真正类型信息的地址
在这里插入图片描述

在Jrockit虚拟机没有永久代,因此整合完就没有永久代了,多了元空间

在这里插入图片描述
永久代是属于虚拟机内存的,因此要给永久代设置内存,当类加载过多,会导致永久代容易产生OOM
在这里插入图片描述在这里插入图片描述
字符串常量应该经常被回收,但是存储在永久代(java1.7之前)他只发生在fullGC才会被回收,因此1.7把字符串常量池划分到了堆区。在1.8去掉永久代/划分出了元空间,继续把字符串常量池存放在了堆区。
在这里插入图片描述
类变量被包括在Class实例里,如上图所示,而class实例被存储在堆里
在这里插入图片描述在这里插入图片描述在这里插入图片描述
对于类的垃圾回收比较严格,三点

  • Class实例未被使用
  • new得对象全部被回收
  • 他的加载器全部被回收在这里插入图片描述在这里插入图片描述在这里插入图片描述
    创建类的六个步骤

在这里插入图片描述

  • 为对象分配内存,会根据内存里面域的类型,为对象分配空间,分配空间由两种方式具体方式还是要取决于JVM采用了什么样的垃圾收集器。一种内存分配方式是在规整的空间,放入对象,叫做指针碰撞法。还有一种是对于内存空间分配不规整,这个时候会维护一个表,表里记录者零散的空闲空间的的大小,会从表里找出一块装的下对象的空间,并更新表 。
  • 初始化分配到的空间。初始化域变量的过程为:隐士初始化->显示赋值->静态代码块(和显示赋值谁先谁后)->构造器->getset方法
  • 在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
    访问定位
    在这里插入图片描述
    句柄访问
    在这里插入图片描述
    直接指针访问
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
    利用反射达到OOM直接内存概述

执行引擎在这里插入图片描述

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
加粗样式
在方法区可以存储JIT代码缓存,这样以后执行热点代码就可以不用去编译了,可以直接运行机器指令
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
JIT和解释执行速度对比,超过一万次JIT运行

package com.atguigu.java;

/**
 * 测试解释器模式和JIT编译模式
 *  -Xint  : 6520ms
 *  -Xcomp : 950ms
 *  -Xmixed : 936ms
 * @author shkstart  shkstart@126.com
 * @create 2020  12:40
 */
public class IntCompTest {
    public static void main(String[] args) {

        long start = System.currentTimeMillis();

        testPrimeNumber(1000000);

        long end = System.currentTimeMillis();

        System.out.println("花费的时间为:" + (end - start));

    }

    public static void testPrimeNumber(int count){
        for (int i = 0; i < count; i++) {
            //计算100以内的质数
            label:for(int j = 2;j <= 100;j++){
                for(int k = 2;k <= Math.sqrt(j);k++){
                    if(j % k == 0){
                        continue label;
                    }
                }
                //System.out.println(j);
            }

        }
    }
}

在这里插入图片描述在这里插入图片描述在这里插入图片描述
在以前latin和ISO-8859(欧洲码)这些都用了两个字节的char表示,但是他们一个字节就能放下来,用来个字节浪费了很多空间。因此,1.9出现了用byte数据表示String的方法,一个字符对应一个字节,但是汉子等一个字节放不下怎么办,因此加了一个encoding flag,表示使用了哪种编码汉字对应了utf-8
在这里插入图片描述在这里插入图片描述在这里插入图片描述

package com.atguigu.java;

/**
 * @author shkstart  shkstart@126.com
 * @create 2020  23:44
 */
public class StringExer {
    String str = new String("good");
    char[] ch = {'t', 'e', 's', 't'};

    public void change(String str, char ch[]) {
        str = "test ok";
        ch[0] = 'b';
    }

    public static void main(String[] args) {
        StringExer ex = new StringExer();
        ex.change(ex.str, ex.ch);
        System.out.println(ex.str);//good
        System.out.println(ex.ch);//best
    }

}

值传递的一个面试题

在这里插入图片描述

java的拼接

package com.atguigu.java1;

import org.junit.Test;

/**
 * 字符串拼接操作
 * @author shkstart  shkstart@126.com
 * @create 2020  0:59
 */
public class StringTest5 {
    @Test
    public void test1(){
        String s1 = "a" + "b" + "c";//编译期优化:等同于"abc"
        String s2 = "abc"; //"abc"一定是放在字符串常量池中,将此地址赋给s2
        /*
         * 最终.java编译成.class,再执行.class
         * String s1 = "abc";
         * String s2 = "abc"
         */
        System.out.println(s1 == s2); //true
        System.out.println(s1.equals(s2)); //true
    }

    @Test
    public void test2(){
        String s1 = "javaEE";
        String s2 = "hadoop";

        String s3 = "javaEEhadoop";
        String s4 = "javaEE" + "hadoop";//编译期优化
        //如果拼接符号的前后出现了变量,则相当于在堆空间中new String(),具体的内容为拼接的结果:javaEEhadoop
        String s5 = s1 + "hadoop";
        String s6 = "javaEE" + s2;
        String s7 = s1 + s2;

        System.out.println(s3 == s4);//true
        System.out.println(s3 == s5);//false
        System.out.println(s3 == s6);//false
        System.out.println(s3 == s7);//false
        System.out.println(s5 == s6);//false
        System.out.println(s5 == s7);//false
        System.out.println(s6 == s7);//false
        //intern():判断字符串常量池中是否存在javaEEhadoop值,如果存在,则返回常量池中javaEEhadoop的地址;
        //如果字符串常量池中不存在javaEEhadoop,则在常量池中加载一份javaEEhadoop,并返回次对象的地址。
        String s8 = s6.intern();
        System.out.println(s3 == s8);//true
    }

    @Test
    public void test3(){
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
        /*
        如下的s1 + s2 的执行细节:(变量s是我临时定义的)
        ① StringBuilder s = new StringBuilder();
        ② s.append("a")
        ③ s.append("b")
        ④ s.toString()  --> 约等于 new String("ab")

        补充:在jdk5.0之后使用的是StringBuilder,在jdk5.0之前使用的是StringBuffer
         */
        String s4 = s1 + s2;//
        System.out.println(s3 == s4);//false
    }
    /*
    1. 字符串拼接操作不一定使用的是StringBuilder!
       如果拼接符号左右两边都是字符串常量或常量引用,则仍然使用编译期优化,即非StringBuilder的方式。
    2. 针对于final修饰类、方法、基本数据类型、引用数据类型的量的结构时,能使用上final的时候建议使用上。
     */
    @Test
    public void test4(){
        final String s1 = "a";
        final String s2 = "b";
        String s3 = "ab";
        String s4 = s1 + s2;
        System.out.println(s3 == s4);//true
    }
    //练习:
    @Test
    public void test5(){
        String s1 = "javaEEhadoop";
        String s2 = "javaEE";
        String s3 = s2 + "hadoop";
        System.out.println(s1 == s3);//false

        final String s4 = "javaEE";//s4:常量
        String s5 = s4 + "hadoop";
        System.out.println(s1 == s5);//true

    }

    /*
    体会执行效率:通过StringBuilder的append()的方式添加字符串的效率要远高于使用String的字符串拼接方式!
    详情:① StringBuilder的append()的方式:自始至终中只创建过一个StringBuilder的对象
          使用String的字符串拼接方式:创建过多个StringBuilder和String的对象
         ② 使用String的字符串拼接方式:内存中由于创建了较多的StringBuilder和String的对象,内存占用更大;如果进行GC,需要花费额外的时间。

     改进的空间:在实际开发中,如果基本确定要前前后后添加的字符串长度不高于某个限定值highLevel的情况下,建议使用构造器实例化:
               StringBuilder s = new StringBuilder(highLevel);//new char[highLevel]
     */
    @Test
    public void test6(){

        long start = System.currentTimeMillis();

//        method1(100000);//4014
        method2(100000);//7

        long end = System.currentTimeMillis();

        System.out.println("花费的时间为:" + (end - start));
    }

    public void method1(int highLevel){
        String src = "";
        for(int i = 0;i < highLevel;i++){
            src = src + "a";//每次循环都会创建一个StringBuilder、String
        }
//        System.out.println(src);

    }

    public void method2(int highLevel){
        //只需要创建一个StringBuilder
        StringBuilder src = new StringBuilder();
        for (int i = 0; i < highLevel; i++) {
            src.append("a");
        }
//        System.out.println(src);
    }
}

在这里插入图片描述
ctral+12可以查看一个类里面的所有方法,并且可以有搜索功能
在这里插入图片描述
加上final是常量,编译即赋值
在这里插入图片描述在这里插入图片描述

题目:
new String(“ab”)会创建几个对象?看字节码,就知道是两个。
一个对象是:new关键字在堆空间创建的
另一个对象是:字符串常量池中的对象"ab"。 字节码指令:ldc
在这里插入图片描述
在这里插入图片描述
对应的字节码指令
在这里插入图片描述
toString对应的字节码指令,里面没有ldc,因此没有在字符串常量池里生成对象
在这里插入图片描述
一个比较难的题
inter

package com.atguigu.java2;

import org.junit.Test;

/**
* 如何保证变量s指向的是字符串常量池中的数据呢?
* 有两种方式:
* 方式一: String s = "shkstart";//字面量定义的方式
* 方式二: 调用intern()
*         String s = new String("shkstart").intern();
*         String s = new StringBuilder("shkstart").toString().intern();
*
* @author shkstart  shkstart@126.com
* @create 2020  18:49
*/
public class StringIntern {
   public static void main(String[] args) {

       String s = new String("1");
       s.intern();//调用此方法之前,字符串常量池中已经存在了"1"
       String s2 = "1";
       System.out.println(s == s2);//jdk6:false   jdk7/8:false


       String s3 = new String("1") + new String("1");//s3变量记录的地址为:new String("11")
       //执行完上一行代码以后,字符串常量池中,是否存在"11"呢?答案:不存在!!
       s3.intern();//在字符串常量池中生成"11"。如何理解:jdk6:创建了一个新的对象"11",也就有新的地址。
                                           //         jdk7:此时常量中并没有创建"11",而是创建一个指向堆空间中new String("11")的地址
       String s4 = "11";//s4变量记录的地址:使用的是上一行代码代码执行时,在常量池中生成的"11"的地址
       System.out.println(s3 == s4);//jdk6:false  jdk7/8:true
   }


}

在这里插入图片描述
"string"字面量它返回的地址指向常量池

package com.atguigu.java2;

/**
 * @author shkstart  shkstart@126.com
 * @create 2020  20:17
 */
public class StringExer1 {
    public static void main(String[] args) {
        String x = "ab";
        String s = new String("a") + new String("b");//new String("ab")
        //在上一行代码执行完以后,字符串常量池中并没有"ab"

        String s2 = s.intern();//jdk6中:在串池中创建一个字符串"ab"
                               //jdk8中:串池中没有创建字符串"ab",而是创建一个引用,指向new String("ab"),将此引用返回

        System.out.println(s2 == "ab");//jdk6:true  jdk8:true
        System.out.println(s == "ab");//jdk6:false  jdk8:true
    }
}

String.valueOf(j).intern();空间占用效率低,因为会进行辣鸡回收

package com.atguigu.java3;

/**
 * String的垃圾回收:
 * -Xms15m -Xmx15m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails
 *
 * @author shkstart  shkstart@126.com
 * @create 2020  21:27
 */
public class StringGCTest {
    public static void main(String[] args) {
        for (int j = 0; j < 100000; j++) {
            String.valueOf(j).intern();
        }
    }
}

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

垃圾回收

在这里插入图片描述在这里插入图片描述在这里插入图片描述

package com.atguigu.java;

/**
 * -XX:+PrintGCDetails
 * 证明:java使用的不是引用计数算法
 * @author shkstart
 * @create 2020 下午 2:38
 */
public class RefCountGC {
    //这个成员属性唯一的作用就是占用一点内存
    private byte[] bigSize = new byte[5 * 1024 * 1024];//5MB

    Object reference = null;

    public static void main(String[] args) {
        RefCountGC obj1 = new RefCountGC();
        RefCountGC obj2 = new RefCountGC();

        obj1.reference = obj2;
        obj2.reference = obj1;

        obj1 = null;
        obj2 = null;
        //显式的执行垃圾回收行为
        //这里发生GC,obj1和obj2能否被回收?
        System.gc();

        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述 在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
finalize()方法使对象复活的代码实现

package com.atguigu.java;

/**
 * 测试Object类中finalize()方法,即对象的finalization机制。
 *
 * @author shkstart
 * @create 2020 下午 2:57
 */
public class CanReliveObj {
    public static CanReliveObj obj;//类变量,属于 GC Root


    //此方法只能被调用一次
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("调用当前类重写的finalize()方法");
        obj = this;//当前待回收的对象在finalize()方法中与引用链上的一个对象obj建立了联系
    }


    public static void main(String[] args) {
        try {
            obj = new CanReliveObj();
            // 对象第一次成功拯救自己
            obj = null;
            System.gc();//调用垃圾回收器
            System.out.println("第1次 gc");
            // 因为Finalizer线程优先级很低,暂停2秒,以等待它
            Thread.sleep(2000);
            if (obj == null) {
                System.out.println("obj is dead");
            } else {
                System.out.println("obj is still alive");
            }
            System.out.println("第2次 gc");
            // 下面这段代码与上面的完全相同,但是这次自救却失败了
            obj = null;
            System.gc();
            // 因为Finalizer线程优先级很低,暂停2秒,以等待它
            Thread.sleep(2000);
            if (obj == null) {
                System.out.println("obj is dead");
            } else {
                System.out.println("obj is still alive");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值