栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

深度理解JVM

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

深度理解JVM

文章目录
    • 1.**什么是** **JVM ?**
    • 2.JVM学习路线
    • 3.JVM内存结构
      • 3.1程序计数器
      • 3.2虚拟机栈
        • 3.3栈内存溢出
      • 3.4线程运行诊断
      • 3.5本地方法栈
      • 3.6堆
      • 3.7堆内存溢出
        • 3.7.1堆内存诊断
      • 3.8方法区
        • 3.8.1方法区--常量池
        • 3.8.2方法区--运行时常量池

1.什么是 JVM ?
定义:
Java Virtual Machine   :java 程序的运行环境(java 二进制字节码的运行环境)

好处:
(1)一次编写,到处运行
(2)自动内存管理,垃圾回收功能
(3)数组下标越界检查
(4)多态

比较:
jvm jre jdk关系图

2.JVM学习路线

(1)classLoader(类加载器):把java源代码编译成二进制字节码
(2)Method Area(方法区):类都是放在方法区的
(3)Heap(堆):类创建的实例都是放在堆里面的
(4)堆里面的实例化对象调用方法时候,又会用到JVM(虚拟机栈)
								PC Register(程序计数器)
								Native Method Stacks(本地方法栈)
(5)Interpreter(解释器):方法执行时,每行代码由执行引擎中的解释器逐行解释
(6)JIT Complier:针对于频繁编译的代码进行优化执行
(7)GC(垃圾回收):会对堆里面一些不再被引用的对象进行垃圾回收
(8)本地方法接口:java代码不方便实现的功能,必须调用底层操作系统的功能
3.JVM内存结构 3.1程序计数器

定义:Program Counter Register程序计数器(寄存器)

作用:

如上图所示:

java源代码经过类加载器编译成二进制字节码,再经过编译器编译成机器码,最后交给CPU
处理,在此过程中,程序计数器就记住下一条要执行的地址3,3的下一条4,没有程序计数器,就不知道下一条要执行什么,程序计数器使用的是CPU的寄存器

程序计数器的特点:

(1)是线程私有的

解释:学java都知道多线程,所以每个线程都有自己私有的程序计数器
如下图:有两个线程,数字代表他们的地址,线程一执行到地址4的时候,时间片用完了,
自然会去执行线程二,这时程序计数器1记住下一条要执行的地址是5,等线程二的
时间片用完了,线程一就会从计数器的5开始执行,如此反复

(2)不会存在内存溢出

3.2虚拟机栈

特点:先进后出

栈—线程运行需要的内存空间

一个栈可以看成由多个栈帧组成的

栈帧:每个方法运行时需要的内存

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

问题讨论

(1)垃圾回收是否设计栈内存?
不需要,因为栈内存无非就是一次次的方法调用所产生的栈帧内存,栈帧内存在每一次方法调用完,都会被弹出栈,也就是会被自动回收掉,根本不要用垃圾回收

(2)栈内存分配的越大越好吗?
不是,栈内存分配过大,会导致线程数变少,一般采用系统默认的大小就可以了

(3)方法内的局部变量是否线程安全?
看一个变量是不是线程安全的,其实我们就要看多个线程对这个变量是共享的还是私有的

得出结论:
如果方法局部变量没有逃离方法的作用访问,他是线程安全的
如果是局部变量引用了对象,并逃离方法的作用方法,需要考虑线程安全
3.3栈内存溢出

1.栈帧过多导致栈内存溢出


public class TestStack {
    private static  int count;

    public static void main(String[] args) {
        try {
            method();
        }catch (Throwable e){//抛出异常
            e.printStackTrace();
            System.out.println(count);//看多少次栈会溢出
        }
    }

    private static void method(){
        count++;
        method(); //自己调用自己,且没有停止条件
    }

}

可以看到执行了36341次,栈溢出

2.栈帧过大(不太容易出现)

3.4线程运行诊断
上面将了许多关于虚拟机栈的知识,我们知道,线程和栈是息息相关的,所以接下来讲讲
线程
案例一:CPU占用过多(陷入死循环)
linux环境下
用top定位哪个进程对cpu的占用过高
ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
jstack 进程id
可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号
案例二:程序运行很长时间没有结果(死锁情况)

3.5本地方法栈

java虚拟机调用一些本地方法时,需要为为本地方法提供的内存空间

本地方法:java代码是有一定限制的,所以就需要一些C或者C++写的本地方法来与本地系统或者底层API打交道,java来调用

常见的本地方法:

java.Object.clone

hashcode()

notify()

notifyAll()

wait()

3.6堆

前面讲的程序计数器,虚拟机栈,本地方法栈,他们都有一个共同的特点,那就是 线程私有的,而这里讲的堆是线程共享的

Heap堆

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

特点:

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

有垃圾回收机制

-Xmx8m:设置堆空间为8MB

3.7堆内存溢出

堆内存虽然有垃圾回收机制,但是如果我们不断创建新的对象,并且被引用,那就回涉及到堆内存溢出

 import java.util.ArrayList;
     import java.util.List;
     
       public class Heap {
       public static void main(String[] args) {
           int i = 0;
           try {
               List list = new ArrayList<>();
               String a = "hello";
               while (true) {
                   list.add(a); // hello, hellohello, hellohellohellohello 
                   a = a + a;  // hellohellohellohello
                   i++;
               }
           } catch (Throwable e) {
            e.printStackTrace();
            System.out.println(i);
        }
    }
    }
 

结果:26次就溢出

3.7.1堆内存诊断
  1. jps 工具
    查看当前系统中有哪些 java 进程
  2. jmap 工具
    查看堆内存占用情况 jmap - heap 进程id
  3. jconsole 工具
    图形界面的,多功能的监测工具,可以连续监测
在java控制台输入jconsole,就可以查看当前运行,堆内存的使用情况

代码举例:

​```java

public class Die {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("1...");
        Thread.sleep(30000);
        byte[] array = new byte[1024 * 1024 * 10]; // 10 Mb
        System.out.println("2...");
        Thread.sleep(20000);
        array = null;
        System.gc();
        System.out.println("3...");
        Thread.sleep(1000000L);
    }
}

案例:垃圾回收后,内存占用依然很高

使用jvisualvm命令,可视化虚拟机内容

import java.util.ArrayList;
import java.util.List;


public class HeapStudent {

    public static void main(String[] args) throws InterruptedException {
        List students = new ArrayList<>();
        for (int i = 0; i < 200; i++) {
            students.add(new Student());//不断往ArrayList添加对象

        }
        Thread.sleep(1000000000L);
    }
}
class Student {
    private byte[] big = new byte[1024 * 1024];
}

可以看出java.util.ArrayList占用内存最大

3.8方法区

组成对比1.6–1.8

可以看出1.8以前用的是永久代

1.8用的是元空间,而且用的是本地内存

* 演示永久代内存溢出 java.lang.OutOfMemoryError: PermGen space 
* -XX:MaxPermSize=8m

* 演示元空间内存溢出 java.lang.OutOfMemoryError: metaspace 
* -XX:MaxmetaspaceSize=8m

场景:spring,mybatis都会涉及到内存溢出(1.8以后就很难了,因为用的本地内存)

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

二进制字节码(类的基本信息,常量池,类方法定义,包含了虚拟机指令)

二进制字节码经过反编译,可以编译成人类基本看得懂东西

经过javac 编译然后 反编译命令:javap -v 类

常量池:

3.8.2方法区–运行时常量池

运行时常量池,常量池是*.class文件中的,当该类被加载,他的常量池就会被放入运行时常量池,并把里面的符号地址变为真实地址

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/572175.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号