JVM的内存结构:
程序计数器、虚拟机栈、本地方法栈、堆、方法区
一、程序计数器(寄存器):
Program Counter Register
每个线程都有一个程序计数器,用来记住线程中下一条指令的地址 (cpu往往是多线程并发执行的,因此cpu会在多个线程中来回切换)
(1)不存在内存泄漏问题
(2)每个线程独有
二、虚拟机栈(Java Virtual Machine Stack)
虚拟机栈内存是每个线程运行需要的总内存,可以再java命令中通过-Xss参数来指定,对于linux、macOS等系统来说默认值都是1024k,对应windows系统则取决于系统的虚拟内存大小。
虚拟机栈由多个栈桢组成,每个栈桢都是线程中一次方法调用所需要的内存,当前处于栈顶的栈桢也称为活动栈桢。线程中位于栈顶的栈桢只有一个,也就是说活动栈桢只有一个。
栈桢(Frame)中的内存包含:方法中参数、局部变量、返回值地址。
虚拟机栈:
(1)可能会出现内存溢出问题
a、栈桢数量太多导致内存溢出
b、栈桢占用内存太多导致内存溢出
(2)java程序cpu内存占用太高,问题排查
a、top 命令
b、ps H -eo pid,tid,%cpu 命令,查看进程中都有哪些线程
c、jstack
命令 ,列出java中进程的线程信息
(3)线程安全问题
a、栈桢中的局部变量,如果没有被方法返回,则不会有线程安全问题
b、栈桢中的局部变量,如果被方法返回了,并且返回值是个引用数据类型,则可能导致线程安全问题
c、栈桢中的参数,如果传入的是引用数据类型,则可能导致线程安全问题
(4)栈中声明的变量,当超过变量的作用范围后,Java会自动释放掉为该变量所分配的内存空间
问题辨析:
1、垃圾回收是否涉及栈内存?
解答:不涉及,因为线程中栈桢是随着方法出栈而自动释放,不涉及垃圾回收。
2、栈内存分配越大越好吗?
解答:不是。栈内存过大意味着java程序中单个线程的内存过大,这样会影响整个java程序的线程并发数。
3、方法内的局部变量是否是线程安全的?
解答: 方法中的局部变量如果被方法返回了,并且返回值是一个引用数据类型,则可能导致线程安全问题。
三、本地方法栈 (Native Method Stack)
java为操作系统相关方法的执行所分配的内存,在java的JDK中存在大量的本地方法,如
Object中的clone、hashcode、wait、notify、notifyAll等都是的。
public native int hashCode();
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
本地方法都是与操作系统底层相关的api方法,java中不提供实现,而由操作系统来实现(c、c++的代码)
四、堆(Heap)
java中堆内存是jvm中管理的最大的一块内存区域, 用来存放所有的引用对象,堆也是垃圾回收器工作的区域。
命令工具:
1、jps 查看java进程,
2、jmap -heap [进程id] 查看某个时刻java进程内存快照 、
3、jconsole(图形化)
4、jvisualvm(图形化)
栈是线程私有的,堆是多个线程共享的
五、方法区(Method Area)
JVM规范中定义的方法区,只是一个逻辑的概念,在具体不同厂商和版本的实现中可能都有不同。
以Oracle的hotspot虚拟机为例,在JDK1.6中的方法区是通过堆中的PermGen(永久代)来实现的,
而JDK1.8中的方法区是通过Metaspace(元空间)来实现的。
运行时常量池;
1、常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
2、运行时常量池,常量池是class文件中的,当该类被加载,他的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
JDK1.6中的方法区中包含: class、classloader、运行期常量池(含StringTable)
JDK1.8中的方法区中包含: class、classloader、运行期常量池(其中的StringTable移到了堆中)
在JDK1.8中调用s.intern(),尝试将字符串放入串池中,返回值(s2)永远是指向串池中的地址,
如果放入成功了则s也指向了串池中的地址,没有放入成功则s还是执行原来的地址(堆中对象)
在JDK1.6中调用s.intern(),尝试将字符串放入串池中,返回值(s2)永远是指向常量池中的地址,
如果放入成功了,s会复制一份放入串池,s的指向不变(仍然是堆中对象)。没有放入成功s还是原来的地址。
JDK中设置元空间的大小
(1)JDK8中设置元空间内存最大值,默认是无限大(取决于操作系统内存)
-XX:MaxMetaspaceSize=128m #JDK8中设置最大的元内存空间128兆
(2)JDK7中设置永久代堆内存大小
-XX:MaxPermSize=128m
-Xss1m 指定栈的内存大小
-Xms128m 指定堆内存最小值
-Xmx256m 指定堆内存最大值
-XX:StringTableSize=2000 #增加桶的数量使StringTable性能增加