JVM-JDK8

  • JVM架构图
  • 类编译的过程
    • 语法分析&词法分析
      • 词法分析
      • 语法分析
    • 填充符号表
    • 注解处理
    • 语义分析
    • 生成字节码文件
  • 类加载的过程
    • 装载
    • 连接
      • 验证
      • 准备
      • 解析
        • 符号引用
        • 直接引用
    • 初始化
  • 类从编译到执行的过程
    • 编译
    • 加载
    • 解释
    • 执行
  • java代码加载的顺序
    • 父类的静态属性
    • 父类的静态代码块
    • 子类的静态属性
    • 子类的静态代码块
    • 父类的实例属性
    • 父类的初始化代码块
    • 父类的构造方法
    • 子类的实例属性
    • 子类的初始化代码块
    • 子类的构造方法
  • Class对象的使用
    • 三种方式创建class对象
    • 通过class对象获取class的属性、方法、构造方法
  • 类加载机制
    • 双亲委派机制
      • 原理
      • 类加载器的分类
        • BootstrapClassLoader(启动类加载器)
        • ExtClassLoader (拓展类加载器)
        • ApplicationClassLoader(应用程序加载器)
      • 存在的意义
    • 沙箱安全机制
      • 概念
  • JVM中的内存结构
      • 概念
      • 栈主要存储什么
      • 概念
      • 堆主要存储什么
    • 方法区
      • 概念
      • 方法区主要存储什么
    • 程序计数器
      • 概念
      • 特点
    • 本地方法栈
      • 概念
      • 本地方法栈主要存储什么
  • JVM中的常量池
    • JVM中的常量池主要有三种
      • Class常量池(静态常量池)
      • 运行时常量池
      • 字符串常量池(全局字符串常量池)
  • JVM中的垃圾回收机制(GC)算法
    • gc流程
    • 什么时候发生轻GC(Minor gc)
    • 什么时候发生重GC(Major gc)
    • 复制算法
      • 为什么在新生代中一定要有幸存区
      • 为什么在新生代中只有一个幸存区不行
      • 为什么复制算法比标记-整理算法快呢
    • 引用计数算法
      • 主要流程
    • 根搜索算法
      • 概念
      • 实现流程
    • 标记-清除法
      • 那为什么要停止掉应用程序的线程来使得GC线程运行呢?
      • 缺点
    • 标记-压缩算法(标记清除-压缩算法)
      • 缺点
    • 总结
  • java中String类的intern方法

JVM架构图

类编译的过程

语法分析&词法分析

词法分析

首先将.java源代码的字符流转换成 token集合,Token是指代码中具有独立语义且不可再分的标记

语法分析

根据解析后的Token集合,解析出抽象语法树(Abstract Syntax Tree, AST),AST中包含了java代码中的层级结构,该结构可以层级地展示代码中所有的变量、方法甚至是注释等各种信息,构建AST的过程会判断Token的类型与其在树中的位置是否匹配,这一步我们很好理解哈,你用关键字作为变量名称的时候编译会不通过,就是在这一步被逮到的。

填充符号表

符号表就是由符号地址(位置)和符号信息构成的”表格“,它存储的是标识所对应的类型、作用域等,这一步就是为了生成记录了类中符号的类型、属性等信息的符号表,方便后续流程中的应用

注解处理

并不是所有的注解都是在编译期起作用的,我们平时用反射处理的注解主要是指运行时注解,运行时注解在编译期不受影响,在编译之后的class文件中还是会保留,最终要在class文件到JVM运行的过程中才生效。

而编译期注解是指以@Retention(RetentionPolicy.SOURCE)定义的,在编译期就处理了的注解,这一类注解不会保留到class文件中。

听起来很懵,但其实编译过程中这一步注解处理其实大家在无意中已经接触过很多次了,比如大家常用的lombok,就是在这一步起作用的。

lombok采用的就是编译期注解处理的方法,因此当我们编译好用了lombok注解的.java文件后,打开生成的class文件就可以看到lombok相关的注解已经消失,而相应的getter、setter方法则已经被注入到class文件中。

在这一步,lombok的注解处理器生效,并对我们前面所说的抽象语法树AST进行增强处理。

首先找到@Data注解所在类对应的语法树(AST),然后修改该语法树(AST),增加getter和setter方法定义的相应树节点,实现我们所需的功能。

