JVM 内存结构

一、程序计数器

1.1 定义

        当前线程所执行的字节码的行号指示器,用于记住下一条 jvm 的执行地址。

1.2 特点

        1、线程私有

        2、不存在内存溢出

二、虚拟机栈

2.1 定义

        每个线程运行时所需要的内存,称为虚拟机栈。

2.2 特点

        1、每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存。

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

        3、线程私有

2.3 问题辨析

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

        不涉及,因为方法执行完毕后,内存就被回收了。

2.3.2 栈内存分配是否越大越好

        不是,它和物理内存有关,假设当前物理内存为 500M,栈的内存为 1M,那么他就可以存放 500 个线程,如果此时增加栈内存为 2M,那么此时就只能存放 250 个线程了,所以不是越大越好。

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

        看情况,如果方法内局部变量没有逃离方法的作用访问,它是线程安全的。如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全。

2.4 栈内存溢出

2.4.1 栈帧过多导致内存溢出

        方法的连续调用且无法释放资源,即栈帧都被加载到虚拟机栈里面,就会出现虚拟机栈的栈帧过多导致内存溢出的情况。比如下面的代码就会导致内存溢出

// 配置栈空间参数:-Xss16k
// 递归调用,方法一直调用自己就会导致栈内存溢出,打印 count 来记录到底递归了多少次
public class Demo1_2 {private static int count;public static void main(String[] args) {try {method1();} catch (Throwable e) {e.printStackTrace();System.out.println(count);}}private static void method1() {count++;method1();}
}

2.4.2 栈帧过大导致内存溢出

        如果栈帧特别的大,把我的虚拟机栈给撑满了也会导致内存溢出, 不过这种情况很少见。

2.5 线程运行诊断

2.5.1 cpu 占用过高

        首先在 linux 环境里面运行一段代码,如下所示:

public class Demo1_16 {public static void main(String[] args) {new Thread(null, () -> {System.out.println("1...");while(true) {}}, "thread1").start();new Thread(null, () -> {System.out.println("2...");try {Thread.sleep(1000000L);} catch (InterruptedException e) {e.printStackTrace();}}, "thread2").start();new Thread(null, () -> {System.out.println("3...");try {Thread.sleep(1000000L);} catch (InterruptedException e) {e.printStackTrace();}}, "thread3").start();}
}

        然后在 linux 环境下启动这段代码,用后台的方式运行,命令如下,此时我们就模拟了生产环境 cpu 占用过高的场景。

nohup java cn.itcast.jvm.t1.stack.Demo1_16 & 

        第一步:输入 top 命令查看当前 linux 进程的 cpu 占用情况,如下所示,我们发现我们的 java 进程的 cpu 几乎快要满了。

        第二步:使用 ps 命令来查看进程里面到底是哪个线程引起的 cpu 占用过高,命令如下:

# H 参数表示输出进程树
# -eo 参数表示后面需要输出的东西,比如 pid,tid,%cpu 表示对 cpu 的占用情况
# grep 表示筛选指定的进程 id
ps H -eo pid,tid,%cpu | grep 进程id

        第三步:使用 jstack工具,他会把我们这个进程中的所有线程 id 给我们列出来

