一.程序计数器
程序计数器作用:记住下一条jvm指令的执行地址
特点:每个线程都有自己的程序计数器,是线程私有的 ;PC是java虚拟机中唯一一个不会出现内存溢出的区域
二、虚拟机栈
栈-线程运行时需要的内存空间,一个线程需要一个栈;如果有多个虚拟机线程,就会有多个虚拟机栈;每个栈内又多个栈帧(Frame)组成(一个栈帧对应着一次方法的调用,也就是说,栈帧是每个方法运行时需要的内存);每个线程只能有一个活动栈帧,对应着正在执行的那个方法
1.垃圾回收是否涉及到栈内存?
不需要,因为栈内存无非就是一次次的方法调用所产生的栈帧内存,而栈帧内存再每一次方法调用结束后都会弹出栈,也就是会被自动的回收掉,所以根本不需要垃圾回收
2.栈内存分配越大越好吗
-Xss size 栈内存分配越大,反而会让线程数变少,栈内存分配大了,只是方便了更多次的方法调用。
3.方法内的局部变量是否是线程安全的?
看一个变量是否线程安全,我妈就要看多个线程对这个变量是共享的还是这个变量是对每个线程是私有的,栈中局部变量是线程私有的,所以是线程安全的;
看一个变量是否是线程安全的,除了看这个变量是否是方法内的局部变量,还要看这个变量是否逃离了方法的作用范围,如果逃出了作用范围(作为返回值返回或者通过调用别的方法将变量传入),就是线程不安全的
总结:如果方法内局部变量没有逃离方法的作用范围,他是线程安全的;如果局部变量引用了对象,并逃离了方法的作用范围,需要考虑线程安全。
栈内存溢出
栈内存溢出原因:1.栈帧过多导致栈内存溢出;
public class Test{
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();
}
}
通过Xss设置栈内存大小
2.栈帧过大导致栈内存溢出;
线程诊断
class A{};
class B{};
public class Test{
static A a = new A();
static B b = new B();
public static void main(String[] args) throws InterruptedException{
new Thread(
()->{
synchronized (a){
try{
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
synchronized (b){
System.out.println("我获得了A和B");
}
}
}
).start();
Thread.sleep(1000);
new Thread(
()->{
synchronized (b){
synchronized (a){
System.out.println("我获得了a和b");
}
}
}
)
}
}
三、 本地方法栈
java虚拟机在调用本地方法(是指不是由java代码编写的方法,因为java代码有一些限制,它无法和操作系统底层打交道,所以就需要由C或者C++编写的本地方法与操作系统更底层的API打交道,java代码可以通过本地方法间接的调用到底层的一些功能)时,需要给这些本地方法提供的一些内存空间叫做本地方法栈。
举例,Object类中:clone()方法、hashCode()方法、notify()方法、notifyAll()方法、wait()方法都是本地方法。
四、堆(Heap)
前边讲到的程序计数器、虚拟机栈、本地方法栈都是线程私有的;后边要讲的堆、方法区都是线程共享的区域。
堆内存溢出(OOM)
示例如下:
使用-Xmx大小m设置堆内存大小
堆内存诊断
1.jmap 通过 jmap -heap 线程号
2.jconsole
3.通过在终端输入jvirsualvm可以可视化内存占用情况
五、方法区
方法区是所有虚拟机线程共享的一个区域,它存储的是和类的结构相关的一些信息,包括构造函数、成员方法、运行时常量池;其在虚拟机启动时被创建,逻辑上其实是堆的组成部分
jvm 内存结构1.6 VS 1.8中方法区
内存溢出
1.8以前会导致永久代内存溢出
报错:java.lang.OutOfMemoryError:PermGen space
1.8之后会导致元空间内存溢出
报错:java.lang.OutOfMemoryError:Metaspace
运行时常量池
常量池
java会先将java代码编码为二进制字节码,二进制字节码包含:类的基本信息、常量池、类的方法定义(包含了虚拟机指令)
常量池,其实就是一张表,虚拟机指令根据这张表找到要执行的类名、方法名、参数类型、字面量等信息。
运行时常量池,常量池是*.class文件中的,当该类被加载到虚拟机以后,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
六、StringTable——俗称的“串池”
上边的s3不等于s4,因为s3存在于串池中,而s4是新的new出来的对象,所以两者不相等
s5会直接到串池中查找有没有“ab”中这个串,如果有,直接赋值,所以s3和s5是相等的;这个地方是因为“a”和"b"两个字符串常量是已经确定好了的,不像变量可能还会变,所以在编译阶段直接就对其进行优化然后赋值了
StringTable特性
常量池中的字符串仅是符号,第一次用到时才变为对象。
利用串池的机制,来避免重复创建字符串对象
字符串变量拼接的原理是StringBuilder(1.8)
字符串常量拼接的原理是编译器优化
可以使用intern方法,主动将串池中还没有的字符串对象放入串池
对比下方两段代码:
第一个段代码之所以s和"ab"相等,是因为在s还没放进串池之前,串池里边没有"ab",于是在执行s.intern()之后,会将s直接放入串池,成为串池中的对象;第二段代码之所以s和“ab“不相等,是因为在s还没有放入串池之前,串池中就已经有了"ab"这个串池对象,所以在执行s.intern()的时候,不会将s放入串池,所以s存在于堆中,自然s与"ab"不相等。
StringTable位置
jdk1.6 StringTable存放在方法区中,1.7之后,jdk1.8之后放到了堆中
StringTable垃圾回收
StringTable也是会受到 垃圾回收机制的管理的,当内存空间不足时,StringTable中那些没有被引用的常量就会被当作垃圾进行回收。
StringTable的性能调优
七、直接内存
直接内存是操作系统的内存,