这一步也是为数不多的,编译器留给程序员自己编写代码来影响源代码编译过程的机会。

注解处理完成后,可能又会产生新的符号,因此如果执行了注解处理,需要再执行一次解析和填充符号表的操作(回到第2步)。

语义分析

语义分析的功能就是从结构和规则上对源代码进行检查,包括声明检查和类型检查等等,语义分析更进一步检查上下文中变量的规范性,例如变量是否已经声明,变量的数据类型与其参与的运算是否匹配等等

生成字节码文件

类加载的过程

装载

为了节省内存的开销,并不会一次性把所有的类都装载至JVM,而是等到「有需要」的时候才进行装载(比如new和反射等等)

为了防止内存中有多份相同的字节码,jvm使用了双亲委派机制去加载这个类

总结为:查找并加载类的二进制数据,在JVM「堆」中创建一个java.lang.Class类的对象,将对象的引用以及类中的方法存储在「栈」中,并将类相关的信息存储在JVM「方法区」中

连接

验证

验证类是否符合 Java 规范和 JVM 规范

准备

为类的静态变量分配内存,并将其初始化为默认值(比如static int num = 5,这里只将num初始化为0,5会在初始化的时候赋值);对于final static修饰的变量,编译的时候就会被分配

解析

把类的符号引用转换为直接引用

符号引用

符号引用就是一个类中(当然不仅是类,还包括类的其他部分,比如方法,字段等),引入了其他的类,可是JVM并不知道引入的其他类在哪里,所以就用唯一符号来代替; 例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现;比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。

直接引用

直接引用是将当前类(Pepole)指向被引用Class对象(Language)的地址,如果符号引用转为直接引用失败,说明被引用的对象还没有被加载到jvm内存中

初始化

为类的静态变量赋予正确的初始值,过程大概就是收集class的静态变量、静态代码块、静态方法,随后从上往下开始执行。如果「实例化对象」则会调用方法对实例变量进行初始化,并执行对应的构造方法内的代码。

类从编译到执行的过程

编译

见上文

加载

见上文

解释

在解释阶段会有两种方式把字节码信息解释成机器指令码,一个是字节码解释器、一个是即时编译器(JIT)

JVM会对「热点代码」做编译,非热点代码直接进行解释。当JVM发现某个方法或代码块的运行特别频繁的时候,就有可能把这部分代码认定为「热点代码」

使用「热点探测」来检测是否为热点代码。「热点探测」一般有两种方式,计数器和抽样。HotSpot使用的是「计数器」的方式进行探测,为每个方法准备了两类计数器:方法调用计数器和回边计数器,这两个计数器都有一个确定的阈值,当计数器超过阈值溢出了,就会触发JIT编译。

即时编译器把热点方法的指令码保存起来,下次执行的时候就无需重复的进行解释,直接执行缓存的机器语言

执行

操作系统把解释器解析出来的指令码,调用系统的硬件执行最终的程序指令。

java代码加载的顺序

public class test {public static void main(String[] args) {new TestLoading();}
}class TestLoading extends SuperTestLoading {private static Integer i = getIntegerI();private Integer j = getIntegerJ();static {System.out.println("子类的静态代码块");}{System.out.println("子类的初始化代码块");}TestLoading() {System.out.println("子类的构造方法");}private static Integer getIntegerI() {System.out.println("子类的静态属性");return null;}private Integer getIntegerJ() {System.out.println("子类的实例属性");return null;}
}class SuperTestLoading {protected static Integer superI = getIntegerI();protected Integer superJ = getIntegerJ();static {System.out.println("父类的静态代码块");}{System.out.println("父类的初始化代码块");}SuperTestLoading() {System.out.println("父类的构造方法");}private static Integer getIntegerI() {System.out.println("父类的静态属性");return null;}private Integer getIntegerJ() {System.out.println("父类的实例属性");return null;}
}

父类的静态属性

父类的静态代码块

子类的静态属性

子类的静态代码块

父类的实例属性

父类的初始化代码块

父类的构造方法

子类的实例属性

子类的初始化代码块

子类的构造方法

Class对象的使用

三种方式创建class对象

// 第一种方式创建字节码对象
Class<JvmMain> jvmMainClass = JvmMain.class;// 第二种方式创建字节码对象
try {Class<?> jvmMainClass1 = Class.forName("JvmMain");
}catch (Exception e){}// 第三种方式创建字节码对象
Class<? extends JvmMain> jvmMainClass2 = new JvmMain().getClass();

