比较好的响应式网站,网页美工设计师,厦门工商网站查询企业信息,怎么找做网站的公司文章目录 内存结构程序计数器#xff08;寄存器#xff09;虚拟机栈局部变量表两类异常状况 线程运行诊断 本地方法栈堆方法区运行时常量池串池#xff08;StringTable#xff09;字符串的拼接串池的位置StringTable垃圾回收StringTable性能调优 直接内存 内存结构
程序计… 文章目录 内存结构程序计数器寄存器虚拟机栈局部变量表两类异常状况 线程运行诊断 本地方法栈堆方法区运行时常量池串池StringTable字符串的拼接串池的位置StringTable垃圾回收StringTable性能调优 直接内存 内存结构
程序计数器寄存器 Java源代码不能被cpu直接执行需要经过编译编译成二进制的字节码二进制字节码中的一行行代码就是jvm指令 Java跨平台技术就是靠这一条条jvm指令对任何操作系统都是一致的 这些指令再经过解释器解释成机器码机器码可以被cpu执行 程序计数器的作用就是在解释器解释jvm指令的过程中记住下一条jvm指令的执行地址 在物理上程序计数器是通过寄存器cpu中读取最快的一个单元实现的因为读取地址是非常频繁的 极小的一块内存
每条线程都需要一个独立的程序计数器程序计数器是线程私有的
是在jvm规范中唯一一个不会存在内存溢出的区
当前线程所执行的字节码的行号指示器
字节码解释器通过改变计数器的值选取下一条要执行的字节码的指令
是程序控制流的指示器
虚拟机栈 每个线程运行所需要的内存,称为虚拟机栈每个栈由栈帧构成,对应每次方法调用时所占的内存每个线程只能有一个活动栈帧,对应当前正在执行的那个方法 也是线程私有的生命周期与线程相同线程结束栈结束所以不存在垃圾回收问题 栈内存不是越大越好,栈的内存大了,线程数就少了,内存大了只能增快方法的递归调用 方法内局部变量是否具有线程安全问题: 如果这个变量是共享的,如static,就会有线程安全问题, 如果这个变量逃离了方法的作用范围,也会有线程安全问题 否则,不会有线程安全问题 每个方法被执行时jvm会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信 息
**栈帧**每个方法运行时需要的内存如参数局部变量返回地址这些都要提前分配内存
8大基本类型对象引用实例方法
局部变量表
存放了各种jvm基本数据类型对象引用reference类型returnAddress类型这些数据类型在局部变量表中的存储空间以局部变量槽Slot来表示。64位的longdouble类型占用两个变量槽其余数据类型占用一个。所需要的内存空间在编译期间完成分配方法运行期间不会改变局部变量表的大小指变量槽的数量
两类异常状况
如果线程请求的栈深度超过了虚拟机允许的深度(栈帧过多,栈帧过大)抛出StackOverflowError异常如果虚拟机栈容量可以动态扩展当栈扩展时无法申请足够的内存抛出OutOfMemeoryError异常
HotSpot虚拟机的栈容量是不可以动态扩展的所以在HotSpot上不会由于·虚拟机栈无法扩展而导致OutOfMemeoryError异常但是如果线程申请栈空间失败仍会出现OOM异常
线程运行诊断
cpu占用过多程序运行很长时间没有结果
本地方法栈
与虚拟机栈发挥作用非常相似和虚拟机栈一样也会在栈深度溢出和栈扩展失败时抛出异常StackOverflowOOM
区别
虚拟机栈为虚拟机执行Java方法服务本地方法栈为虚拟机使用到的本地Native方法服务
堆 VM options 控制堆内存大小为8mb -Xmx 8m 虚拟机管理内存中最大的一块是被所有线程共享(堆中对象需要考虑线程安全问题)的一块内存区域一个jvm只有一个堆内存堆内存大小可以调节在虚拟机启动时创建唯一目的是存放对象实例几乎所有对象实例都在这里分配内存是垃圾收集器管理的内存区域也被称为GC堆 从回收内存角度来看 现代垃圾收集器大部分都是基于分代收集理论设计的如新生代永久代等而这些仅仅是一部分垃圾收集器的设计风格而已不是虚拟机固有布局也不是对堆的进一步细致划分 从分配内存角度看 所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区用来提升对象分配时的效率 无论从哪个角度堆中存储的都只能是对象的实例将Java堆细分只是为了更好的回收内存更快的分配内存 Java堆不需要连续的内存
Java堆既可以是固定大小的也可以是可扩展的目前主流的虚拟机都是按照可扩展来实现的如果堆中没有内存完成实例分配并且堆无法扩展时将抛出OOM异常
方法区 被所有线程共享 静态变量常量类信息构造方法接口定义运行时常量池存在方法区中但是实例变量存在堆内存中和方法区无关重点 static finalClass模板常量池 方法区是堆的一个逻辑部分(关于他到底是不是堆的一部分,不同的jvm厂商实现方式不同)但是有个别名叫非堆从而与Java堆区分开来 JDK8之前是使用永久代实现的方法区考虑到HotSpot的发展这种实现方式被逐步放弃改为用本地内存来实现方法区 JDK7时将原本放在永久代的字符串常量池静态变量等移出 JDK8完全废弃的永久代的概念改用本地内存中实现的元空间把JDK7中剩余的内容全部移到元空间中 和Java堆一样不需要连续的内存可以选择固定大小和可扩展甚至可以选择不实现垃圾收集
相对来说方法区垃圾收集比较少见这部分内存回收的目标是针对常量池的回收和对类型的卸载
JDK8前会导致永久代内存溢出
JDK8后会导致元空间内存溢出
运行时常量池 常量池就是一张表虚拟机指令根据这张常量表找到要执行的类名方法名参数类型字面量等信息 是方法区的一部分常量池是.class文件中的当该类被加载他的常量池信息就会放入运行时常量池并把里面的符号地址变成真实的地址
串池StringTable StringTable特性 常量池中的字符串仅仅是符号只有用到时才变成对象 利用串池的机制来避免重复创建字符串对象 字符串变量拼接原理是StringBuilder 1.8 字符串常量拼接原理是编译器优化 可以用intern方法主动将串池中还没有的字符串对象放入串池 1.8将这个字符串对象放入串池时如果有则不会放入没有就将这个对象放入最后返回串池中的对象1.6如果有不会放入如果没有把对象复制一份放入串池最后返回串池中的对象 区别1.8放的是堆中的地址1.6放的是对象副本的地址 String对象的加载是延迟的只有走到才会将对象放到串池
/*** author gwj*/
public class HelloWorld {public static void main(String[] args) {//常量池中的信息运行时被加载到运行时常量池这是a b ab都是常量池中的符号还没有变成Java字符串对象String s1 a;String s2 b;String s3 ab;}
}StringTable 是哈希表结构不能扩容
字符串的拼接
public class HelloWorld {public static void main(String[] args) {//常量池中的信息运行时被加载到运行时常量池这是a b ab都是常量池中的符号还没有变成Java字符串对象String s1 a;String s2 b;String s3 ab;String s4 s1 s2; //new StringBuilder().append(a).append(b).toString()//new String(ab)System.out.println(s3 s4);//s3在串池中s4在堆中所以false//javac 在编译期间的优化结果在编译期已确定为ab//而上一行代码中s1和s4是变量只能在运行期间用StringBuilder动态拼接String s5 a b;}
}StringBuilder的toString源码new了一个String对象
Override
public String toString() {// Create a copy, dont share the arrayreturn new String(value, 0, count);
}从字节码中能看出在执行String s4 “a” “b”时生成字节码与String s3 “ab”相同
串池的位置
JDK1.6时StringTable在常量池中常量池在方法区中方法区使用永久代实现JDK1.8时StringTable从永久代转到了堆中因为串池中存着大量字符串放在永久代中使用效率低且永久代垃圾回收较难触发
StringTable垃圾回收
当内存空间不足时StringTable中那些没有被引用的字符串就会被回收
StringTable性能调优
如果系统中字符串常量个数非常多建议将StringTableSize调大些减少哈希冲突提高查找效率可以通过intern方法让字符串入池从而减少重复字符串的个数减少内存的占用
直接内存 直接内存不是虚拟机运行时数据区的一部分也不是定义的内存区域是操作系统内存但是也被频繁使用也可能导致OOM异常 不受jvm内存回收管理直接内存的释放可以通过调用Unsafe对象的freeMemory方法 常见于NIO操作中用于数据缓冲区 JDK4中加入了NIO类引入了一种基于通道和缓冲区的I/O方式 可以使用Native函数直接分配堆外内存然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作这样避免了在Java堆和Native堆中来回复制数据