官方网站下载打印机驱动程序,江苏建设电子信息网站,郑州锐旗网站公司,wordpress主题modown文章目录 JVM一、思考二、jvm探究2.1、什么是jvm2.2、jvm的位置2.3、虚拟机2.4、作图#xff08;本人心得#xff09; 三、jvm体系结构3.1、体系结构图3.2、类加载器3.2.1、概念及作用3.2.2、类的加载过程#xff08;生命周期#xff09;3.2.3、加载器3.2.4、双亲委派机制3… 文章目录 JVM一、思考二、jvm探究2.1、什么是jvm2.2、jvm的位置2.3、虚拟机2.4、作图本人心得 三、jvm体系结构3.1、体系结构图3.2、类加载器3.2.1、概念及作用3.2.2、类的加载过程生命周期3.2.3、加载器3.2.4、双亲委派机制3.2.5、沙箱安全机制 3.3、程序计数器image-20220410013938381 3.4、虚拟机栈3.4.1、栈和队列3.4.2、什么是栈3.4.3、栈与栈帧3.4.4、栈溢出StackOverflowError 3.5、本地方法栈3.6、堆3.6.1、什么是堆**3.6.2、堆溢出**3.6.3、堆内存快照抓取3.6.4、堆空间演变 3.7、方法区3.7.1、什么是方法区3.7.2、方法区溢出3.7.3、jdk8和之前的区别3.7.4、常量池3.7.5、运行时常量池3.7.6、字符串常量池 JVM
一、思考
什么是JVMJVM的位置为什么有强大的跨平台性JVM的体系结构JVM类加载器的类加载过程什么是双亲委派机制什么是沙箱安全机制为什么要用程序计数器PC寄存器程序计数器存在内存溢出吗什么是OOM什么是堆溢出什么是栈溢出栈的生命周期什么是栈帧内存快照怎么抓取JVM调优主要针对的哪部分调优jdk1.8虚拟机和之前有什么变化
二、jvm探究
2.1、什么是jvm
JVM 是 java虚拟机是用来执行java字节码(二进制的形式)的虚拟计算机。具体详细介绍可以看百度百科下面是百度百科对 JVM 的定义 2.2、jvm的位置 jvm是运行在操作系统之上的 Java语言的可移植性正是建立在Java虚拟机的基础上。任何平台只要装有针对于该平台的Java虚拟机字节码文件.class就可以在该平台上运行。这就是“一次编译多次运行”。 原理使用Java编译器编译Java程序时生成的是与平台无关的字节码这些字节码只面向JVM。不同平台的JVM都是不同的但它们都提供了相同的接口只需要在不同的操作系统上安装一个对应版本的虚拟机(JVM) 。
2.3、虚拟机 通过 Java -version 命令可以查看自己是什么虚拟机常用的是HotSpotHotSpot VM是Sun JDK和OpenJDK中所带的虚拟机也是目前使用范围最广的Java虚拟机。还有其他几种虚拟机有兴趣可以自行了解
2.4、作图本人心得
JVM相对抽象不是口头就能描述清楚通过图片我们才能更直观便于理解。要想清楚的记住JVM的体系一定要自己作图只有自己多次画图才能在脑海里形成清楚的记忆。网上有很多详细的体系图我们可以先画一些简单的简图等到熟练以后再去看每一块详细的图一步一步理解。能自己动手尝试的东西有空一定要尝试只有自己动手才能记得牢固有时候看了一遍理解了过个一两个月就忘记了。只有频繁的使用才会形成深度记忆只有真正的理解才能永不遗忘。作图https://www.processon.com/ 或者本地画图软件
三、jvm体系结构
3.1、体系结构图 java文件经过编译器编译器前端编译为字节码文件–经过类加载子系统将字节码加载到内存当中生成一个class对象中间经过三步加载—链接—初始化 具体的可以查看类加载器内部图 在内存中方法区和堆区是多个线程共享区。 Java虚拟机栈本地方法栈程序计数器每一个线程独有。
3.2、类加载器
3.2.1、概念及作用
概念
类的加载指的是将类的.class文件中的二进制数据读入到方法区内然后在堆区创建一个java.lang.Class对象用来封装类在方法区内的数据结构。Class对象封装了类在方法区内的数据结构并且向Java程序员提供了访问方法区内的数据结构的接口。类加载器并不需要等到某个类被“首次主动使用”时再加载它JVM规范允许类加载器在预料某个类将要被使用时就预先加载它如果在预先加载的过程中遇到了.class文件缺失或存在错误类加载器必须在程序首次主动使用该类时才报告错误LinkageError、ClassNotFoundException错误如果这个类一直没有被程序主动使用那么类加载器就不会报告错误。
作用
将class文件加载到虚拟机的内存
3.2.2、类的加载过程生命周期 1、加载查找并加载类的二进制数据加载时类加载过程的第一个阶段在加载阶段虚拟机需要完成以下三件事情:
通过一个类的全限定名来获取其定义的二进制字节流。将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。在Java堆中生成一个代表这个类的java.lang.Class对象作为对方法区中这些数据的访问入口。
2、验证确保Class文件的字节流中包含信息符合当前虚拟机要求不会危害虚拟机自身安全。主要包括四种验证:
文件格式的验证验证字节流是否符合Class文件格式的规范例如是否以0xCAFEBABE开头版本号是否合理元数据验证对字节码描述的信息进行语义分析以保证其描述的信息符合Java语言规范的要求。例如是否有父类继承了final类非抽象类实现了所有的抽象方法字节码验证通过数据流和控制流分析确定程序语义是合法的、符合逻辑的。符号引用验证符号引用中通过字符串描述的全限定名是否能找到对应的类、符号引用类中的类字段和方法的访问性(private、protected、public、default)是否可被当前类访问。
3、准备正式为类变量分配内存并设置类变量初始值的阶段这些内存都将在方法区中分配
类变量static 和 全局变量如果不显式地对其赋值而直接使用则系统会为其赋予默认的值。对于同时被static和final修饰的常量必须在声明的时候就为其显式地赋值否则编译时不通过。对于引用数据类型reference来说如数组引用、对象引用等如果没有对其进行显式地赋值而直接使用系统都会为其赋予默认的零值即null。局部变量不需初始化。
4、解析虚拟机将常量池内的符号引用替换为直接引用的过程
5、**初始化**类加载最后阶段若该类具有超类则对其进行初始化。只有当对类的主动使用的时候才会导致类的初始化类的主动使用 包括以下六种
创建类的实例也就是new的方式访问某个类或接口的静态变量或者对该静态变量赋值调用类的静态方法反射初始化某个类的子类则其父类也会被初始化Java虚拟机启动时被标明为启动类的类Java Test直接使用java.exe命令来运行某个主类
6、**使用**使用类进行业务操作。
7、**卸载**结束生命周期
执行System.exit()方法程序正常执行结束由于操作系统出现错误而导致Java虚拟机进程终止
3.2.3、加载器
1、启动类加载器Bootstrap ClassLoader
启动类加载器主要加载的是JVM自身需要的类这个类加载使用C语言实现的是虚拟机自身的一部分它负责将 JAVA_HOME/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中注意由于虚拟机是按照文件名识别加载jar包的如rt.jar如果文件名不被虚拟机识别即使把jar包丢到lib目录下也是没有作用的(出于安全考虑Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类)。
2、扩展类加载器Extension ClassLoader
扩展类加载器是指Sun公司(已被Oracle收购)实现的sun.misc.Launcher$ExtClassLoader类由Java语言实现的是Launcher的静态内部类它负责加载JAVA_HOME/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库开发者可以直接使用标准扩展类加载器。jdk8后废弃使用平台类加载器Platform ClassLoader替换了原来的扩展类加载器Extension ClassLoader
3、系统类加载器应用程序加载器
应用程序加载器是指 Sun公司实现的sun.misc.Launcher$AppClassLoader负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库也就是我们经常用到的classpath路径
4、自定义类加载器
用户根据自己业务需求自己定义的加载器
接下来我们通过代码看一下
/*** 类加载器测试类*/
public class Demo01 {public static void main(String[] args) {Demo01 demo new Demo01();//输出应用程序加载器System.out.println(demo.getClass().getClassLoader());//AppClassLoader//输出扩展类加载器System.out.println(demo.getClass().getClassLoader().getParent());//PlatformClassLoader//输出启动类加载器System.out.println(demo.getClass().getClassLoader().getParent().getParent());//null}
}jdk8输出: jdk18输出 首先输出的是应用程序加载器然后是平台类加载器jdk8以后扩展类加载器被丢弃
原因
1、在JDK8中的这个Extension ClassLoader主要用于加载jre环境下的lib下的ext下的jar包。当想要扩展Java的功能的时候 把jar包放到这个ext文件夹下。然而这样的做法并不安全不提倡使用。
2、这种扩展机制被JDK9开始加入的“模块化开发”的天然的扩展能力所取代
最后启动类加载器输出了null这是因为启动加载器使用C语言实现我们获取不到。
3.2.4、双亲委派机制
先看代码在我们的src下建一个java.lang包
package java.lang;/*** 自定义String类* 放到自己建的 java.lang包下*/
public class String {static {System.out.println(自定义String类);}public static void main(String[] args) {System.out.println(自定义Sting类);}
}jdk8输出 如果你的jdk版本过高会显示 程序包xxx不可见程序包xxx已在模块 java.base都是正常的。
并且会导致其他类也无法运行删除String类即可。
新增测试类
/*** 测试类测试加载时是否会调用自定义String类的静态代码块*/
public class Demo02 {public static void main(String[] args) {String s new String();System.out.println(Hello String);}
}运行 发现我们自定义的String类并不会执行说明类加载器加载的不是我们自定义的String,这也就解释了为什么找不到main方法因为
java.lang下的String类是没有main方法的这就涉及到了我们的双亲委派机制。
双亲委派机制
如果一个类加载器收到类的加载请求他并不会自己先去加载而是把这个请求先委托给自己的父类去执行如果父类加载器还存在其父类加载器则进一步向上委托依次递归请求最终会到达最顶层的启动类加载器如果父类加载器可以完成加载任务就成功返回倘若父类加载器无法完成此类的加载任务子加载器才会尝试自己去加载这就是双亲委派模型
通俗的说就是自定义的类但是系统类加载器不会直接加载先向上传递给扩展类加载器扩展类加载器在向上传递给启动类加载器因为启动类加载器没有父类加载器所以他就尝试自己加载但是发现自己不能加载然后他就向下传递给扩展类加载器但是扩展类加载器发现自己也不能加载就在向下传递最终由系统类加载器进行加载注意扩展类加载器和启动类加载器有自己的类的加载目录不在此目录中这两个加载器就不会加载
一句话总结向上委托向下加载。
工作原理图 优势
避免类的重复加载使得java中的类随着他的类加载器一起具备了一种带有优先级的层次关系会保证系统的类不会受到恶意的攻击保护程序安全防止核心API被随意篡改
3.2.5、沙箱安全机制
什么是沙箱
Java安全模型的核心就是Java沙箱沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中并且严格限制代码对本地系统资源访问通过这样的措施来保证对代码的有效隔离防止对本地系统造成破坏。沙箱主要限制系统资源访问那系统资源包括什么——CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。
java中的安全模型
在Java中将执行程序分成本地代码和远程代码两种本地代码默认视为可信任的而远程代码则被看作是不受信的。对于授信的本地代码可以访问一切本地资源。而对于非授信的远程代码在早期的Java实现中安全依赖于沙箱 (Sandbox) 机制。如下图所示
JDK1.0安全模型 但如此严格的安全机制也给程序的功能扩展带来障碍比如当用户希望远程代码访问本地系统的文件时候就无法实现。因此在后续的 Java1.1 版本中针对安全机制做了改进增加了安全策略允许用户指定代码对本地资源的访问权限。如下图所示
JDK1.1安全模型 在 Java1.2 版本中再次改进了安全机制增加了代码签名。不论本地代码或是远程代码都会按照用户的安全策略设定由类加载器加载到虚拟机中权限不同的运行空间来实现差异化的代码执行权限控制。如下图所示
JDK1.2安全模型 当前最新的安全机制实现则引入了域 (Domain) 的概念。虚拟机会把所有代码加载到不同的系统域和应用域系统域部分专门负责与关键资源进行交互而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域 (Protected Domain)对应不一样的权限 (Permission)。存在于不同域中的类文件就具有了当前域的全部权限如下图所示 3.3、程序计数器
什么是程序计数器
程序计数器(Program Counter Register)是一个记录着当前线程所执行的字节码的行号指示器.Register的命名源于CPU的寄存器寄存器存储指令相关的现场信息。它占用很小的内存空间几乎可以忽略不记。也是运行速度最快的存储区域每个线程都有它自己的程序计数器是线程私有的生命周期与线程的生命周期保持一致。不会存在内存溢出OutOfMemoryError
看一段代码
/*** 程序计数器*/
public class Demo03 {public static void main(String[] args) {int a 10;int b 20;int c a b;}
}然后将代码进行编译成字节码文件
Code:stack2, locals4, args_size10: bipush 102: istore_13: bipush 205: istore_26: iload_17: iload_28: iadd9: istore_310: return左边的指令地址就是程序计数器记录的地址
优点
CPU需要不停的切换各个线程这时候切换回来以后就得知道接着从哪开始继续执行JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令
PC寄存器为什么被设定为私有的
所谓的多线程在一个特定的时间段内只会执行其中某一个线程的方法CPU会不停地做任务切换这样必然导致经常中断或恢复如何保证分毫无差呢为了能够准确地记录各个线程正在执行的当前字节码指令地址最好的办法自然是为每一个线程都分配一个PC寄存器这样一来各个线程之间便可以进行独立计算从而不会出现相互干扰的情况。由于cpu时间片轮限制多个线程在执行的过程中任何一个确定的时刻一个处理器或者多核处理器中的一个内核只会执行某一个线程中的一条指令。这样必然导致经常的中断和恢复如何保证在这个过程中不出现差错为了能够准确的记录各个线程正在执行的当前字节码指令的地址最好办法是为每一个线程都分配一个pc寄存器这样一来各个线程之间便可以进行独立计算从而不会出现干扰的情况。
3.4、虚拟机栈
3.4.1、栈和队列
栈先进后出后进先出队列先进先出FIFO 栈就像一个弹夹先压进去的子弹后出来
队列就像一个管道先进去的先出来
3.4.2、什么是栈
1、栈的概念
栈stack又名堆栈它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈它是把新元素放到栈顶元素的上面使之成为新的栈顶元素从一个栈删除元素又称作出栈或退栈它是把栈顶元素删除掉使其相邻的元素成为新的栈顶元素。
2、生命周期
栈的生命周期和线程的生命周期一致随着线程的启动而创建随着线程的停止而销毁。当主线程结束后整个的虚拟机的栈就全部销毁掉。
3、存放类型
8大基本类型 对象的引用 实例的方法
4、特点
访问速度快仅次于程序计数器线程私有存在 OOM不存在 GC
3.4.3、栈与栈帧
1、概念
栈线程运行时需要的内存空间。栈帧每个方法运行时需要的内存栈帧是栈的基本单位。每个线程只能有一个活动栈帧对应当前执行的那个方法
例如下图main()test()test1()都是一个栈帧test1()是活动栈帧也可以说是当前栈帧 2、运行原理
入栈时栈帧1会通过子帧指向栈帧2出栈时栈帧2会通过父帧指向栈帧1 3、栈帧演示
代码
/*** 栈帧演示*/
public class Demo04 {public static void main(String[] args) {test();}private static void test(){test1(1,2);}private static int test1(int a, int b) {int c a b;return c;}
}debug启动一步一步观察发现main()先入栈然后是test(),最后是test1()再走下去test1()先出栈然后是test(),最后是main() 4、总结
每个线程在创建的时候都会创建一个虚拟机栈其内部都会保存一个个的栈帧对应着一次次的方法调用栈是线程私有的会存在OOM不存在GC随着线程的启动而创建随着线程的停止而销毁先进后出
3.4.4、栈溢出StackOverflowError
1、栈溢出原因
栈帧过多栈帧过大不容易出现 代码演示递归调用
采用递归方法不停的调用方法栈帧就会过多导致栈溢出
/*** 测试内存溢出*/
public class Demo05 {private static int count;public static void main(String[] args) {try {test();} catch (Throwable e) {//注意捕获异常用Throwable不要用Exceptione.printStackTrace();System.out.println(count);//捕获异常打印执行方法次数}}private static void test(){count;//递归调用test();}
}输出 可以看出发生了栈溢出StackOverflowError记住这个错误error错误Exception无法捕获我这里方法执行了32059次我们可以自己设置栈大小
2、设置栈内存大小
-Xss256k//大小自己设定我的idea版本没有所以需要添加一个正常的应该有VM options直接输入就可以 可以看到把栈内存设置小了以后4368次栈就溢出了 总结
递归调用没有终止条件会导致栈溢出有时候调用第三方接口也会产生栈溢出互相调用产生了循环套娃。如果虚拟机栈容量不可以动态扩展当线程请求的栈深度大于虚拟机允许的最大容量就会抛出 StackOverFlowError 异常如果虚拟机栈容量可以动态扩展当栈无法申请到足够的内存会抛出 OutOfMemoryError 异常。
3.5、本地方法栈
1、概念
一个Native Method就是一个Java调用非Java代码的接口本地方法接口的作用是融合不同的编程语言为Java所用它的初衷是融合C/C程序标识符native可以与其他的Java标识符连用但是abstract除外
2、特点
本地方法栈也是线程私有的实现Java应用与Java外面的环境交互标识符native
3、Native关键字
我们打开Object类Alt7可以看到很多方法都是Native调用的 线程star()
代码
/*** Native* 线程 start()*/
public class Demo06 {public static void main(String[] args) {new Thread(()-{},my threa).start();}
}点进去看start()方法可以看到调用了start0(),然后start0()是native修饰 4、总结
本地方法栈也是线程私有的native关键字会进入本地方法栈调用本地方法接口调用其他语言的库java诞生的时候C和C很火想要有一席之地必须调用C C.
3.6、堆
3.6.1、什么是堆
1、概念
一个JVM实例只有一个堆内存堆也是Java内存管理的核心区域堆在JVM启动的时候创建其空间大小也被创建是JVM中最大的一块内存空间所有线程共享Java堆物理上不连续的逻辑上连续的内存空间几乎所有的实例都在这里分配内存在方法结束后堆中的对象不会马上删除仅仅在垃圾收集的时候被删除堆是GC垃圾收集器执行垃圾回收的重点区域。使用New关键字创建的对象都会使用堆内存
2、特点
线程共享要考虑线程安全问题一个JVM实例只有一个堆内存有GC垃圾回收响性能主要因素之一垃圾回收的重点区域
3.6.2、堆溢出
1、溢出原因
对象实例过多
代码
/*** 测试堆溢出* -Xmx8m*/
public class Demo07 {public static void main(String[] args) {int count 0;//计数try {ArrayListString list new ArrayList();String a Heap 堆内存;/*1、死循环做对象拼接2、String是固定不变的每次拼接都是一个新的对象3、对象存在list集合中由于不停的存放所以list集合不能被垃圾回收4、字符串对象存在集合里集合还在字符串对象也不能被垃圾回收*/while(true){list.add(a);aaa;count;}} catch (Throwable e) {//捕获最大的错误e.printStackTrace();System.out.println(count);}}
}输出 报错内存溢出堆空间不足OutOfMemoryError Java heap space
2、设置堆内存大小
-Xmx8m只执行了16次 3.6.3、堆内存快照抓取
jps工具查看当前系统中有那些进程jmap工具查看堆内存占用情况jconsole工具图形界面的多功能的监测工具可以连续监测
代码
/** 堆内存监测* 命令* 1、jps查看当前系统中有那些进程* 2、jmap -head 当前线程 例如jmap -head 39120*/
public class Demo08 {public static void main(String[] args) throws InterruptedException{System.out.println(开始);Thread.sleep(30000);//睡眠30s打命令byte[] bytes new byte[1024 * 1024 * 10];//10MSystem.out.println(创建数组);Thread.sleep(30000);bytes null;//清空对象System.gc();//垃圾回收System.out.println(结束);Thread.sleep(1000000L);}
}输出
1、jps jmap
命令 堆参数 占用情况 可以观察堆内存的使用情况
2、jconsole工具
命令 选择连接 可视化界面 3.6.4、堆空间演变 对空间划分
版本变化jdk1.7及之前新生代年轻代老年代 永久代jdk1.8及之后新生代年轻代老年代 元空间本地内存
3.7、方法区
3.7.1、什么是方法区
Java方法区和堆一样方法区是一块所有线程共享的内存区域方法区在JVM启动的时候被创建并且它的实际的物理内存空间中与Java堆区一样都是可以是不连续的。.方法区的大小跟堆空间一样可以选择固定大小或者可扩展
3.7.2、方法区溢出
方法区的大小决定了系统可以保存多少个类如果系统定义了太多的类导致方法区溢出虚拟机同样会抛出内存溢出错误java.lang.OutOfMemoryError:PermGen space永久代或者java.lang.OutOfMemoryError:Metaspace元空间比如加载了大量的第三方的jar包或者tomcat部署的工程过多都可能导致方法区溢出出现java.lang.OutOfMemoryError错误。
3.7.3、jdk8和之前的区别
1、方法区的演变
版本变化jdk1.6及之前有永久代Permanent generation静态变量存放在永久代jdk1.7字符串常量池、静态变量移出永久代存放在堆中jdk1.8及之后去除了永久代本地内存的元空间Metaspace取代
对比图 2、jdk1.7中字符串常量池StringTable为什么从永久代移到堆中
永久代的回收效率很低只有full Gc才会触发老年代或永久代空间不足会触发full Gc导致StringTable回收效率不高开发中会有大量字符串被创建放到堆里能够及时回收内存。
3、为什么去掉永久代
永久代在jvm中合适的大小难以确定元空间分配在本地内存无需考虑大小对永久代调优很困难
3.7.4、常量池
代码
/*** 二进制字节码类的基本信息常量池类方法定义虚拟机指令*/
public class Demo09 {public static void main(String[] args) {//梦开始的地方又回到最初的起点System.out.println(Hello World);}
}查看字节码文件
常量池
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OrnjfI43-1651405760875)(https://gitee.com/hyl199605251513/typora-img/raw/master/img/image-20220412212520222.png)]
编译后代码 在常量池中找 #2 发现对应的 #21 #22#21 对应 #28 System #22 对应#29 out同理可以找到我们 Hello World在常量池中对应的编码
Code:stack2, locals1, args_size1// 从常量池中符号地址为 #2 的地方先获取静态变量System.out0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;// 从常量池中符号地址为 #3 的地方加载常量 hello world3: ldc #3 // String hello world// 从常量池中符号地址为 #3 的地方获取要执行的方法描述并执行方法输出hello world5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V// main方法返回8: return3.7.5、运行时常量池
1、什么是运行时常量池
当类的字节码被加载到内存中后他的常量池信息就会集中放入到一块内存这块内存就称为运行时常量池并且把里面的符号地址变为真实地址运行时常量池具有动态性即运行期间也可以向常量池中添加新的常量
代码
/*** 运行时常量池*/
public class Demo10 {public static void main(String[] args) {Student student new Student();student.name凌晨;//存放运行时常量池}}class Student{String name;
}3.7.6、字符串常量池
1、StringTable
/*** 串池* StringTable [a,b,ab] hashtable结构不能扩容*/
public class Demo11 {public static void main(String[] args) {/*常量池中的信息都会被加载到运行时常量池这是 a b ab 都是常量池中的符号还没有成为字符串对象懒惰只有走到当前代码才会变为字符串对象*/String s1 a;//把a变为字符串对象“a”String s2 b;//把b变为字符串对象bString s3 ab;//把ab变为字符串对象ab//new StringBuilder().append(a).append(b).toString(); new String(ab)String s4 s1 s2 ;System.out.println(s3 s4);String s5 a b;//javac 在编译期间优化在编译阶段已经确定结果不可改变System.out.println(s3s5);}
}s1 s2 等同于 new StringBuilder().append(“a”).append(“b”).toString()等同于 new String(“ab”) 但是字符串常量池不创建ab对象因为StringBuilder的toString()中的new String 是调用的不同的构造器s5 “a” b在编译期间优化在编译阶段已经确定结果不可改变 相当于“ab”
2、intern
jdk1.8主动将串池中还没有的字符串对象放入串池 ,有则不放然后返回串池中的对象jdk1.6 有则不放入没有则对象复制一份放入串池然后返回串池中的对象
/*** intern*/
public class Demo12 {public static void main(String[] args) {String s1 new String(a) new String(b);//创建了几个对象//将字符串对象放入串池如果有就返回没有就放入String s2 s1.intern();//串池中没有“ab”,会把s1放入串池然后返回串池中的对象System.out.println(s2ab);//trueSystem.out.println(s1ab);//true}}/*** intern*/
public class Demo12 {public static void main(String[] args) {String s3 ab;String s1 new String(a) new String(b);//创建了几个对象//将字符串对象放入串池如果有就返回没有就放入String s2 s1.intern();//串池有“ab”,不会会把s1放入串池会返回串池中的对象System.out.println(s2s3);//trueSystem.out.println(s1s3);//false}}于“ab”
2、intern
jdk1.8主动将串池中还没有的字符串对象放入串池 ,有则不放然后返回串池中的对象jdk1.6 有则不放入没有则对象复制一份放入串池然后返回串池中的对象
/*** intern*/
public class Demo12 {public static void main(String[] args) {String s1 new String(a) new String(b);//创建了几个对象//将字符串对象放入串池如果有就返回没有就放入String s2 s1.intern();//串池中没有“ab”,会把s1放入串池然后返回串池中的对象System.out.println(s2ab);//trueSystem.out.println(s1ab);//true}}/*** intern*/
public class Demo12 {public static void main(String[] args) {String s3 ab;String s1 new String(a) new String(b);//创建了几个对象//将字符串对象放入串池如果有就返回没有就放入String s2 s1.intern();//串池有“ab”,不会会把s1放入串池会返回串池中的对象System.out.println(s2s3);//trueSystem.out.println(s1s3);//false}}