天天看点

内存结构

作用:记住下一条 jvm 指令的执行地址

特点:

是线程私有的

为了线程切换后能恢复到原来的执行位置,每条线程都需要有一个独立的程序计数器,各个计数器之间互不影响。

不会存在内存溢出

Java Virtual Machine Stacks (Java 虚拟机栈)

每一个线程都会开辟一个虚拟机栈,用于存放栈帧;栈帧用于存储局部变量表、操作数栈、动态连接和方法出口等信息。

局部变量表存放了编译期可见的各种基本数据类型,这些数据类型在表中的存储空间以局部变量槽(slot)表示,

每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。

如果线程请求的栈深度大于虚拟机所允许的深度,抛出SO异常;如果栈扩展无法申请到足够的内存抛出OOM异常。

问题 ?

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

不是,栈内存随着方法执行的结束而释放,并不会被垃圾回收释放,垃圾回收释放的堆内存。

栈内存分配越大越好吗?

不是,栈内存分配越大,会导致线程数量减少。

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

如果方法内局部变量没有逃离方法的作用范围,它是线程安全的

如果是局部变量引用了对象,或逃离方法的作用范围,需要考虑线程安全

栈帧过多导致栈内存溢出

栈帧过大导致栈内存溢出

1、cpu 占用过多

用top定位哪个进程对cpu的占用过高

ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)

jstack 进程id

可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号

2、程序运行很长时间没有结果

调用本地方法和操作系统交互,例如Object 里的方法,方法前有 native 修饰。

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

它是线程共享的,堆中对象都需要考虑线程安全的问题

有垃圾回收机制

垃圾回收会对无用的对象进行回收,但如果一直有对象被创建,并且对象一直被使用,最终会导致堆内存溢出。

1、 jps 工具

查看当前系统中有哪些 java 进程

2、map 工具

查看堆内存占用情况

3、jconsole 工具

图形界面的,多功能的监测工具,可以连续监测

案例

垃圾回收后,内存占用仍然很高

打开jdk按照目录:jvisualvm.exe

内存结构

1.8前会导致永久代溢出

1.8后会导致元空间溢出

常量池就是一张表,虚拟机指令根据这张表找到要执行的类名、方法名、参数类型、字面量等信息

运行时常量池,当该类被加载时,它的常量池信息会放入运行时常量池,并把里面的符号地址变为真实地址

常量池中的字符串仅仅是符号,只有第一次用到时才变为对象

利用串池的机制,避免重复创建字符串对象

字符串变量拼接的原理是StringBuilder

字符串常量拼接的原理是编译器优化

可以使用intern 方法,主动将串池中还没有的字符串对象加入串池

1.8 将这个字符串对象尝试放入串池,如果有则不放入,没有则放入,返回串池中的对象

1.6 将这个字符串对象尝试放入串池,如果有则不放入,没有则复制一份此对象,放入串池,返回串池中的对象

StringTable 调优:

-Xms500m -Xmx500m -XX:PrintStringTableStatistics -XX:StringTableSize =

-jvm启动时分配的最大内存 -jvm运行过程中分配的最大内存 -打印串池中的统计信息 -设置串池中桶的个数

有大量的重复的字符串,可以让字符串入池,减少字符串的个数,减少堆内存的使用。

常见于NIO操作,用于数据缓冲区

分配回收成本高,但读写性能高

不受jvm内存回收管理

内存结构

使用了Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用freeMemory方法

ByteBuffer 的实现类内部,使用了 Cleaner(虚引用) 来监测 ByteBuffer 对象,一旦 ByteBuffer 对象被垃圾回收,那么就会由ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存

-XX:+DisableExplictGC 关闭显示的gc:System.gc()