什么是JVM

定义

Java Virtual Machine –>Java程序的运行环境(Java二进制字节码的运行环境)

优点

  • 一次编写,到处运行
  • 自动内存管理,垃圾回收功能
  • 数组下标越界检查
  • 多态

JVM内存结构

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈
  • 方法区

1,程序计数器(Program Counter Register 寄存器)

代码的运行

Java代码被转换成JVM指令(二进制字节码)—->解释器—–>机器码—->CPU

程序计数器的作用就是记住下一条JVM指令的执行地址,如果当前执行了第四条命令,那么程序计数器就记住了第五条命令的地址。

如果没有计数器,那么当前命令执行完成之后,下一个命令不知道执行那一条。

特点

  • 是线程私有的
  • 不会存在内存溢出

2,栈

定义

  • 每个线程运行所需要的内存,称为虚拟机栈
  • 每个栈由多个栈帧组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

栈帧:每个方法运行时需要的内存。

方法1在栈中占有内存,当方法1执行完成后释放内存出栈。

问题

垃圾回收是否涉及栈内存?

并没有涉及到,因为栈帧在执行完成之后会自动释放内存

栈内存分配越大越好吗?

并不是,默认系统的栈内存就好了

方法内的局部变量是否线程安全?

  • 如果方法内部局部变量没有逃离方法的作用访问,他是线程安全
  • 如果是局部变量引用了对象,并逃离方法的作用方法,需要考虑线程安全

局部变量是线程私有的,所以当有两个线程同时调用同一个方法时,并不会影响他们的结果,所以是安全的。

如果在局部变量前加了关键字 static 那么就会产生线程安全问题,两个线程同时调用一个方法时,该变量会被两个线程调用,该局部变量是共享的。

栈内存溢出的原因

  • 栈帧过多导致内存溢出
  • 栈帧过大导致内存溢出

3,本地方法栈

定义

给本地方法提供存储的内存

4,堆

定义

通过new关键字,创建对象都会使用堆内存

特点

  • 他是线程共享的,堆中的对象都需要考虑线程安全问题
  • 有垃圾回收机制

5,方法区

定义

是一个线程共享区,存储了跟类的结构相关的信息。

组成

1.8:Metaspace(元空间实现):Class,ClassLoader,运行时常量池,

1.6:PermGen(永久代实现):运行时常量池,StringTable,Class,ClassLoader

常量池作用

常量池就是一张表,给指令提供一些量符号,根据常量符号去查找到对应要执行的类名,方法名、参数类型、字面量等信息。

运行时常量池

常量池是*.class文件中的,当该类被加载时,它的常量池信息就会放入运行时常量池,开始把里面的符号地址变为真实地址。

StringTable特性

  • 常量池中的字符串仅是符号,第一次用到时才变为对象
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是编译器优化
  • 可以使用intern方法,主动将串池中还没有的字符串对象放入串池中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
例如

public static void main(String[] args){
String x = "ab";
String s = new String("a") + new String("b");

//1.8将字符串对象尝试放入串池中,如果没有就会放入,如果有就不会放入,不管有没有都会把串池中的对象返回。
String s2 = s.intern();

System.out.println( s2 == x ); //true
System.out.println( s == x ); //false
}



public static void main(String[] args){
String s = new String("a") + new String("b");

String s2 = s.intern();

System.out.println( s2 == "ab" ); //true
System.out.println( s == "ab" ); //true
}

StringTable的位置

在堆中

6,直接内存(Direct Memory)

定义

  • 常见于NIO操作,用于书数据缓冲区
  • 分配回收成本较高,但读写性能高
  • 不受JVM内存回收管理

NIO:Java传统的IO是面向Stream-Oriented,而NIO是面向Block-Oriented,也就是说NIO的操作正常情况下是相对比较大的Block块为单位的,而不是像IO一样针对字节或者字符进行操作,ByteBuffer使用的就是直接内存。

在NIO所有的操作都是使用缓存区来处理的,也就是说NIO的读写操作都是基于缓存区完成的。

分配和回收原理

  • 使用了Unsafe对象完成直接内存的分配回收,并且回收需要主动调用freeMemory方法。
  • ByteBuffer的实现类内部,使用了Cleaner来检测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由ReferenceHandler线程通过Cleaner的clean方法调用freeMemory来释放内存。