JVM
JVM的定义:java的运行环境。
JVM的优点:
1.自动内存管理,垃圾回收环境
2.数组下标月结检查
3.多态
4.一次编写,到处运行
比较jdk,jre,jvm
JVM的结构
JVM内存结构
程序计数器
java代码的执行流程:java源代码先编译成为二进制字节码,二进制字节码放入到解释器中,转换成机器码,机器码最后交由CPU执行。
作用:程序计数器用来记住二进制字节码中的下一条执行jvm指令的执行地址。在解释器执行完上一条指令时,会调用程序计数器中的地址,执行下一条jvm指令。
特点:线程私有的。
不会出现内存溢出。
虚拟机栈
虚拟机栈的定义:线程运行需要的内存时间。每个栈中可以装有若干个栈帧。
栈帧的定义:每个方法运行时需要的内存。
活动栈帧:对应着当前正在执行的方法。
虚拟机栈常见的问题:
垃圾回收是否会涉及栈内存?
不会,栈内存在方法调用完成后就会进行处理,垃圾回收涉及到的是堆内存。
栈内存分配越大越好吗?
不是,栈内存越大,线程数量会减少,物理内存是固定的,栈内存越大,最大的线程数量就会越少。
方法内的局部变量是否线程安全?
如果方法内的局部变量是私有的,那么局部变量不会有线程安全的问题。
如果方法内的局部变量是共享的,那么局部变量会产生线程安全问题。
如果局部变量引入了对象并逃离方法的作用方法,需要考虑线程安全。
栈内存溢出问题:
栈帧过多导致栈内存溢出。
示例:
栈帧过大导致栈内存溢出。
堆
定义:Heap堆:通过new关键字都会使用堆内存
特点:是线程共享的,需要考虑线程安全问题,具有垃圾回收机制。
堆内存溢出:
示例:
import java.util.ArrayList;
import java.util.List;
public class lianxi1 {
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 (Throwable e){
e.printStackTrace();
System.out.println(i);
}
}
}
内存诊断方法
jps工具:查看当前系统中哪些java进程
jmap工具:查看堆内存占用情况
jconsole工具:图形界面的,多功能的监测工具,可以连续监测。
演示:
运行程序
然后输入jps,查看当前系统中的java进程
然后输入jmap -heap 10712查看堆内存的占用情况。
堆内存的占用情况
除了上述之外还有jvisualvm,
方法区
方法区结构
方法区在1.8之前内存溢出会导致永久代内存溢出,java. lang . OutOfMemoryError: PermGen space
方法区在1.8之后内存溢出会导致元空间内存溢出, java. lang . OutOfMemoryError: Metaspace
常量池
示例:
使用javac编译HelloWord,然后使用javap进行一次反编译,其中包含类的基本信息,常量池,类方法定义,
类的基本信息
常量池:
类方法定义
常量池的定义:常量池就是一张表,虚拟机根据这张常量表找到要执行的类名、方法名、参数类型和字面量等信息。
StringTable
StringTable是一个HashTable结构,不能扩容,常量池中的信息都会加载到运行时的常量池中,此时常量池中的符号,还没有变为java字符串对象。
示例:
字符串拼接:
示例:
S4使用在编译期间进行优化,已经在编译期间确定为ab
S5使用StringBuilder动态的配拼接。
StringTable特性:
常量池中字符串仅是符号,第一次使用时才会变成对象。
利用串池的机制,来避免重复创建字符串对象,
字符串变量的拼接原理是StringBuilder。
字符串常量拼接原理是编译器优化。
可以使用interm方法主动将串池中还没有的字符串对象放入串池。
在jdk1.8下,使用interm会将字符串尝试放入串池,如果没有,直接放入串池,如果串池中有,则不会放入串池,会把串池中的对象返回。
在jdk1.6下,使用interm会将字符串尝试放入串池,如果没有,会拷贝一份放入串池,如果没有则不会放入串池,会把串池中的对象返回。
示例:
第一问为false,s1,s2,s3将“a”,“b”,“ab”都放入到了串池,s4使用StringBuilder进行变量的拼接,s4在堆中,所以返回false。
第二问,s5查询串池中是否含有ab,串池中含有ab,直接引入。返回true。
第三问,s6尝试将ab放入常量池,ab已经在常量池中存在了,返回true。
第四问,x2是堆中的一个对象,x1是常量池中的对象所以返回false。
第五问,如果调转,那么x2会被放入到常量池中,而x1是常量池中的对象,返回true。
第六问,如果为jdk1.6,那么x2放入到常量池中的是x2的拷贝,x2本身还是在堆中,而x1是常量池中的对象,则返回false。
StringTable在jdk1.6中存在于常量池中,
StringTable在jdk1.8中存在于堆中。
直接内存
直接内存常用于NIO操作,分配回收成本较高,读写性能高,不受JVM垃圾回收管理。
文件读写过程:
直接内存:
直接内存释放是调用unsafe.freeMemory()方法进行释放,不是通过垃圾回收机制进行内存释放。ByteBuffer的实现类内部使用了虚引用,来检测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就由ReferenceHandler线程通过Clear的clean方法进行释放内存。