通过class对象获取class的属性、方法、构造方法

// 获取所有的属性,例如public、protected、private修饰等等
Field[] jvmMainClassDeclaredFields = jvmMainClass.getDeclaredFields();
for (Field field : jvmMainClassDeclaredFields) {System.out.println("属性名为:" + field.getName());
}// 获取方法
Method[] declaredMethods = jvmMainClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {System.out.println("方法名为:" + declaredMethod);
}// 获取构造方法
try {Constructor<JvmMain> declaredConstructor = jvmMainClass.getDeclaredConstructor(String.class, Integer.class);JvmMain zhouyong = declaredConstructor.newInstance("zhouyong", 20);System.out.println("通过构造方法进行实例化对象,并且调用方法,结果为:" + zhouyong.getName());
}catch (Exception e){e.printStackTrace();
}

类加载机制

双亲委派机制

原理

当一个类加载器收到了类加载的请求的时候,他不会直接去加载指定的类,而是把这个请求委托给自己的父加载器去加载。只有父加载器无法加载这个类的时候,才会由当前这个加载器来负责类的加载,若所有的类加载器都加载不了的时候就会抛出Class Not Found异常

类加载器的分类

BootstrapClassLoader(启动类加载器)

主要负责加载jre/lib/rt.jar包下的class

ExtClassLoader (拓展类加载器)

主要负责加载jre/lib/ext.jar包下的class

ApplicationClassLoader(应用程序加载器)

加载当前类路径下的class

存在的意义

1、通过委派的方式,可以避免类的重复加载,当父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类。

2、通过双亲委派的方式,还保证了安全性。因为Bootstrap ClassLoader在加载的时候,只会加载JAVA_HOME中的jar包里面的类,如java.lang.Integer,那么这个类是不会被随意替换的,除非有人跑到你的机器上, 破坏你的JDK。那么,就可以避免有人自定义一个有破坏功能的java.lang.Integer被加载。这样可以有效的防止核心Java API被篡改。

沙箱安全机制

概念

主要限制远程代码或者操作来访问本地的资源,例如cpu、内存等

JVM中的内存结构

概念

栈,栈中的数据都是以栈帧的格式存在的,栈帧是一个内存区块,是一个数据集,是一个有关方法 (Method) 和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧 F1,并被压入到栈中。每一个线程都有自己独立的栈,如果线程结束之后,栈内存也就释放了,所以栈是没有垃圾回收机制的说法的,因为它会有栈满StackOverFlowError的现象。

栈主要存储什么

8大基本数据类型 + 对象引用 + 类中的方法

概念

在jvm中只有一个堆,堆是被所有线程所共享的,GC垃圾回收机制主要在新生代和老年代中,分为重gc和轻gc

堆主要分为三个区域,若新生代和老年代的内存都已经满了且重gc也清理不了了,则就会报OOM:

  1. 新生代 - 刚开始创建的对象通常会存储在新生代,分为幸存者0区和幸存者1区
  2. 老年代 - 若15次轻gc后对象还存活就会被转入老年代中
  3. 元空间 (在jdk1.8之前是叫永久代,存储的是运行时常量池和一些class信息,元空间不属于堆内存,它属于本地内存 )

堆主要存储什么

存储实例化对象的所有信息 + 字符串常量池 + static变量

方法区

概念

方法区也是和堆一样是一个被所有线程共享的区域,它是堆的一个逻辑部分,为了区分堆,方法区还有个别名叫非堆元空间永久代都是方法区的一种实现,在方法区中有垃圾回收机制

方法区主要存储什么

class常量池 + 运行时常量池

程序计数器

概念

程序计数器是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里字节码解析器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

地址:https://blog.csdn.net/leaf_0303/article/details/78953669

​ https://www.cnblogs.com/manayi/p/9290490.html

class字节码文件中的程序计数器的值

特点

1.线程隔离性,每个线程工作时都有属于自己的独立计数器。

2.执行native本地方法时,程序计数器的值为空(Undefined)。因为native方法是java通过JNI直接调用本地C/C++库,可以近似的认为native方法相当于C/C++暴露给java的一个接口,java通过调用这个接口从而调用到C/C++方法。由于该方法是通过C/3C++而不是java进行实现。那么自然无法产生相应的字节码,并且C/C++执行时的内存分配是由自己语言决定的,而不是由JVM决定的。