jstack 进程 id
[root@localhost src]# jstack 67951
2023-09-06 01:58:54
Full thread dump OpenJDK 64-Bit Server VM (25.262-b10 mixed mode):"Attach Listener" #13 daemon prio=9 os_prio=0 tid=0x00007f3424001000 nid=0x10c2f waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"DestroyJavaVM" #12 prio=5 os_prio=0 tid=0x00007f346804b800 nid=0x10970 waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"thread3" #11 prio=5 os_prio=0 tid=0x00007f34681ee800 nid=0x10980 waiting on condition [0x00007f344fad1000]java.lang.Thread.State: TIMED_WAITING (sleeping)at java.lang.Thread.sleep(Native Method)at cn.itcast.jvm.t1.stack.Demo1_16.lambda$main$2(Demo1_16.java:29)at cn.itcast.jvm.t1.stack.Demo1_16$$Lambda$3/135721597.run(Unknown Source)at java.lang.Thread.run(Thread.java:748)"thread2" #10 prio=5 os_prio=0 tid=0x00007f34681ec800 nid=0x1097f waiting on condition [0x00007f344fbd2000]java.lang.Thread.State: TIMED_WAITING (sleeping)at java.lang.Thread.sleep(Native Method)at cn.itcast.jvm.t1.stack.Demo1_16.lambda$main$1(Demo1_16.java:20)at cn.itcast.jvm.t1.stack.Demo1_16$$Lambda$2/303563356.run(Unknown Source)at java.lang.Thread.run(Thread.java:748)"thread1" #9 prio=5 os_prio=0 tid=0x00007f34681ea800 nid=0x1097e runnable [0x00007f34541ab000]java.lang.Thread.State: RUNNABLEat cn.itcast.jvm.t1.stack.Demo1_16.lambda$main$0(Demo1_16.java:11)at cn.itcast.jvm.t1.stack.Demo1_16$$Lambda$1/471910020.run(Unknown Source)at java.lang.Thread.run(Thread.java:748)"Service Thread" #8 daemon prio=9 os_prio=0 tid=0x00007f3468136800 nid=0x1097c runnable [0x0000000000000000]java.lang.Thread.State: RUNNABLE"C1 CompilerThread2" #7 daemon prio=9 os_prio=0 tid=0x00007f3468123800 nid=0x1097b waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"C2 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f3468121800 nid=0x1097a waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f3468114000 nid=0x10979 waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f3468112000 nid=0x10978 runnable [0x0000000000000000]java.lang.Thread.State: RUNNABLE"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f34680e4000 nid=0x10977 in Object.wait() [0x00007f34548e3000]java.lang.Thread.State: WAITING (on object monitor)at java.lang.Object.wait(Native Method)- waiting on <0x00000000edc08ee0> (a java.lang.ref.ReferenceQueue$Lock)at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)- locked <0x00000000edc08ee0> (a java.lang.ref.ReferenceQueue$Lock)at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f34680df800 nid=0x10976 in Object.wait() [0x00007f34549e4000]java.lang.Thread.State: WAITING (on object monitor)at java.lang.Object.wait(Native Method)- waiting on <0x00000000edc06c00> (a java.lang.ref.Reference$Lock)at java.lang.Object.wait(Object.java:502)at java.lang.ref.Reference.tryHandlePending(Reference.java:191)- locked <0x00000000edc06c00> (a java.lang.ref.Reference$Lock)at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)"VM Thread" os_prio=0 tid=0x00007f34680d5800 nid=0x10975 runnable "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f346805e000 nid=0x10971 runnable "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f3468060000 nid=0x10972 runnable "GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007f3468062000 nid=0x10973 runnable "GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007f3468063800 nid=0x10974 runnable "VM Periodic Task Thread" os_prio=0 tid=0x00007f346814b800 nid=0x1097d waiting on condition JNI global references: 311[root@localhost src]# 

        第四步:根据我们在第二步找到的线程 id 67966,这个是十进制的,而我们第三步输出的线程 id 为十六进制的,需要将 67966 转换为十六进制的形式为:1097e;然后查看查看第三步的线程 id 为 1097e 的线程,如下所示:

        此时我们就定位到了问题出现了 Thread-1 的这个线程这里,他的线程状态是 Runnable ,处于一直运行的状态,就是他导致的 cpu 占用过高的问题,从它的下面就可以看出来是第 11 行的代码出现了问题,接下来就可以解决了。

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

        想象一个场景,比如点击新增按钮长时间没有返回,或者其他的那种长时间服务器没有反应的情况,该怎么办呢?我们用代码来模拟下这种场景,代码如下所示:

package cn.itcast.jvm.t1.stack;/*** 演示线程死锁*/
class A{};
class B{};
public class Demo1_3 {static A a = new A();static B b = new B();public static void main(String[] args) throws InterruptedException {new Thread(()->{synchronized (a) {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (b) {System.out.println("我获得了 a 和 b");}}}).start();Thread.sleep(1000);new Thread(()->{synchronized (b) {synchronized (a) {System.out.println("我获得了 a 和 b");}}}).start();}}

        然后在 linux 环境下启动这段代码,用后台的方式运行,命令如下

[root@localhost src]# nohup java cn.itcast.jvm.t1.stack.Demo1_3 &
[1] 69772

        第一步:使用 jstack 命令查看当前进程里面的线程情况,我们可以观察下最后的几行代码

