Java虚拟机 - 运行时数据区域

Posted Leida_wanglin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java虚拟机 - 运行时数据区域相关的知识,希望对你有一定的参考价值。

Java虚拟机 - 运行时数据区域

柯南语录:每天学习一小点,未来无限好。

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

作用:记住下一条jvm指令的执行地址
特点:
(1)线程私有的
(2)不会存在内存溢出(OutOfMemoryError)

2. Java虚拟机栈(Java Virtual Machine Stacks)

每个线程运行时所需要的内存,称虚拟机栈
每个栈由多个栈帧(Frame)组成,对应每次方法调用时所占用的内存
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
每一个方法被调用直至执行完毕的过程,对应着一个栈帧在虚拟机栈中从入栈到出栈的过程
特点:
(1)线程私有的
(2)生命周期与线程相同

栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息

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

不涉及,栈内存由一次次的方法调用产生的栈帧内存,栈帧内存在每一次方法调用结束后会弹出栈,会自动的被回收,所以不需要垃圾回收去处理。

2.2. 栈内存分配越大越好吗?

不是的,栈内存划分的越多,线程数就越少,一般使用系统默认的大小即可。

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

局部变量如果是线程私有的,因为方法内会为局部变量创建独立的栈帧,则不会造成线程安全问题,如果是线程共享的,例如static,会存在线程安全问题。
大白话:
(1)如果方法内局部变量没有逃离方法的作用访问,它是线程安全的
(2)如果是局部变量引用了对象,并逃离方法的作用方法,需要考虑线程安全

2.4. 栈内存溢出(StackOverFlowError)

(1)栈帧过多(例如方法递归调用,导致一直入栈)
(2)栈帧过大

2.5. 线程运行诊断

2.5.1. cpu占用过多

定位
(1)用top定位哪个进程对cpu的占用过高
(2)ps H -eo pd,tid,%cpu | grep 进程id(用ps命令进一步定位是哪个线程引起的cpu占用过高
(3)jstack 进程id 
		可以根据线程id找到有问题的线程,进一步定位到代码的源码行号

2.5.2. 程序运行很长时间没有结果

jstack 进程id
可能是由于死锁产生的 

3. 本地方法栈(Native Method Stacks)

与虚拟机栈非常相似,区别就是Java虚拟机栈为Java方法提供服务,而本地方法只为本地(Native)方法服务。
例如:Object中的方法许多是本地方法

4. Java堆(Java Heap)

通过new关键字,创建对象都会使用堆内存
特点:
(1)它是线程共享的,堆对象都需要考虑线程安全的问题
(2)有垃圾回收机制

4.1. 堆内存溢出(OutOfMemoryError)

存储的太多造成的,可以通过改变堆空间的大小
使用参数-Xmx和-Xms设定

4.2. 堆内存诊断

(1)jps工具:查看当前系统中有哪些java进程
(2)jmap工具(jmap -head 进程id ):查看堆内存占用情况
(3)jconsole工具:图形界面的,多功能的监测工具,可以连续监测

5. 方法区(Method Area)

与Java堆一样,线程共享
用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据

5.1. 方法区内存溢出

(1)1.8以前会导致永久代内存溢出 java.lang.OutOfMemoryError: PermGen space
可以设置永久代最大大小:-XX:MaxPermSize=8m 
(2)1.8以后会导致元空间内存溢出 java.lang.OutOfMemoryError: Metaspace
可以设置元空间最大大小:-XX:MaxMetaspaceSize=8m 

5.2. 运行时常量池(Runtime Constant Pool)

常量池:就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
运行时常量池:常量池是*.class文件中的,当该类被加载后,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址

运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是说,并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。

intern方法主动将串池中还没有的字符串对象放入串池
(1)1.8将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回
(2)1.6将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池,会把串池中的对象返回

常见面试题:

public class StringTable 
    //常量池中的信息,都会被加载到运行时常量池中,这时a b ab 都是常量池中的符号, 还没有变为java字符串对象
    //执行字节码命令会将a符号变为"a"字符串对象
    //执行字节码命令会将a符号变为"b"字符串对象
    //执行字节码命令会将a符号变为"ab"字符串对象

    public static void main(String[] args) 
        //StringTable["a", "b", "ab"] hashtable结构, 不能扩容

        String s1 = "a";
        String s2 = "b";

        String s3 = "ab";

        String s4 = s1 + s2;
        //new StringBuilder().append("a").append("b").toString()  new String("ab") 放在堆中

        String s5 = "a" + "b";
        //从池中找,javac在编译期间的优化,结果已经在编译期确定为ab

        String s6 = s4.intern();
        //intern 1.8将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回

        System.out.println(s3 == s4);
        //false

        System.out.println(s3 == s5);
        //true

        System.out.println(s3 == s6);
        //true
    


public class Demo 
    public static void main(String[] args) 

        String s = new String("a" ) + new String("b");

        String s2 = s.intern();

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

public class Demo 
    public static void main(String[] args) 

        String x = "ab";

        String s = new String("a" ) + new String("b");
		
        String s2 = s.intern();
        //常量池中"ab"已经存在了,所以s2对象用的是常量池中的,而s仍然是堆中的,所以返回false

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

6. 直接内存(Direct Memory,不属于JVM运行时数据区)

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


以上是关于Java虚拟机 - 运行时数据区域的主要内容,如果未能解决你的问题,请参考以下文章

JVM运行时数据区域

jvm-运行时数据区域(《深入理解java虚拟机》)

深入理解Java虚拟机:运行时数据区域

Java虚拟机结构原理与运行时数据区域

Java虚拟机 - 结构原理与运行时数据区域

Java虚拟机之运行时数据区域