3.程序计数器占用内存很小,在进行JVM内存计算时,可以忽略不计,因为只需要修改计数器的值,所以不会有OOM错误出现。

本地方法栈

概念

本地方法栈即被native修饰的方法就会存储在本地方法栈中,然后通过调用JNI(本地接口)实现

本地方法栈主要存储什么

native修饰的方法

JVM中的常量池

注意:在jdk1.6及之前常量池(包括所有常量池)都是存储在永久代中,而到了jdk1.7的时候由于永久代内存小的原因(仅有几m),字符串常量池又是一个不可控的常量池,当不断在常量池中创建对象的时候,就会有可能报OOM(内存溢出错误),所以后面将字符串常量池移到了中进行存储,其他常量池被移到了方法区中;到了jdk1.8的时候,还是因为内存大小的原因,又将永久代中的其它内容全部(class常量池和运行时常量池)移到了元空间(本地内存)中进行存储

JVM中的常量池主要有三种

Class常量池(静态常量池)

在class文件中除了字段,方法,接口,类的版本等描述信息外,还有一个重要的信息就是常量池。

class常量池主要存储的是 字面量 + 符号引用,相当于存储了类中的所有信息

字面量:

  1. 文本字符串:就是我们在代码中能够看到的字符串,例如String a = “aa”。其中”aa”就是字面量。
  2. 被final修饰的变量。

符号引用:

  1. 类和接口的全限定名
  2. 字段的名称和描述符
  3. 方法的名称和描述符

运行时常量池

  1. JVM在类加载完成后并运行,class常量池(静态常量池)的数据就会被保存到运行时常量池中,而运行时常量池是存储在方法区的,那说明class常量池也是在方法区
  2. class常量池和运行时常量池都是类所独有的,即每一个类在被加载和运行的时候,都会产生class常量池和运行时常量池

字符串常量池(全局字符串常量池)

  1. 在jdk1.6之前字符串常量池是存储在方法区的
  2. 在jdk1.7之后字符串常量池就存储在堆中了,因为方法区的内存大小没有堆大
  3. 字符串常量池是全类共享的

JVM中的垃圾回收机制(GC)算法

gc流程

刚刚新建的对象在 eden 中,经历一次 Minor GC,eden 中的存活对象就会被移动到第一块 survivor S0,eden 被清空;

等 eden 区再满了,就再触发一次 Minor GC,eden 和 S0 中的存活对象又会被复制送入第二块 survivor S1(这个过程非常重要,因为这种复制算法保证了 S1 中来自 S0 和 eden 两部分的存活对象按顺序放置,占用连续的内存空间,避免了碎片化的发生),然后 S0 和 eden 被清空

继续往 eden 区里放新生对象,eden 再满之后,S0 与 S1 交换角色,eden 和 S1 的存活对象被复制到 S0,如此循环往复。如果经过了15次gc之后对象还存活,那么该对象就会被送到老年代中。

若老年代的内存空间也满的话,此时就会进行Full GC,主要用到的就是标记-清除-压缩/标记-清除算法

什么时候发生轻GC(Minor gc)

若在年轻代的伊甸园区无法创建对象的时候就会进行一次轻gc

什么时候发生重GC(Major gc)

若年轻代和老年代的对象都满了的话就采取重gc

复制算法

主要发生在年轻代的幸存0区和幸存1区之间的存活对象复制过程

为什么在新生代中一定要有幸存区

如果没有 Survivor,Eden 区每进行一次 Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发 Full GC。

老年代的内存空间远大于新生代,进行一次Full GC 消耗的时间比 Minor GC 长得多。你也许会问,执行时间长有什么坏处?频发的 Full GC 消耗的时间是非常可观的,这一点会影响大型程序的执行和响应速度,更不要说某些连接会因为超时发生连接错误了。

结论:Survivor 的存在意义,就是减少被送到老年代的对象,进而减少 Full GC 的发生,Survivor 的预筛选保证,只有经历 15次 Minor GC 还能在新生代中存活的对象,才会被送到老年代。

为什么在新生代中只有一个幸存区不行

