虚拟机与Java虚拟机?
虚拟机(Virtual Machine),就是一台虚拟的计算机,它是一款软件,用来执行一系列虚拟计算机指令。 大体上,虚拟机可以分为系统虚拟机和程序虚拟机大名鼎鼎的Visual Box,VMware就属于 系统虚拟机,它们完全是对物理计算机的仿真,提供了一个可运行完整操作系统的软件平台.程序虚拟机的典型代表就是Java虚拟机,它专门为执行单个计算机程序而设计,在 java虚拟机中执行的指令称为Java字节码指令java虚拟机是一台执行Java字节码的虚拟计算机,它拥有独立的运行机制,其运行的java字节码也未必由Java语言编译而成.JVM平台的各种语言可以共享Java虚拟机带来的跨平台型,优秀的垃圾回收器,以及即时编译器.JAVA技术的核心就是Java虚拟机(JVM,Java Virtual Machine),因为所有的Java程序都运行在Java虚拟机内部. //java虚拟机称为JVM
什么是JVM?
1.Java Visual Machine程序的运行环境,(即Java二进制字节码的运行环境)2.JVM是跨语言的平台(随着Java7的正式发布,Java虚拟机基本实现在Java虚拟机平台上运行非Java语言编写的程序,所以jvm能处理多语言混合编程.)Java虚拟机根本不关心运行在其内部的程序到底是使用何种编程语言编写的,它 只关心"字节码"文件.也就是说java虚拟机拥有语言无关性,只要是有其他 编程语言的编译结果满足并包含Java虚拟机的内部指令集,符号表以及其他的辅助信息,它就是一个有效的字节码文件,就能够被虚拟机所识别并装载运行.
JVM的作用?JAVA虚拟机就是二进制字节码的运行环境,负责装载字节码到其内部,解释/编译为对应平台上的机器指令执行.对于每一条Java指令,Java虚拟机规范都有详细定义,如怎么取操作数,怎么处理操作数,处理结果放在哪里.JVM的特点?1.一次编译,到处运行. 2.自动内存管理. 3. 自动垃圾回收功能JVM的位置?JVM是运行在操作系统之上的,它与硬件没有直接的交互.JVM内存结构?JVM内存结构分为:1.程序计数器 2.虚拟机栈 3.本地方法栈 4.堆 5.方法区Java: 跨平台的语言(即java程序通过编译变成字节码文件,再通过不同操作系统的不同版本的JVM就能实现一次编译到处运行)JVM的整体结构?HotSpot VM是目前市面上高性能虚拟机的代表之一.它 采用解释器与即时编译器并存的架构.在今天,Java程序的运行性能早已脱胎换骨,已经达到了可以和C/C++程序一较高下的地步
什么是字节码?
平时说的 Java字节码,指的是用java语言编译成的字节码但准确的说 任何能在jvm平台上执行的字节码格式都是一样的.所以应该统称为:jvm字节码。java虚拟机只与特定的二进制文件格式—Class文件格式所关联,Class文件中包含了java虚拟机指令集(或者称为字节码,Bytecodes)和符号表,还有一些其他辅助信息.不同的编译器,可以编译出相同的字节码文件,字节码文件也可以在不同的jvm上运行.
JVM的所处位置
JVM内存结构?
JVM内存结构?
JVM内存结构分为:1.程序计数器 2.虚拟机栈 3.本地方法栈 4.堆 5.方法区java程序执行流程?一个类从java源代码编译成二进制字节码(Java Class)后必须经过类加载器(ClassLoader)才能被加载到JVM里去运行,类放在方法区(Method Area)方法区,类创建的实例对象放在堆(Heap)中, 而Heap里面对象调用方法时会用到虚拟机栈,程序计数器,本地方法栈。方法执行时,是由执行引擎中的解释器(Interpreter)逐行进行执行方法里的热点代码(频繁被调用的代码)会用即时编译(JITCompiler) 编译优化 GC垃圾回收会对堆里面不再被引用的对象垃圾回收, 还有一些java代码不方便实现的功能,需要调用底层系统的功能,就需要本地方法接口
程序计算器( Program Counter Register)?
程序计算器的作用?程序计数器对应在物理上是通过cpu寄存器实现的(物理上叫寄存器,抽象为程序计数器),因为它频繁读写,是读取速率最快的一个单元它 。程序计数器主要用来记住下一条jvm指令的执行地址java源代码编译后对应的二进制字节码是一些jvm指令 , 这些指令不能直接交给CPU执行,它必须经过解释器 (该解释器是jvm虚拟机执行引擎的一个组件) , 它是用来专门把虚拟机指令解释成机器码的,然后机器码才能被CPU执行(CPU只能执行机器码) 。那么 程序计数器的作用就是记住下一条jvm指令的执行地址,也就是用来记住下一条要执行的jvm指令(jvm指令都对应一个数字,而这个数字就是一个内存地址)程序计算器( Program Counter Register)的特点?1. 线程是私有的,每个线程都有自己的程序计数器,即使线程被挂起,也能记住下一条jvm指令的执行地址 ,也就是能记住下一条要执行的jvm指令2. 不会存在内存溢出( 因为java虚拟机规范了程序计数器是没有内存溢出的区域)
内存溢出?
内存溢出是指程序在申请内存时,没有足够的内存空间供其使用。内存溢出( Out Of Memory,简称 OOM)原因是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存。此时程序就运行不了,系统会提示内存溢出,有时候会自动关闭软件,重启电脑后或者软件释放掉一部分内存又可以正常运行该软件,而由系统配置、数据流、用户代码等原因而导致的内存溢出错误,即使用户重新执行任务依然无法避免
虚拟机栈(Java virtual Machine stack)?
Java虚拟机栈是什么?线程运行时需要的内存空间 , 每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(stack Frame) ,对应着一次次的Java方法调用 。虚拟机栈是线程私有的。生命周期:生命周期和线程一致作用:主管Java程序的运行,保存方法的局部变量,部分结果,并参与方法的调用和返回。栈的特点:栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器JVM直接对虚拟机栈的操作只有两个:每个方法都执行伴随着 进栈(入栈、压栈) 和 执行结束后的 出栈工作对于栈来说不存在垃圾回收问题GC,但存在内存溢出问题OOM
栈可能出现的异常?
Java虚拟机规范允许Java栈的大小是动态的或者是固定不变的。如果虚拟机采用的是固定大小的Java虚拟机栈,那么每个线程的Java虚拟机栈的容量就可以在线程创立的时候单独选定。如果线程请求分配的容量超过了虚拟机栈允许的容量,那么就会抛出 StackOverflowError异常。如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个 OutOfMemoryError 异常。
栈中存储什么?
每个线程都有自己的栈,栈中的数据都是以栈帧(stack Frame)的格式存在。在这个线程上正在执行的每个方法都各自对应一个栈帧(stack Frame) 。 栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息。
虚拟机栈--->线程运行时需要的内存空间
栈帧--->每个方法运行时在栈中所需要的内存空间
虚拟机栈?-每个线程运行时所需要的内存,就称为虚拟机栈-每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存-每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法,(活动栈帧就是当前线程正在运行的那个方法就叫做活动栈帧)栈帧包括:参数,局部变量,返回地址每个栈帧中存储着:-局部变量表-操作数栈(或表达式栈)-动态链接(或指向运行时常量池的方法引用)-方法返回地址(或方法正常退出或者异常退出的定义)-一些附加信息
栈帧的内部结构?
栈帧的内部结构 : 局部变量表,操作数栈,动态链接(指向运行时常量池方法引用),方法返回地址,一些附加信息 。 栈帧的大小主要由局部变量表和操作数栈定。
问题?
1.垃圾回收是否涉及栈内存?
不涉及,因为栈内存无非就是一次次方法调用后所产生的这种栈帧内存,而栈帧内存在每次方法调用结束后都会被弹出栈内存,也就是会被自动的给回收掉,所以根本就不需要垃圾回收来管理栈内存,那么垃圾回收也就不涉及栈内存。栈内存,主要指方法调用时产生的的栈帧, //每次方法调用后会自动回收栈帧。堆内存,垃圾回收主要回收堆内存中的对象。 //栈是运行时的单位,而堆是存储的单位
2. 栈内存分配越大越好吗?--->不好!栈帧的内存划分大,则线程数变少,
不好,因为物理内存是一定的,栈内存越大,是可以支持每个栈中更多的递归调用(即栈帧调用),但是可执行的线程数就会越少。 栈--->线程运行时需要的内存空间栈内存分配越大越好吗? 不好,因为栈内存越大,就可能会导致线程数越少,因为物理内存有限。栈内存越大,是能够调用更多的方法,但会导致线程数越少(物理内存有限)。栈内存大小可以通过虚拟机参数来指定栈内存大小VM option中 -Xss1m 指定栈帧大小为1m一般情况默认栈帧大小为:Linux/x64 | MacOS | Oracle Solaris 基本都是1024KB(1M) 而window 根据虚拟内存为什么需要设置栈内存的大小?栈的大小直接决定了函数调用的最大可达深度。 可以使用参数 -Xss选项来设置线程的最大栈空间,//idea里面设置堆空间大小在VM options:-Xss数值m
3.方法内的局部变量是否线程安全?--->看实际情况
A.局部变量是线程私有的,那么它不会受到其他线程干扰B.加static -> 线程共享,则就需要考虑线程安全的问题C.如果方法内局部变量没有逃离方法的作用范围,那么它是线程安全的。D如果局部变量引用了对象,并逃离方法的作用方法,那么它需要考虑线程安全。
案例:
/**
*局部变量的线程安全问题
*/
public class Demo1_2{
// 多个线程同时执行此方法,不会造成x值混乱
static void m1() {
int x = 0;
for (int i = 0; i < 5000; i++) {
x++;
}
System.out.println(x);
// 多个线程同时执行此方法,会造成x值混乱
static void m2() {
static int xx = 0; //static关键字在这里已经共享了局部变量
for (int i = 0; i < 5000; i++) {
xx++;
}
System.out.println(xx);
}
public class Demo1_3 {
public static void main(String[] args) {
}
public static void m1() {
//不需要考虑线程安全? 因为局部变量是线程私有的,那么它不会受到其他线程干扰,即使它本身是线程不安全的
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
sb.append(3);
System.out.println(sb.toString());
}
public static void m2(StringBuilder sb) { // 作为参数传入,需要考虑线程安全
sb.append(1);
sb.append(2);
sb.append(3);
System.out.println(sb.toString());
}
public static StringBuilder m3() { // 作为返回值传出,需要考虑线程安全
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
sb.append(3);
return sb;
}
}
4.栈中可能出现的异常
Java 虚拟机规范允许Java栈的大小是动态的或者是固定不变的。如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。(栈容量=栈内存大小) 如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将会抛出一个StackOverFlowError 异常。如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在 创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个 OutOfMemoryError 异常。例如:无限递归会造成StackOverFlowError异常
虚拟机栈--->线程运行时需要的内存空间
栈帧--->每个方法运行时在栈中所需要的内存空间
栈-内存溢出?--->栈来说不存在垃圾回收问题GC,但存在内存溢出问题OOM
栈溢出几种情况?1.栈帧过多导致栈内存溢出2.栈帧过大导致内存溢出(一般不容易出现)3.局部数组过大。当函数内部的数组过大时,有可能导致堆栈溢出。4.递归调用层次太多。递归函数在运行时会执行压栈操作,当压栈次数太多时,也会导致堆栈溢出。5.指针或数组越界。这种情况最常见,例如进行字符串拷贝,或处理用户输入等等。
/**
* -Xms 为jvm启动时分配的内存,比如-Xms200m,表示分配200M
* -Xmx JVM可申请的最大Heap值,
* -Xss 为jvm启动的每个线程分配的内存大小 Java每个线程的Stack大小
* @author ppc
* @Date 2022/5/24 21:50
*/
//栈帧过多导致内存溢出演示 会报java.lang.StackOverflowError 栈溢出;如果线程请求的栈深度大于虚拟机所允许的深度将抛出
public class StackOverflowDemo {
private static int count;
public static void main(String[] args) {
try {
method1();
}catch (Throwable e){
e.printStackTrace();
System.out.println(count);
}
}
//因为这里没有条件来终止代码,那么代码就会一直调用,直到抛出栈溢出
private static void method1() {
count++;
method1();
}
}
public class StackOverflowDemo2 {
public static void main(String[] args) throws JsonProcessingException {
Dept d = new Dept();
d.setName("Market");
Emp e1 = new Emp();
e1.setName("zhang");
e1.setDept(d);
Emp e2 = new Emp();
e1.setName("li");
e1.setDept(d);
d.setEmps(Arrays.asList(e1,e2));
//{name:"Market",emps:[name:"zhangsan",dept:{name:'',emps:[{}]}},]} 因为两个类之间一直在循环引用导致JSON转换失败,会报错无限递归,就是Infinite recursion (StackOverflowError)
ObjectMapper mapper = new ObjectMapper();
// 把java对象输出成字符串实例
System.out.println(mapper.writeValueAsString(d));
}
}
class Emp{
private String name;
//如何解决转换时的循环引用,而产生的问题,那么就在要转换的类属性上添上注解@JsonIgnore,
private Dept dept;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
}
class Dept{
private String name;
private List<Emp> emps;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Emp> getEmps() {
return emps;
}
public void setEmps(List<Emp> emps) {
this.emps = emps;
}
线程运行诊断?
后台运行java程序 --->nohup java -jar test-0.0.1-SNAPSHOT.jar &
Linux环境下运行某些程序的时候,可能导致CPU的占用过高,这时需要定位占用CPU过高的线程1. top命令,查看是哪个进程占用CPU过高,也由此可以得到该进程的进程号pid(进程id),tid(线程id) %cpu(cpu占有率)2. ps H -eo pid,tid,%cpu | grep 进程号id(进程id) 刚才通过top查到的进程号再通过ps命令进一步可以查看到是哪个线程占用CPU过高, (tid)找出来的线程号id是十进制的需要转换为16进制,然后通过 jstack 进程id 可以得到该进程里面的所有线程名nid,通过对比就可以定位到坏掉了的线程以及具体信息jstack 进程id 可以查看进程中的所有线程的nid(线程id),刚才通过ps命令看到的tid来对比定位,注意jstack查找出的(tid)线程id是16进制的
1. 简单后台运行 java -jar XXX.jar &此时日志文件会保存到当前目录下的 ‘nohup.out’文件中,可以通过cat nohup.out查看[root@root file] nohup java -jar test-0.0.1-SNAPSHOT.jar &[1] 28535[root@root file] nohup: ignoring input and appending output to ‘nohup.out’[1] 28535代表jobid为1,pid为28535,最后一行空白为系统提示,代表此时系统等待输入,可以通过回车返回,这种方式不能保存日志。
2. 后台运行,保存控制台输出日志nohup java -jar XXX.jar >filename &[root@root file] nohup java -jar test-0.0.1-SNAPSHOT.jar >log.txt &[1] 28761[root@root file] nohup: ignoring input and redirecting stderr to stdout信息同上。但这种方式只能保存正常是输出信息,报错信息并不能正常输入到文件中,如需要保存错误信息,则参考以下方式。
3. 后台运行,保存控制台日志与错误信息nohup java -jar XXX.jar >filename 2>&1 & //把标准输出和标准错误一起重定向到一个文件中案例:程序运行很长时间没有结果,可能是多个线程造成了死锁,那么死锁如何进行定位?...通过jstack 进程id 可以得到该进程的所有线程信息,在得到里面的信息里面找到Found one Java-level deadlock: 这样就能判断出哪里发生死锁了
本地方法栈(Native Method Stack)?
本地方法栈与虚拟机栈发挥的作用是非常相似的,它们之间的区别不过是 虚拟机栈为虚拟机执行java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。在虚拟机规范中对本地方法栈中方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样, 本地方法栈区域也会抛出StackOverflowError和OutofMemoryError异常。一些带有native关键字的方法就是需要JAVA去调用本地的C或者C++方法,因为JAVA有时候没法直接和操作系统底层交互,所以需要用到本地方法
本地方法栈?
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用非常相似,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而 本地方法栈则是为虚拟机使用到的本地(Native)方法服务。本地方法栈也是线程私有的。和虚拟机栈一样,本地方法栈也会在栈深度溢出或者拓展失败时分别抛出StackOverFlowError和OutOfMemoryError异常。本地方法是使用C或者C++语言实现的。
堆(Heap)?
堆内存: 通过new关键字, 创建对象都会使用堆内存特点:它是线程共享的,堆中对象都需要考虑线程安全的问题有垃圾回收机制(堆中不再被引用的对象需要当做垃圾一样进行垃圾回收,释放内存)
堆内存溢出(java.lang.OutOfMemoryEeror:Java heap space)?
堆内存溢出(OOM)的常见原因有哪些?OutOfMemoryError: Java heap space。 在创建新的对象时, 堆内存中的空间不足以存放新创建的对象时发生。产生原因:程序中出现了死循环,不断创建对象;程序占用内存太多,超过了JVM堆设置的最大值。一般来说,绝大部分Java的内存溢出都属于 堆溢出。原因是因为大量对象占据了堆空间,这些对象都持有强引用导致无法回收,当对象大小之和大于Xmx参数指定的堆空间时就会发生堆溢出;OutOfMemoryError: unable to create new native thread。产生原因:系统内存耗尽,无法为新线程分配内存;创建线程数超过了操作系统的限制。OutOfMemoryError: PermGen space。永久代溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。OutOfMemoryError:GC overhead limit exceeded。超过98%的时间都在用来做GC并且回收了不到2%的堆内存。连续多次的GC,都回收了不到2%的极端情况下才会抛出。
OOM解决办法?
1.使用Xmx参数指定一个更大的堆空间; -Xmx JVM可申请的最大Heap值//idea里面设置堆空间大小在VM options:-Xmx数值m2.由于堆空间不可能无限增长,分析找到大量占用对空间的对象,在应用程序上做出优化;
堆内存诊断?
堆内存诊断三种方式1.1: jps工具 //查看当前系统中有哪些Java进程终端:jps1.2: jmap工具 //查看堆内存占用情况(只能查看某一时刻终端: jmap -heap 进程id2. jconsole工具 图形界面的终端:jconsole多功能的检测工具(堆内存使用,线程,cpu占有率,类加载数量),可以连续监测3.jvisualvm工具 图形界面的终端:jvisualvm
方法区?
方法区是线程共享的方法区定义:存储了跟类的结构相关的一些信息,包括类的成员变量、方法数据、成员方法和构造器方法的代码,运行时常量池等。方法区是所有Java虚拟机线程的一个共享区域 ,存储了跟类的结构相关的一些信息。主要包括:运行时常量池,方法,构造器,成员方法;方法区在虚拟机启动时被创建,逻辑上是堆的一个组成部分。具体实现上不同的jvm厂商实现时各不相同,并不强制方法区的位置; 方法区在运行时如果内存不足,也会抛出OutOfMemoryError
1.8版本方法区的实现位置在本地内存:即操作系统内存
方法区内存溢出1.8以前会导致永久代内存溢出1.8以后会导致元空间内存溢出
/**
* 演示元空间内存溢出------>元空间内存溢出:Metaspace
* -XX:MaxMetaspaceSize=8m //用来设置元空间的最大大小 -XX:MaxMetaspaceSize=数值m
*/
/**这里使用的是1.8版本的jdk,所以方法区的实现位置在本地内存:即操作系统内存,那么就很难演示
*方法区内存溢出,但是我们可以设置虚拟机参数,来演示这个问题
*idea里面可以通过VM options处指定设置虚拟的参数来指定元空间大小: -XX:MaxMetaspaceSize=8m
*/
//该案例修改了元空间大小为8m演示元空间内存溢出 元空间内存溢出:Metaspace
public class Demo1_2 extends ClassLoader { // 可以用来加载类的二进制字节码
public static void main(String[] args){
int j = 0;
try {
Demo1_8 test = new Demo1_8();
for (int i = 0; i < 10000; i++, j++) {
// ClassWriter 作用是生成类的二进制字节码
ClassWriter cw = new ClassWriter(0);
// 版本号,public,类名,包名,父类,接口
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
// 返回 byte[]
byte[] code = cw.toByteArray();
// 执行了类的加载
test.defineClass("Class" + i, code, 0, code.length);
}
} finally {
System.ou.println(j);
}
}
}
/**
* 演示永久代内存溢出 ------> 永久代内存溢出:PermGen space
* -XX:MaxPermSize=8m //用来设置永久代的最大大小 -XX:MaxPermSize=数值m
*/
/**
*1.6版本方法区的实现就不叫元空间叫做永久代,使用的是1.6版本的jdk,所以方法区的实现位置在jvm内存
*中
*idea里面在可以通过VM options处指定设置虚拟的参数来指定永久代大小: -XX:MaxPermSize=8m
*/
//该案例修改了永久代内存大小为8m演示永久代内存溢出 永久代内存溢出:PermGen space
public class Demo1_2 extends ClassLoader { // 可以用来加载类的二进制字节码
public static void main(String[] args){
int j = 0;
try {
Demo1_8 test = new Demo1_8();
for (int i = 0; i < 10000; i++, j++) {
// ClassWriter 作用是生成类的二进制字节码
ClassWriter cw = new ClassWriter(0);
// 版本号,public,类名,包名,父类,接口
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
// 返回 byte[]
byte[] code = cw.toByteArray();
// 执行了类的加载
test.defineClass("Class" + i, code, 0, code.length);
}
} finally {
System.ou.println(j);
}
}
}
jdk1.8以前会导致永久代内存溢出:java.lang.OutOfMemoryEeror:PermGen space需要先设置元空间内存大小,方便演示:-XX:MaxMetaspaceSize=8mjdk1.8之后会导致元空间内存溢出:java.lang.OutOfMemoryEeror:Metaspace
二进制字节码?
二进制字节码里面的包含的信息:1.类基本信息, 2.常量池,3.类方法定义, 4.包含了虚拟机指令二进制里面的常量池:常量池,就是一张表,虚拟机指令根据这张表找到要执行的类名,方法名,参数类型,自变量等信息将字节码进行反编译的指令 -v 操作显示反编译后的详细信息终端 :javap -v 类名.class
方法区里面的运行时常量池?
运行时常量池,常量池是 *.class文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址字符串常量池(string pool)是什么? !!!字符串常量池
在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个Hash表,默认值大小长度是1009;这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。字符串常量由一个一个字符组成,放在了StringTable上。在JDK6.0中,StringTable的长度是固定的,长度就是1009,因此如果放入String Pool中的String非常多,就会造成hash冲突,导致链表过长,当调用String#intern()时会需要到链表上一个一个找,从而导致性能大幅度下降; 但在JDK7.0以及之后中,StringTable的长度可以通过参数指定:-XX:StringTableSize=66666字符串常量池在Java内存区域的哪个位置?-在JDK6.0及之前版本,字符串常量池是放在Perm Gen区(也就是方法区)中;-在JDK7.0版本以及之后,字符串常量池被移到了堆中了。至于为什么移到堆内,大概是由于方法区的内存空间太小了 。字符串常量池里放的是什么?在JDK6.0及之前版本中,String Pool里放的都是字符串常量;在JDK7.0中,由于String#intern()发生了改变,因此String Pool中也可以存放放于堆内的字符串对象的引用字符串常量池中的字符串只存在一份!例如:String s1 = "hello,world!";String s2 = "hello,world!";即执行完第一行代码后,常量池中已存在 “hello,world!”,那么 s2不会在常量池中申请新的空间,而是直接把已存在的字符串内存地址返回给s2class常量池(Class Constant Pool) !!!class常量池
我们写的每一个Java类被编译后,就会形成一份class文件;class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References); 每个class文件都有一个class常量池。什么是字面量和符号引用:字面量包括:1.文本字符串 2.八种基本类型的值 3.被声明为final的常量等;符号引用包括:1.类和方法的全限定名 2.字段的名称和描述符 3.方法的名称和描述符。运行时常量池(Runtime Constant Pool) !!!运行时常量池
运行时常量池存在于内存中,也就是class常量池被加载到内存之后的版本,不同之处是:它的字面量可以动态的添加(String#intern()),符号引用可以被解析为直接引用JVM在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。而 当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中, 由此可知,运行时常量池也是每个类都有一个。在解析阶段,会把符号引用替换为直接引用,解析的过程会去查询字符串常量池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与字符串常量池中是一致的。