Java虚拟机内存结构及编码实战

如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常,让我们写一段代码,使其抛出该异常:

/**

  • VM Args: -Xss128k

*/

public class JVMStackSOF {

private int stackLength = 1;

public void stackLeak() {

stackLength++;

stackLeak();

}

public static void main(String[] args) {

JVMStackSOF sof = new JVMStackSOF();

try {

sof.stackLeak();

} catch (Throwable e) {

System.out.println(“Stack length:” + sof.stackLength);

throw e;

}

}

}

在运行之前,设置JVM的参数为-Xss128k,运行结果如下:

Stack length:1002

Exception in thread “main” java.lang.StackOverflowError

at OneMoreStudy.JVMStackSOF.stackLeak(JVMStackSOF.java:10)

at OneMoreStudy.JVMStackSOF.stackLeak(JVMStackSOF.java:11)

at OneMoreStudy.JVMStackSOF.stackLeak(JVMStackSOF.java:11)

栈的深度达到1002时,抛出了StackOverflowError异常。

如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常,还是让我们写一段代码,使其抛出该异常:

/**

  • VM Args: -Xss2M

*/

public class JVMStackOOM {

private void dontStop() {

while (true) {

}

}

public void stackLeakByThread() {

while (true) {

Thread t = new Thread(new Runnable() {

public void run() {

dontStop();

}

});

t.start();

}

}

public static void main(String[] args) {

JVMStackOOM oom = new JVMStackOOM();

oom.stackLeakByThread();

}

}

这段代码会创建出无限多的线程,因为Java的线程会映射系统的内核线程上,所以会造成CPU占用率100%,系统假死等现象,请谨慎运行。在运行之前,设置JVM的参数为-Xss2M,运行很长一段时间后结果如下:

Exception in thread “main” java.lang.OutMemoryError: unable to create new native thread

at java.lang.Thread.start0(Native Method)

at java.lang.Thread.start(Unknown Source)

at OneMoreStudy.JVMStackOOM.stackLeakByThread(JVMStackOOM.java:18)

at OneMoreStudy.JVMStackOOM.main(JVMStackOOM.java:24)

本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。

虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,所以具体的虚拟机可以自由实现它。甚至有的虚拟机(比如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。

Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。它是被所有线程共享的一块内存区域,在虚拟机启动时创建。它就是用来存放对象实例的,几乎所有的对象实例都在这里分配内存。

堆是垃圾收集器管理的主要区域,如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以Java堆中还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。从内存分配的角度看,线程共享的堆中又可能划分出多个线程私有的分配缓存区(Thread Local Allocation Buffer,TLAB)。

根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。

如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常,再让我们写一段代码,使其抛出该异常:

/*

  • VM Args: -Xms20M -Xmx20M

*/

public class HeapOOM {

static class OOMObject{

}

public static void main(String[] args){

List list = new ArrayList();

while(true){

//把对象实例放入列表中,

//使其一直被引用,不会被垃圾回收

list.add(new OOMObject());

}

}

}

在运行之前,设置JVM的参数为-Xms20M -Xmx20M,运行结果如下:

Exception in thread “main” java.lang.OutOfMemoryError: Java heap space

at java.util.Arrays.copyOf(Unknown Source)

at java.util.Arrays.copyOf(Unknown Source)

at java.util.ArrayList.grow(Unknown Source)

at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)

at java.util.ArrayList.ensureCapacityInternal(Unknown Source)

at java.util.ArrayList.add(Unknown Source)

at OneMoreStudy.HeapOOM.main(HeapOOM.java:18)

方法区

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

对于习惯在HotSpot虚拟机上开发和部署程序的开发者来说,很多人愿意把方法区称为“永久代”(Permanent Generation),本质上两者并不等价,仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已。在JDK7的HotSpot中,已经把原本在永久代的字符串常量池移出,在JDK8的HotSpot中,已经没有永久代的存在了,而是采用了新的内存空间:元空间(Metaspace)。

JVM规范对这个区域的限制非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。相对而言,垃圾收集行为在这个区域是比较少出现的,但并不是数据进入了方法区就被一直存放。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是有必要的。

根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。再让我们写一段代码,尝试使其抛出该异常:

/*

  • VM Args: -XX:PermSize=2M -XX:MaxPermSize=2M

*/

public class RuntimeConstantPoolOOM {

public static void main(String[] args) {

List list = new ArrayList();

for (int i = 0; i < 100000; i++) {

System.out.println(i);

//将i转化为字符串,

//并且调用intern(),把字符串放在运行时常量池

list.add(String.valueOf(i).intern());

}

}

}

在运行之前,设置JVM的参数为-XX:PermSize=2M -XX:MaxPermSize=2M。

JDK6中运行抛出了老年代的OutOfMemoryError异常,结果如下:

35813

35814

Exception in thread “main” java.lang.OutOfMemoryError: PermGen space

at java.lang.String.intern(Native Method)

at OneMoreStudy.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:12)

JDK7中运行,循环全部完毕后,也没有抛出任何异常,结果如下:

99996

99997

99998

99999

同一段代码,在不同版本JDK中的运行结果为什么是不同的呢?这是因为:在JDK6中,字符串常量池还在永久代中,而在JDK7中,已经把原本在永久代的字符串常量池移出了。

再再让我们写一段代码,尝试使其抛出该异常:

/*

  • VM Args: -XX:PermSize=2M -XX:MaxPermSize=2M

*/

public class MethodAreaOOM {

static class OOMObject {

}

public static void main(String[] args) {

for (int i = 0; i < 300; i++) {

System.out.println(i);

createNewClass();

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
从何学起的朋友,同时减轻大家的负担。**

[外链图片转存中…(img-EktHo1XA-1715763298324)]

[外链图片转存中…(img-fILi1LeT-1715763298325)]

[外链图片转存中…(img-eu37AZgD-1715763298325)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值