类装载器
负责加载class文件,class文件再文件开头有特定的文件标识,将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构并且ClassLoader只负责class 文件的加载,至于它是否可以运行,则由Execution Engine决定。
方法区中不仅仅只有方法
程序计数器
作用,记住下一条jvm指令的执行地址
特点
- 线程私有
- 不会存在内存溢出
虚拟机栈
线程私有
2.1定义
栈:线程运行需要的内存空间
栈帧:每个方法运行时需要的内存(参数,局部变量,返回地址)
每个线程只能有一个活动栈帧,对应着正在执行的那个方法
问题
1:垃圾回收是否涉及栈内存?
- 不需要,方法调用涉及,栈帧内存,每次调用后,会被自动的弹出栈,不涉及到垃圾回收,垃圾回收只针对堆内存。
2:栈内存的分配越大越好吗?
- 栈内存越大,会让线程数变少。只会增加方法调用的深度。
3:方法内的局部变量是否线程安全?
- 主要看,变量是否是方法私有,是否逃离了方法的作用范围,没有逃离方法的作用范围,私有则安全。共享不安全,如static修饰,是类共享的,则线程不安全。作为方法参数,同样也容易被其他方法调用,线程不安全。
- 如果局部变量引用的是对象,并且逃离了方法的作用返回,则需要考虑线程安全。
2.2 栈内存溢出
- 栈帧过多
- 栈帧过大
2.3 线程运行诊断
定位
- 用top定位哪个进程对cpu的占用过高
- ps H -eo pid,tid, %cpu | grep 进程id(用ps命令进一步定位是哪个线程引起的cpu占用过高)
- jstack 进程id
- 可以根据线程id找到有问题的线程,进一步找到有问题的行数。
本地方法栈
本地方法运行的内存空间
堆
定义
- 通过new关键字,创建对象都会使用堆内存
特点 - 线程共享,需要考虑线程安全的问题
- 有垃圾回收机制
堆内存溢出
堆内存诊断
1.jps工具
- 查看当前系统中有哪些java进程
- jmap工具
- 查看堆内存占用情况
- jmap -heap 进程id
- jconsole工具
- 图形界面的,多功能的监测工具,可以连续监测
方法区
组成
方法区内存溢出
java1.8后,堆放在元空间,用的是系统内存,一般不会内存溢出,需要更改元空间大小。
运行时常量池
- 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
- 运行时常量池,常量池是* .class文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并
把里面的符号地址变为真实地址
String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2; //new StringBuilder().append("a").append("b").toString(),
//是一个对象存在内存中
String s5 = "a" + "b"; //javac在编译期间的优化,结果已经在编译期确定。
System.out.println(s3 == s4); //false
System.out.println(s3 == s5); //true
StringTable特性
- 常量池中的字符仅是符号,第一次用到是才变为对象
- 利用串池的机制,来避免重复创建对象
- 字符串变量拼接的原理是StringBuilder()
- 字符串常量拼接的原理是编译器的优化
- 可以使用intern方法,主动将串池中还没有的字符串放入串池
- 1.8将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象
返回。 - 1.6将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串
池,会把串池中的对象返回
- 1.8将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象
StringTable位置
版本不同会不同
StringTable垃圾回收
StringTable中的字符串常量,当没有被应用时,会被垃圾回收。
StringTable 性能调优
- 调整 -xx:StringTableSize=桶个数
当串池中的字符串常量的字数非常多时,可以把StringTable中桶的数量增大,可以让性能有明显的提升。 - 考虑字符串对象是否入池。
直接内存
定义
- 常见于NIO操作,用于数据缓冲区
- 分配回收成本较高,但读写性能号
- 不受JVM内存回收管理,属于操作系统内存