假设现在只有一个 survivor 区,我们来模拟一下流程:
刚刚新建的对象在 Eden 中,一旦 Eden 满了,触发一次 Minor GC,Eden中的存活对象就会被移动到 Survivor 区。这样继续循环下去,下一次 Eden 满了的时候,问题来了,此时进行 Minor GC,Eden 和 Survivor 各有一些存活对象,如果此时把 Eden 区的存活对象硬放到 Survivor 区,很明显这两部分对象所占有的内存是不连续的,也就导致了内存碎片化。

– 这里解释一下为什么会导致内存碎片化,按道理来说,只要每次将 Eden 区存活的对象按顺序跟在 survivor 区后面就好了呀,survivor 区也没有内存碎片。

其实不是这样的,第二次垃圾回收时,会先回收 Eden 和 survivor 两个区:具体过程先将 eden 区存活对象放在 survivor 区,然后回收掉两个区的对象,回收之后 survivor 区就会出现内存碎片

因为 survivor 区对象不会按顺序挂掉,复制的时候挂的对象也占空间,所以复制算法回收完,survivor 有碎片空间。

为什么复制算法比标记-整理算法快呢

  1. 复制算法是只需要将存活的对象直接复制到幸存区,而标记整理是将每个存活的对象进行一个标记
  2. 复制算法是利用空间来换时间,它直接开辟了一个内存空间(幸存区S1)来解决整理内存碎片的问题,但是标记整理法是使用自身去实现内存碎片的整理,实现内存空间的连续

引用计数算法

主要流程

每次当对象被引用的时候,计数器都会+1,若没有被引用到则-1,直到为0的时候就被回收


引用计数器现在已经java被淘汰了,主要是它的缺点比较多:

  1. 每次创建对象的时候,都会在对象的头部创建一个计数器,来记录该对象是否被引用,这样的话就比较占用内存
  2. 循环引用对象引用计数器是回收不了的,比如a对象引用b对象,b对象引用a对象

根搜索算法

概念

由于引用计数算法的缺陷,所以JVM一般会采用一种新的算法,叫做根搜索算法。它的处理方式就是,设立若干种根对象,当任何一个根对象到某一个对象均不可达时,则认为这个对象是可以被回收的

实现流程

可达性分析:

从根(GC Roots)的对象作为起始点,开始向下搜索,搜索所走过的路径称为“引用链”,当一个对象到GC Roots没有任何引用链相连(用图论的概念来讲,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

标记-清除法

1/ 标记过程:通过根搜索算法遍历GC Roots,将有引用关系的对象标记起来

2/ 清除过程:将未被标记的对象清除,清除完成后,已标记的对象重新变成未标记

也就是说,就是当程序运行期间,若可以使用的堆内存被耗尽的时候,GC线程就会被触发并将应用程序线程暂停,随后将依旧存活的对象标记一遍,最终再将堆中所有没被标记的对象全部清除掉,接下来便让应用程序线程恢复运行。

那为什么要停止掉应用程序的线程来使得GC线程运行呢?

假设我们刚标记完图中最右边的那个对象,暂且记为A,结果此时在程序当中又new了一个新对象B,且A对象可以到达B对象。但是由于此时A对象已经标记结束,B对象此时的标记位依然是0,因为它错过了标记阶段。因此当接下来轮到清除阶段的时候,新对象B将会被苦逼的清除掉。如此一来,不难想象结果,GC线程将会导致程序无法正常工作。

缺点

1/ 效率低,每次都要递归搜索对象是否与GC Roots有引用关系

2/ 在清除完成之后,会有内存碎片

标记-压缩算法(标记清除-压缩算法)

1/ 标记过程:通过根搜索算法遍历GC Roots,将有引用关系的对象标记起来

2/ 整理过程:将被标记的对象压缩到内存的一端之后,清理边界外所有的空间(未被标记的对象)

下图中可以看到,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉。如此一来,当我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可,这比维护一个空闲列表显然少了许多开销。标记/整理算法不仅可以弥补标记/清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内存减半的高额代价。

缺点

1、效率也不高,和标记-清除算法的第一点相似,其实和标记清除算法没有什么区别,就是加多了一步整理的操作,将内存空间进行整理变成连续的内存空间

总结

执行效率:复制 > 标记 - 整理 > 标记-清除

内存整齐度:复制 = 标记 - 整理 > 标记 - 清除

内存利用率:标记 - 整理 > 标记 - 清除 > 复制

java中String类的intern方法

深入解析String#intern -> 美团技术团队的牛逼技术文章,看了就懂

作用:

在jdk1.6及之前,使用intern()方法,它会先去字符串常量池中查找是否有该对象指定的字符串,若有则返回这个字符串对象,若没有则在常量池中创建该字符串。

在jdk1.7及之后,使用intern()方法,它会先去字符串常量池中查找是否有该对象指定的字符串,若有则返回这个字符串对象,若没有则在常量池中创建该引用对象。

好处

就是能够防止我们在创建字符串对象的时候在字符串缓冲池中创建太多的字符串,导致内存爆满,出现oom错误

对不同的jdk版本的intern()方法作用都是不一样的,就是因为不同jdk版本的字符串常量池在jvm中存储的位置不同,举个例子:

public static void main(String[] args) {String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
}

jdk1.6及之前版本

例子:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-baJrnx1L-1661574548922)(JVM/1624677001322.png)]