[root@localhost src]# jstack 69772
2023-09-06 02:15:27
Full thread dump OpenJDK 64-Bit Server VM (25.262-b10 mixed mode):"Attach Listener" #12 daemon prio=9 os_prio=0 tid=0x00007f513c001000 nid=0x110b8 waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"DestroyJavaVM" #11 prio=5 os_prio=0 tid=0x00007f517c04b800 nid=0x1108d waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"Thread-1" #10 prio=5 os_prio=0 tid=0x00007f517c20c800 nid=0x1109d waiting for monitor entry [0x00007f5166fc6000]java.lang.Thread.State: BLOCKED (on object monitor)at cn.itcast.jvm.t1.stack.Demo1_3.lambda$main$1(Demo1_3.java:30)- waiting to lock <0x00000000edc64ab8> (a cn.itcast.jvm.t1.stack.A)- locked <0x00000000edc65dc0> (a cn.itcast.jvm.t1.stack.B)at cn.itcast.jvm.t1.stack.Demo1_3$$Lambda$2/303563356.run(Unknown Source)at java.lang.Thread.run(Thread.java:748)"Thread-0" #9 prio=5 os_prio=0 tid=0x00007f517c20a800 nid=0x1109c waiting for monitor entry [0x00007f51670c7000]java.lang.Thread.State: BLOCKED (on object monitor)at cn.itcast.jvm.t1.stack.Demo1_3.lambda$main$0(Demo1_3.java:22)- waiting to lock <0x00000000edc65dc0> (a cn.itcast.jvm.t1.stack.B)- locked <0x00000000edc64ab8> (a cn.itcast.jvm.t1.stack.A)at cn.itcast.jvm.t1.stack.Demo1_3$$Lambda$1/471910020.run(Unknown Source)at java.lang.Thread.run(Thread.java:748)"Service Thread" #8 daemon prio=9 os_prio=0 tid=0x00007f517c136800 nid=0x1109a runnable [0x0000000000000000]java.lang.Thread.State: RUNNABLE"C1 CompilerThread2" #7 daemon prio=9 os_prio=0 tid=0x00007f517c123800 nid=0x11099 waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"C2 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f517c121800 nid=0x11098 waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f517c114000 nid=0x11097 waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f517c112000 nid=0x11096 runnable [0x0000000000000000]java.lang.Thread.State: RUNNABLE"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f517c0e4000 nid=0x11095 in Object.wait() [0x00007f5167bfa000]java.lang.Thread.State: WAITING (on object monitor)at java.lang.Object.wait(Native Method)- waiting on <0x00000000edc08ee0> (a java.lang.ref.ReferenceQueue$Lock)at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)- locked <0x00000000edc08ee0> (a java.lang.ref.ReferenceQueue$Lock)at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f517c0df800 nid=0x11094 in Object.wait() [0x00007f5167cfb000]java.lang.Thread.State: WAITING (on object monitor)at java.lang.Object.wait(Native Method)- waiting on <0x00000000edc06c00> (a java.lang.ref.Reference$Lock)at java.lang.Object.wait(Object.java:502)at java.lang.ref.Reference.tryHandlePending(Reference.java:191)- locked <0x00000000edc06c00> (a java.lang.ref.Reference$Lock)at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)"VM Thread" os_prio=0 tid=0x00007f517c0d5800 nid=0x11093 runnable "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f517c05e000 nid=0x1108f runnable "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f517c060000 nid=0x11090 runnable "GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007f517c062000 nid=0x11091 runnable "GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007f517c063800 nid=0x11092 runnable "VM Periodic Task Thread" os_prio=0 tid=0x00007f517c13b800 nid=0x1109b waiting on condition JNI global references: 310Found one Java-level deadlock:
=============================
"Thread-1":waiting to lock monitor 0x00007f5148002178 (object 0x00000000edc64ab8, a cn.itcast.jvm.t1.stack.A),which is held by "Thread-0"
"Thread-0":waiting to lock monitor 0x00007f5148006218 (object 0x00000000edc65dc0, a cn.itcast.jvm.t1.stack.B),which is held by "Thread-1"Java stack information for the threads listed above:
===================================================
"Thread-1":at cn.itcast.jvm.t1.stack.Demo1_3.lambda$main$1(Demo1_3.java:30)- waiting to lock <0x00000000edc64ab8> (a cn.itcast.jvm.t1.stack.A)- locked <0x00000000edc65dc0> (a cn.itcast.jvm.t1.stack.B)at cn.itcast.jvm.t1.stack.Demo1_3$$Lambda$2/303563356.run(Unknown Source)at java.lang.Thread.run(Thread.java:748)
"Thread-0":at cn.itcast.jvm.t1.stack.Demo1_3.lambda$main$0(Demo1_3.java:22)- waiting to lock <0x00000000edc65dc0> (a cn.itcast.jvm.t1.stack.B)- locked <0x00000000edc64ab8> (a cn.itcast.jvm.t1.stack.A)at cn.itcast.jvm.t1.stack.Demo1_3$$Lambda$1/471910020.run(Unknown Source)at java.lang.Thread.run(Thread.java:748)Found 1 deadlock.

        第二步:从下面看就可以发现了死锁的说明,下面就是具体哪两个线程发生了死锁,然后解决就可以了。

三、本地方法栈

3.1 定义

        jvm 为使用 native 方法提供所需要的内存,称为本地方法栈。

3.2 特点

        线程私有

四、堆

4.1 定义

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

4.2 特点

        1、线程共享的,堆中的对象需要考虑线程安全的问题

        2、有垃圾回收机制。

4.3 堆内存溢出

        下面的代码就可以模拟堆内存溢出的场景,如下所示:

