一、定义:Java Virtual Machine - java程
序的运行环境(java二进制)
二、优点:
一、定义:Program Counter Register 程序计数器
二、作用:
右侧为Java源代码,其不能被直接执行,需要进行一次编译,编译成右侧的二进制字节码也就是JVM指令,再进过解释器,解释成机器码,最后交给CPU执行。JVM指令是Java跨平台的基础,它在windows以及Linux下都是一样的。而程序计数器在这里的作用是记住下一条jvm指令的执行地址。
三、实现
程序计数器主要通过寄存器实现。因为程序计数器需要频繁读取指令,而在CPU中寄存器是读取指令最快的
四、特点
二、组成:每个栈都是由栈帧组成,一个栈帧就是一次方法的调用。
三、栈与栈帧
栈-线程运行时内存空间,栈帧-每个方法运行时需要的内存
一、定义:Java Virtual Machine Stacks(Java虚拟机栈)
每个线程运行时需要的内存,称为虚拟机栈
每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
二、问题辨析
1、垃圾回收是否涉及栈内存?
不涉及
2、栈内存的分配越大越好吗?
不是。栈内存越大,反而线程数变少。
3、方法内的局部变量是否是线程安全
如果方法内局部变量没有逃离方法的作用访问,它是线程安全的
如果局部变量引用了对象,并逃离了方法的作用范围,需要考虑线程安全
eg:
public class test {
static void ml(){
int x=0;
for(int i=0;i<5000;i++){
x++;
}
System.out.println(x);
}
}
它们线程私有,所以是安全的。但是如果将x改为静态变量,就不安全了。
public class test {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
sb.append(4);
sb.append(5);
new Thread(()->{
m2(sb);
});
}
//线程私有,是安全的
public static void m1(){
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
sb.append(3);
}
//线程不安全,在主程序又一线程会对此线程造成影响
public static void m2(StringBuilder sb){
sb.append(1);
sb.append(2);
sb.append(3);
}
//线程不安全,其逃离了方法的作用范围,有可能被其他线程影响
public static StringBuilder m3(){
StringBuilder sb =new StringBuilder();
sb.append(1);
sb.append(2);
sb.append(3);
return sb;
}
}
一、发生原因
1、栈帧过多
public class test {
private static int count;
public static void main(String[] args) {
try {
method();
}catch (Throwable e)
{
e.printStackTrace();
System.out.println(count);
}
}
private static void method() {
count++;
method();
}
}
定位
死锁
一些带有native关键字的方法就是需要JAVA去调用本地的C或者C++方法,因为JAVA有时候没法直接和操作系统底层交互,所以需要用到本地方法,而本地方法栈就是为这些方法提供内存空间
一、定义:Heap 堆
通过new关机字,创建对象都会使用堆内存
二、特点
eg
import java.util.ArrayList;
import java.util.List;
public class test {
public static void main(String[] args) {
int i=0;
try {
List<String> list = new ArrayList<>();
String a="hello";
while(true){
list.add(a);
a=a+a;
i++;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
java.lang.OutofMemoryError :java heap space. 堆内存溢出
1、jps工具
查看当前系统中有哪些java进程
2、jmap工具
查看堆内存占用情况
命令:jmap -heap 进程号
3、jconsole工具
图形界面,多功能监测工具,可以连续监测
4、jvisualvm工具
JVM可视化工具
一、定义:方法区也是所有线程共享。主要用于存储类的信息、常量池、方法数据、方法代码等。**方法区逻辑上属于堆的一部分,**但是为了与堆进行区分,通常又叫“非堆”。永久代和元空间只是方法区的实现。
二、组成:
演示永久代内存溢出 java.lang.OutOfMemoryError: PerGen space
-XX: MaxPermSize=8m
演示元空间内存溢出 java.lang.OutOfMemoryError:Metaspace
-XX:MaxMetaspaceSize=8m
演示代码
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
public class Demo extends ClassLoader{
public static void main(String[] args) {
int j=0;
Demo test = new Demo();
for (int i=0;i<10000;i++){
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);
}
}
}
场景
一、定义
二进制字节码的组成:类的基本信息、常量池、类的方法定义(包含了虚拟机指令)
二、分类
静态常量池 存在于class文件中,比如经常使用的javap -verbose中,常量池总是在最前面
运行时常量池呢,就是在class文件被加载进了内存之后,常量池保存在了方法区中,通常说的常量池值的是运行时常量池。所以,讨论的都是运行时常量池
三、类的反编译
在这里为大家介绍一下类的反编译
String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // true
System.out.println(s1 == s4); // false
System.out.println(s1 == s9); // false
System.out.println(s4 == s5); // false
System.out.println(s1 == s6); // true
s1==s2很好判断,对此我就不多做赘述了
s1s3,s3实际上是两个字符串相加,其结果已经确定为hello,这里就要说到编译期的优化了,既然已经知道了值不会变,那它就不会再新建了。我们可以参照s1s9,在这个表达式中我们可以看到它是两个变量相加,所以在编译期间无法javac无法优化,需要使用StringBuilder创建新的对象
s1 = = s4 s4是分别用了常量池中的字符串和存放对象的堆中的字符串,做+的时候会进行动态调用,最后生成的仍然是一个String对象存放在堆中。
对于inter方法 ,它可以主动将串池中还没有的字符串对象传入串池,这也就是为什么s1==s6为true
五、总结
常量池中的字符串仅是符号,第一次使用才变为对象
利用串池的机制,来避免重复创建字符串对象
字符串变量拼接的原理是StringBuilder
字符串常量的拼接原理是编译期间优化
可以使用intern方法,主动将串池中还没有的字符串对象放入串池