在jdk1.6的时候,字符串常量池是在永久代中的

String s = new String(“1”)这是字符串通过new去创建对象,在这里它一共创建了两个对象,第一个是堆中的对象,第二个是字符串常量池中的对象"1"。这里使用s.intern()是没有效果的,因为当new String(“1”)的时候已经创建了“1”,所以它去字符串常量池中寻找的时候发现已经存在了就直接引用该字符串,结果为对象引用指向堆中的对象;而String s2 = ”1“,是对象引用直接指向字符串常量池的地址

**结论如下:**一个指向堆一个指向字符串常量池,那么它们两个当然不相同,所以为false

String s3 = new String(“1”) + new String(“1”),在这里它一共创建了两个对象,第一个是堆中的对象,第二个是字符串常量池中的对象"1",此时s3引用对象内容是”11”,但此时常量池中是没有 “11”对象的,在经过s3.intern()方法,没有寻找到"11"这个字符串,那么就直接在常量池中创建"11"字符串并返回,所以对象引用还是指向堆中的对象;而String s4 = ”11“,是对象引用直接指向字符串常量池的地址

**结论如下:**一个指向堆一个指向字符串常量池,那么它们两个当然不相同,所以为false

jdk1.7及往后版本

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NfZUDdlt-1661574548924)(JVM/1624677027196.png)]

在jdk1.7的时候,字符串常量池是存储在堆中的

String s = new String(“1”)这是字符串通过new去创建对象,在这里它一共创建了两个对象,第一个是堆中的对象,第二个是字符串常量池中的对象"1"。这里使用s.intern()也是没有效果的,因为当new String(“1”)的时候已经创建了“1”,所以它去字符串常量池中寻找的时候发现已经存在了就直接引用该字符串,结果为对象引用指向堆中的对象;而String s2 = ”1“,是对象引用直接指向字符串常量池的地址

**结论如下:**一个指向堆一个指向字符串常量池,那么它们两个当然不相同,所以为false

String s3 = new String(“1”) + new String(“1”),在这里它一共创建了两个对象,第一个是堆中的对象,第二个是字符串常量池中的对象"1",此时s3引用对象内容是”11”,但常量池中是没有 “11”对象的,在经过s3.intern()方法,没有寻找到"11"这个字符串,这里跟1.6的区别就很大,它是直接在字符串常量池中存储对象的引用s3,并不是存储"11"字符串;而String s4 = ”11“,它会从字符串常量池中查找到已经有这个值,则直接指向对象引用s3。

其实我个人觉得这里很像是将s3的地址赋值给s4地址,即如下代码

String s3 = new String("1") + new String("1");
String s4 = s3;
System.out.println(s3 == s4);// true
System.out.println(s3.equals(s4))// true;

**结论如下:**s3和s4两个对象的地址都是相同的,所以为true

创作不易,希望大家能够点个赞,也希望大家能帮忙指出问题,一起进步!!!谢谢大家~~