/* 配置堆空间参数:-Xms8m  */
public class Demo1_5 {public static void main(String[] args) {int i = 0;try {List<String> list = new ArrayList<>();String a = "hello";while (true) {list.add(a);a = a + a;  i++;}} catch (Throwable e) {e.printStackTrace();System.out.println(i);}}
}

 4.4 堆内存诊断

        首先运行下面的代码用于模拟堆内存特别大的场景

public class Demo1_4 {public static void main(String[] args) throws InterruptedException {System.out.println("1...");Thread.sleep(30000);byte[] array = new byte[1024 * 1024 * 10]; // 10 MbSystem.out.println("2...");Thread.sleep(20000);array = null;// 手动触发 gc 操作System.gc();System.out.println("3...");Thread.sleep(1000000L);}
}

4.4.1 jmap 工具

        首先查看当前系统有哪些 java 进程,并将他们的进程编号显示出来,使用命令 jps,如下所示,找到我们正在运行的 java 进程编号 24764

        然后使用 jmap 命令查看堆内存的使用情况,不过查询的只是某一时刻的情况,语法如下:

jmap -heap 进程id
# 命令一共需要运行三次,对应程序三种不同的状态# 当程序运行输出 1... 的时候第一次执行
# 此时的状态还没有向堆内存中存放对象
C:\Users\Administrator\Desktop\资料 解密JVM\代码\jvm\jvm>jmap -heap 24764
Attaching to process ID 24764, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11using thread-local object allocation.
Parallel GC with 8 thread(s)Heap Configuration:MinHeapFreeRatio         = 0MaxHeapFreeRatio         = 100MaxHeapSize              = 4083154944 (3894.0MB)NewSize                  = 84934656 (81.0MB)MaxNewSize               = 1361051648 (1298.0MB)OldSize                  = 170917888 (163.0MB)NewRatio                 = 2SurvivorRatio            = 8MetaspaceSize            = 21807104 (20.796875MB)CompressedClassSpaceSize = 1073741824 (1024.0MB)MaxMetaspaceSize         = 17592186044415 MBG1HeapRegionSize         = 0 (0.0MB)Heap Usage:
PS Young Generation
Eden Space:capacity = 63963136 (61.0MB)used     = 6434872 (6.136772155761719MB)free     = 57528264 (54.86322784423828MB)10.060282222560195% used
From Space:capacity = 10485760 (10.0MB)used     = 0 (0.0MB)free     = 10485760 (10.0MB)0.0% used
To Space:capacity = 10485760 (10.0MB)used     = 0 (0.0MB)free     = 10485760 (10.0MB)0.0% used
PS Old Generationcapacity = 170917888 (163.0MB)used     = 0 (0.0MB)free     = 170917888 (163.0MB)0.0% used3179 interned Strings occupying 281368 bytes.# 当程序运行输出 2... 的时候第二次执行
# 此时向堆内存中放了一个对象,占用了新生代的10M的内存
C:\Users\Administrator\Desktop\资料 解密JVM\代码\jvm\jvm>jmap -heap 24764
Attaching to process ID 24764, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11using thread-local object allocation.
Parallel GC with 8 thread(s)Heap Configuration:MinHeapFreeRatio         = 0MaxHeapFreeRatio         = 100MaxHeapSize              = 4083154944 (3894.0MB)NewSize                  = 84934656 (81.0MB)MaxNewSize               = 1361051648 (1298.0MB)OldSize                  = 170917888 (163.0MB)NewRatio                 = 2SurvivorRatio            = 8MetaspaceSize            = 21807104 (20.796875MB)CompressedClassSpaceSize = 1073741824 (1024.0MB)MaxMetaspaceSize         = 17592186044415 MBG1HeapRegionSize         = 0 (0.0MB)Heap Usage:
PS Young Generation
Eden Space:capacity = 63963136 (61.0MB)used     = 16920648 (16.13678741455078MB)free     = 47042488 (44.86321258544922MB)26.453749859919313% used
From Space:capacity = 10485760 (10.0MB)used     = 0 (0.0MB)free     = 10485760 (10.0MB)0.0% used
To Space:capacity = 10485760 (10.0MB)used     = 0 (0.0MB)free     = 10485760 (10.0MB)0.0% used
PS Old Generationcapacity = 170917888 (163.0MB)used     = 0 (0.0MB)free     = 170917888 (163.0MB)0.0% used3180 interned Strings occupying 281416 bytes.# 当程序运行输出 3... 的时候第三次执行
# 由于对象被赋值为 null 且手动触发了一次 GC,导致内存又都被回收起来了
C:\Users\Administrator\Desktop\资料 解密JVM\代码\jvm\jvm>jmap -heap 24764
Attaching to process ID 24764, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11using thread-local object allocation.
Parallel GC with 8 thread(s)Heap Configuration:MinHeapFreeRatio         = 0MaxHeapFreeRatio         = 100MaxHeapSize              = 4083154944 (3894.0MB)NewSize                  = 84934656 (81.0MB)MaxNewSize               = 1361051648 (1298.0MB)OldSize                  = 170917888 (163.0MB)NewRatio                 = 2SurvivorRatio            = 8MetaspaceSize            = 21807104 (20.796875MB)CompressedClassSpaceSize = 1073741824 (1024.0MB)MaxMetaspaceSize         = 17592186044415 MBG1HeapRegionSize         = 0 (0.0MB)Heap Usage:
PS Young Generation
Eden Space:capacity = 63963136 (61.0MB)used     = 1279280 (1.2200164794921875MB)free     = 62683856 (59.77998352050781MB)2.000027015560963% used
From Space:capacity = 10485760 (10.0MB)used     = 0 (0.0MB)free     = 10485760 (10.0MB)0.0% used
To Space:capacity = 10485760 (10.0MB)used     = 0 (0.0MB)free     = 10485760 (10.0MB)0.0% used
PS Old Generationcapacity = 170917888 (163.0MB)used     = 1076400 (1.0265350341796875MB)free     = 169841488 (161.9734649658203MB)0.6297760945887654% used3166 interned Strings occupying 280424 bytes.

4.4.2 jconsole 工具

        图形界面的,多功能的检测工具,可以连续监测,重新启动工程,并在控制台输入命令:jconsole,如下所示:

        接下来就等于启动了 jconsole 软件 ,就可以查看堆内存的实时变化信息了。

4.4.3 jvisualvm 工具

        首先模拟一个场景,垃圾回收后,内存占用仍然很高,代码如下:

/*** 演示查看对象个数 堆转储 dump*/
public class Demo1_13 {public static void main(String[] args) throws InterruptedException {List<Student> students = new ArrayList<>();for (int i = 0; i < 200; i++) {students.add(new Student());
//            Student student = new Student();}Thread.sleep(1000000000L);}
}
class Student {private byte[] big = new byte[1024*1024];
}

         将代码运行起来,启动 jvisualvm 工具,如下所示:

五、方法区

5.1 定义

        方法区在 jvm 启动的时候被创建,逻辑上是堆的一个组成部分,但是不同的 jvm 厂商在实现的时候不一定会遵从这个逻辑上的定义。

        方法区存储了跟类的结构有关的信息,包括运行时常量池、成员变量、方法数据、成员方法和构造器方法代码部分,还有一些特殊方法(类的构造器),并且是线程共享的。

        方法区在申请内存时发现不足了,也会报 outOfMemoryError。

5.2 特点

        以 Hotspot jdk1.6 jdk1.8 的内存结构为例,方法区只是一个概念上的东西。

        在  jdk1.6 里面方法区是使用永久代来实现的,这个永久代里面包含了运行时常量池、类的信息、类加载器等。

        到了 jdk1.8 以后,永久代的这个实现就被废弃了,变成了使用元空间,他存储了类的信息、类加载器、运行时常量池,但是他已经不占用堆内存了,换句话说就是不由我们的 jvm 来管理了,它被移出到我们的本地内存(操作系统内存)当中,但 StringTable(串池) 被移动到了堆里面。

5.3 方法区内存溢出

5.3.1 jdk1.8 以前会导致永久代内存溢出

        下面的代码可以复现永久代内存溢出的情况,首先我们需要将 jdk 调成 1.6,然后修改下 jvm 的参数,命令如下:

# 限制永久代最大的内存,为了更好的复现问题
-XX:MaxPermSize=8m

         执行下面的代码

import com.sun.xml.internal.ws.org.objectweb.asm.ClassWriter;
import com.sun.xml.internal.ws.org.objectweb.asm.Opcodes;/*** 演示永久代内存溢出  java.lang.OutOfMemoryError: PermGen space* -XX:MaxPermSize=8m*/
public class Demo1_6 extends ClassLoader {public static void main(String[] args) {int j = 0;try {Demo1_6 test = new Demo1_6();for (int i = 0; i < 100000; i++, j++) {ClassWriter cw = new ClassWriter(0);cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);byte[] code = cw.toByteArray();test.defineClass("Class" + i, code, 0, code.length);}} finally {System.out.println(j);}}
}

        根据提示的错误信息我们可以看出来,提示永久代的内存溢出了 

5.3.2 jdk1.8 以后会导致元空间内存溢出