JVM虚拟机(JDK8)相关推荐

  1. java visualvm远程监控_深入理解JVM虚拟机12:JVM性能管理神器VisualVM介绍与实战

    本文转自互联网,侵删 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutori ...

  2. 深入JVM虚拟机(四) Java GC收集器

    转载自  深入JVM虚拟机(四) Java GC收集器 1 GC收集器 1.1 Serial串行收集器 串行收集器主要有两个特点:第一,它仅仅使用单线程进行垃圾回收:第二,它独占式的垃圾回收. 在串行 ...

  3. 一文读懂JVM虚拟机:JVM虚拟机的内存管理(万字详解)

    JVM虚拟机的内存管理 文章目录 JVM虚拟机的内存管理 JVM与操作系统 Java虚拟机规范和 Java 语言规范的关系 java虚拟机的内存管理 JVM整体架构 一.PC 程序计数器 二.虚拟机栈 ...

  4. 徐无忌深入JVM虚拟机笔记:Java代码到底是如何运行起来的?

    徐无忌深入JVM虚拟机笔记:Java代码到底是如何运行起来的? 完成:第一遍 1.Java代码到底是如何运行起来的? Demo.java编写的源文件 打包成:Jar包即Demo.class 通过Jav ...

  5. JVM(一)JVM虚拟机内存结构 和 JAVA内存模型(JMM)

    本文转自:浅析java内存模型--JMM(Java Memory Model) - 路易小七 - 博客园,尊重作者,转载请注明出处~ JVM虚拟机内存结构 和 JAVA内存模型 是两个不同的概念 JV ...

  6. 我所知道JVM虚拟机之聊聊JVM虚拟机

    一.介绍JVM跨语言的平台 随着Java7的正式发布,Java虚拟机的设计者们通过JSR-292规范基本实现在Java虚拟机平台上运行非Java语言编写的程序 ava虚拟机根本不关心运行在其内部的程序 ...

  7. 学习深入理解JVM虚拟机及JavaGuide后的学习笔记

    JVM虚拟机 一.JVM组成部分: 1.程序计数器 作用,是记住下一条JVM指令的内存地址:1.多线程情况下,程序计数器用于记录当前线程执行的位置,从而线程切换回来的时候能够知道线程上次运行到哪儿了. ...

  8. 深入理解JVM虚拟机(二):垃圾回收机制

    谈起GC,应该是让Java程序员最激动的一项技术,我相信每个Java程序员都有探究GC本质的冲动!JVM垃圾回收机制对于了解对象的创建和对象的回收极为重要,是每个Java程序员必须掌握的技能. 本博客 ...

  9. msm(CentOS 6)及jvm虚拟机性能监控(04)_recv

    为什么80%的码农都做不了架构师?>>>    JVM JVM内存管理--运行时数据区 JVM大体由五个部分组成,分别为JVM Stack.Native Stack.Program ...

最新文章

  1. MapReduce学习总结之Combiner、Partitioner、Jobhistory
  2. android studio下的NDK开发详解
  3. oracle获取父级,如何通过sql获取oracle connect中的最终父id列
  4. 你真的懂select吗??
  5. Go性能测试benchmark
  6. 实战演练:PostgreSQL在线扩容
  7. 未能加载文件或程序集 Newtonsoft.Json, Version=4.5.0.0 的报错,解决方法
  8. Harmony OS — ProgressBar垂直、水平进度条
  9. 164 Maximum Gap 最大间距
  10. 打补丁更新不适用计算机,安装补丁“此更新不适用于你的计算机”解决办法
  11. php安装ziparchive扩展,记一次PHP扩展-ZipArchive安装
  12. 前端VUE学习总结(一)
  13. php eof记录指针,关于ASP eof与bof 区别分析
  14. shell脚本下的教你如果运用for,while,unti循环,以及区别l
  15. C语言之数据的四种表现形式
  16. 两轮车燃油喷射系统的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  17. IOS(iphone,ipad,itouch)开发 之 屏幕旋转
  18. 【实用工具箱】将CSDN文章内容转成PDF文件实用教程(程序员小技巧)—— 禅与计算机程序设计艺术
  19. 华安基金高管事发 基金业突遇“公信力寒流”(ZT)
  20. 如何选择Python版本2还是3

热门文章

  1. 微信公众号自动回复机器人
  2. 对PHM铣刀磨损数据进行分析
  3. Windows10彻底卸载VMWare虚拟机
  4. 批量提取word doc文档中的表格
  5. 谈谈Http长连接和Keep-Alive以及Tcp的Keepalive
  6. 华为Honor6打开开发者选项
  7. element-ui el-dialog侧边弹窗可横向拖拽改变宽度
  8. 09_keras_Tuner使用keras Tuner调整超参数(超参数优化)
  9. 微生物组测序方法比较
  10. 2. vibrate-arch