        首先将 jdk 调成 1.8,然后执行下面的代码

import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;/*** 演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace* -XX:MaxMetaspaceSize=8m*/
public class Demo1_8 extends ClassLoader { // 可以用来加载类的二进制字节码public static void main(String[] args) {int j = 0;try {Demo1_8 test = new Demo1_8();for (int i = 0; i < 10000; i++, j++) {// ClassWriter 作用是生成类的二进制字节码ClassWriter cw = new ClassWriter(0);// 版本号, public, 类名, 包名, 父类, 接口cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);// 返回 byte[]byte[] code = cw.toByteArray();// 执行了类的加载test.defineClass("Class" + i, code, 0, code.length); // Class 对象}} finally {System.out.println(j);}}
}

        输出结果如下,并没有报错,因为元空间默认情况下使用的是系统的内存,而且没有设置它的上限。

         我们修改下 jvm 的参数,命令如下:

# 限制元空间最大的内存,为了更好的复现问题
-XX:MaxMetaspaceSize=8m

        再次运行程序,发现提示元空间的内存溢出了。

5.4 运行时常量池

        一个 java 类想要运行,就需要先将其编译成 .class 文件(二进制字节码),那这个 .class 文件有哪些部分组成呢?一般来说由三部分组成:类的基本信息、常量池、类方法定义包含了虚拟机指令。

5.4.1 常量池

        常量池就是一张常量的表,虚拟机的指令根据这张常量表找到要执行的类名、方法名、参数类型和字面量等信息。常量池是存储在 *.class 文件中的。

5.4.2 运行时常量池

        当我们所有的类被加载到虚拟机中以后,每一个的类的常量池信息就都会被放入运行时常量池,并把里面的符号地址变为真实地址。运行时常量池是在内存当中存储的。

5.5 StringTable 串池

5.5.1 编译分析

        先看一段代码,然后我们分析下它的执行逻辑

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

        刚才我们说过,常量池中的信息在运行的时候都会被加载到运行时常量池中,这时 abab 都是常量池中的符号  还没有成为 java 字符串对象,需要等到代码执行到那一行才会变为对象。

        当执行到 String s1 = "a" 的时候,首先先去 StringTable 里面看看有没有 a 对象,如果有就直接使用,如果没有,则把 "a" 放进去,然后再返回,执行完一条语句判断一次。

        StringTable hashtable 结构,不能扩容。

// s1 和s2 是变量,引用的值有可能在执行的使用发生变化
// 所以在堆内存中新创建的对象
String s4 = s1 + s2;  
System.out.println(s3 == s4); //false

5.5.2 编译期自动优化 

        因为 s3 ”ab“ 值在 StringTable 中,s4 在堆内存中,所以不等。

// javac 在编译期的优化
String s5 = "a" + "b";  
System.out.println(s3 == s5); // true

        编译器认为:”a“ ”b“ 是常量,内容不会变了,所以他俩拼接的结果是固定的,那我就可以在编译期间得到结果为 ”ab“。

5.5.3 jdk1.8 的 intern 方法

        intern() 方法:将这个字符串对象尝试放入串池,如果有则不会放入,如果没有则放入串池,最会会把串池中的对象返回。

public static void main(String[] args) {String s = new String("a") + new String("b");// 执行完词条语句的存储状态是为:// stringtable里面:["a","b"]// 堆内存里面:[new String("a")、new String("b")、new String("ab")]String s2 = s.intern();// 将这个字符串对象尝试放入串池,如果有则不会放入,如果没有则放入串池,最会会把串池中的对象返回// 执行完此条语句的存储状态是:// stringtable里面:["a","b","ab"]// 堆内存里面:[new String("a")、new String("b")、new String("ab")]// 并且 s2 返回的对象是串池中的 "ab" 对象System.out.println(s2 =="ab"); // trueSystem.out.println(s =="ab"); // true}
public static void main(String[] args) {String x= "ab";String s = new String("a") + new String("b");// 执行完词条语句的存储状态是为:// stringtable里面:["ab","a","b"]// 堆内存里面:[new String("a")、new String("b")、new String("ab")]String s2 = s.intern();// 将这个字符串对象尝试放入串池,如果有则不会放入,如果没有则放入串池,最会会把串池中的对象返回// 执行完此条语句的存储状态是:// stringtable里面:["ab","a","b"]// 堆内存里面:[new String("a")、new String("b")、new String("ab")]// 并且 s2 返回的对象是串池中的 "ab" 对象System.out.println(s2 == x); // trueSystem.out.println(s == x); // false ,因为没放进去,s引用的还是堆内存的对象}

5.5.4 jdk1.6 的 intern 方法

        intern() 方法:将这个字符串对象尝试放入串池,如果有则不会放入,如果没有则拷贝一份放入串池,最会会把串池中的对象返回

public static void main(String[] args) {String s = new String("a") + new String("b");// 执行完词条语句的存储状态是为:// stringtable里面:["a","b"]// 堆内存里面:[new String("a")、new String("b")、new String("ab")]String s2 = s.intern();// 将这个字符串对象尝试放入串池,如果有则不会放入,如果没有则拷贝一份放入串池,最会会把串池中的对象返回// 执行完此条语句的存储状态是:// stringtable里面:["a","b","ab"]// 堆内存里面:[new String("a")、new String("b")、new String("ab")]// 并且 s2 返回的对象是串池中的 "ab" 对象System.out.println(s2 =="ab"); // trueSystem.out.println(s =="ab"); // false,因为是复制了一份放入串池,所以 s 引用的还是堆内存的对象}
public static void main(String[] args) {String x= "ab";String s = new String("a") + new String("b");// 执行完词条语句的存储状态是为:// stringtable里面:["ab","a","b"]// 堆内存里面:[new String("a")、new String("b")、new String("ab")]String s2 = s.intern();// 将这个字符串对象尝试放入串池,如果有则不会放入,如果没有则拷贝一份放入串池,最会会把串池中的对象返回// 执行完此条语句的存储状态是:// stringtable里面:["ab","a","b"]// 堆内存里面:[new String("a")、new String("b")、new String("ab")]// 并且 s2 返回的对象是串池中的 "ab" 对象System.out.println(s2 == x); // trueSystem.out.println(s == x); // false ,因为没放进去,s 引用的还是堆内存的对象}

5.6 StringTable 位置

        jdk1.6 常量出是存储在永久代里面,而从 jdk1.7 开始就转移到了堆中,因为永久代的内存回收效率很低,只有 full gc 的时候才会触发永久代的垃圾回收,就会导致 StringTable 的回收效率不高。到了  jdk1.7 之后,放在堆里面只需要 minor gc 就可以触发垃圾回收。

5.7 StringTable 性能调优

        1、调整  -XX:StringTableSize=桶个数

        2、考虑将字符串对象是否入池

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/128029.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OceanBase 里的 schema 是什么?

李博洋 OceanBase 技术部研发工程师。 OceanBase 开源社区里经常会看到一些类似于 “ schema 是什么” 的疑问&#xff1a; 很多同学经常会误以为在 OceanBase 里&#xff0c;schema 只是 database 的同义词&#xff0c;这次分享就从 schema 是什么这个问题稍微展开聊一下。 首…

【4-5章】Spark编程基础(Python版)

课程资源&#xff1a;&#xff08;林子雨&#xff09;Spark编程基础(Python版)_哔哩哔哩_bilibili 第4章 RDD编程&#xff08;21节&#xff09; Spark生态系统&#xff1a; Spark Core&#xff1a;底层核心&#xff08;RDD编程是针对这个&#xff09;Spark SQL&#xff1a;…

Linux命令200例:mkfs用于创建文件系统

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;CSDN领军人物&#xff0c;全栈领域优质创作者✌。CSDN专家博主&#xff0c;阿里云社区专家博主&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师&#xff0…

Kafka3.0.0版本——消费者(消费者组初始化流程图解)

一、消费者组初始化流程图解 每个consumer都发送JoinGroup请求&#xff0c;如下图所示&#xff1a; 选出一个consumer作为leader&#xff0c;如下图所示&#xff1a; 把要消费的topic情况发送给leader 消费者&#xff0c;如下图所示&#xff1a; leader会负责制定消费方案…

SpringBoot / Vue 对SSE的基本使用(简单上手)

一、SSE是什么&#xff1f; SSE技术是基于单工通信模式&#xff0c;只是单纯的客户端向服务端发送请求&#xff0c;服务端不会主动发送给客户端。服务端采取的策略是抓住这个请求不放&#xff0c;等数据更新的时候才返回给客户端&#xff0c;当客户端接收到消息后&#xff0c;…

(18)线程的实例认识:线程的控制,暂停,继续,停止,线程相互控制,协作

话不多&#xff0c;但比较中肯&#xff0c;本文参照c# 线程暂停继续的实现方式_哔哩哔哩_bilibili 一、老方式 1、这是一个老的实现方式&#xff0c;基本不推荐&#xff0c;背后控制的原理需要了解。 界面&#xff1a;三个button一个textbox …

Python爬取电影信息:Ajax介绍、爬取案例实战 + MongoDB存储

Ajax介绍 Ajax&#xff08;Asynchronous JavaScript and XML&#xff09;是一种用于在Web应用程序中实现异步通信的技术。它允许在不刷新整个网页的情况下&#xff0c;通过在后台与服务器进行数据交换&#xff0c;实时更新网页的一部分。Ajax的主要特点包括&#xff1a; 异步通…

软件测试行业35岁职场魔咒,你准备怎么应对?

以前就流传一种说法“IT行业职场35岁危机”&#xff0c;那时我半信半疑。 或许那时觉得还离我比较遥远&#xff0c;也或许那时每天都重复着996&#xff0c;工作上的任务已经应接不暇&#xff0c;每天都处在忙碌中&#xff0c;也没精力花时间去过多思考这个问题。休息的时候能好…

CMS指纹识别

一.什么是指纹识别 常见cms系统 通过关键特征&#xff0c;识别出目标的CMS系统&#xff0c;服务器&#xff0c;开发语言&#xff0c;操作系统&#xff0c;CDN&#xff0c;WAF的类别版本等等 1.识别对象 1.CMS信息&#xff1a;比如Discuz,织梦&#xff0c;帝国CMS&#xff0…

【Linux】进程概念I --操作系统概念与冯诺依曼体系结构

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法…感兴趣就关注我吧&#xff01;你定不会失望。 本篇导航 1. 冯诺依曼体系结构为什么这样设计? 2. 操作系统概念为什么我们需要操作系统呢?操作系统怎么进行管理? 计算机是由两部分组…

数学分析:势场

首先从散度的物理解释开始。首先&#xff0c;在球内的向量场的散度的积分&#xff0c;等于它在球边界上的流量的积分。所以根据积分中值定理&#xff0c;我们可以这么理解散度&#xff0c;它就是这个体积内的速度场的平均密度。而速度场只和源有关&#xff0c;所以它表示的某个…

03-系统篇-内存碎片

一.常见的malloc内存分配原理 1内存分配原理 linux中应用层动态分配内存一般是用的malloc函数&#xff0c;而malloc在glibc中实现时&#xff0c;是用sbrk()来分内存. 在前面的章节中&#xff0c;我们了解到了堆的概念&#xff0c;堆在内存中&#xff0c;是一断连续的内存&…

语音识别数据的采集方法:基本流程数据类型

“人工智能是一种模仿人类功能的产品。数据采集的方法需要针对特定的场景需求。”—–Mark Brayan (澳鹏CEO) 我们一直说&#xff0c;对于一个高质量的人工智能产品离不开高质量的训练数据。对于不同的人工智能我们需要不同的数据对其训练。要采集正确的数据去训练特定的模型才…

【Apollo】自动驾驶技术的介绍

阿波罗是百度发布的名为“Apollo&#xff08;阿波罗&#xff09;”的向汽车行业及自动驾驶领域的合作伙伴提供的软件平台。 帮助汽车行业及自动驾驶领域的合作伙伴结合车辆和硬件系统&#xff0c;快速搭建一套属于自己的自动驾驶系统。 百度开放此项计划旨在建立一个以合作为中…

SQL语言的分类:DDL(数据库、表的增、删、改)、DML(数据的增、删、改)

数据库管理系统&#xff08;数据库软件&#xff09;功能非常多&#xff0c;不仅仅是存储数据&#xff0c;还要包含&#xff1a;数据的管理、表的管理、库的管理、账户管理、权限管理等。 操作数据库的SQL语言&#xff0c;基于功能&#xff0c;划分为4类&#xff1a; 1、数据定…

单片机第三季-第一课:STM32基础

官方网址&#xff1a;STMCU中文官网 STM32系列分类&#xff1a; 型号命名原则&#xff1a; STM32F103系列&#xff1a; 涉及到的几个概念&#xff1a; DMA&#xff1a;Direct Memory Access&#xff0c;直接存储器访问。DMA传输将数据从一个地址空间复制到另一个地址空间&…

最近

深圳近日经历了一场暴雨&#xff0c;周四夜晚&#xff0c;很多下晚班的同事因为雨水没法顺利打车回家。有个朋友&#xff0c;因为打车的等待时间太长&#xff0c;索性直接在公司过夜了。 早上因为磅礴的大雨我搁浅在家&#xff0c;也是因为下雨&#xff0c;本来要和龙哥一起去广…

Mac电脑怎么使用NTFS磁盘管理器 NTFS磁盘详细使用教程

Mac是可以识别NTFS硬盘的&#xff0c;但是macOS系统虽然能够正确识别NTFS硬盘&#xff0c;但只支持读取&#xff0c;不支持写入。换句话说&#xff0c;Mac不支持对NTFS硬盘进行编辑、创建、删除等写入操作&#xff0c;比如将Mac里的文件拖入NTFS硬盘&#xff0c;在NTFS硬盘里新…

网络原理(一)网络基础,包括IP ,网络相关的定义

网络基础&#xff0c;包括IP &#xff0c;网络相关的定义 网络基础冲突域广播域DNSNATNAPT 网络基础 以下图片是书上的网图。 什么是IP地址&#xff1f; IP地址&#xff08;Internet Protocol Address&#xff09;是指互联网协议地址&#xff0c;又译为网际协议地址。P地址是…

Springboot+druid

1.Druid是Java语言中最好的数据库连接池。Druid能够提供强大的监控和扩展功能。 2.配置maven <dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>${druid-version}</version></dependency>…