JVM简介—1.Java内存区域
1.运行时数据区的介绍
(1)运行时数据区的定义
Java虚拟机在执行Java程序的过程中,会把它所管理的内存划分为若干个不同的数据区域,这些区域各有各的用途以及各自的创建和销毁时间也不一样。有的区域会随着虚拟机的进程启动而存在,有的区域则依赖用户线程的启动和结束而进而跟着建立和销毁。
(2)运行时数据区的类型
程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区(运行时常量池)、直接内存。
(3)运行时数据区的展示图
2.运行时数据区各区域的作用
(1)程序计数器
当前线程所执行的字节码的行号指示器,占用内存空间小,线程私有,各线程间独立存储,互不影响。
字节码解释器工作时:就是通过改变程序计数器的值,来选取下一条需要执行的字节码指令。
程序计数器是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等功能都依赖它完成。
Java多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何时刻,一个单核处理器都只会执行一条线程中的指令。为了使得线程切换后能恢复到正确的执行位置,因此每条线程都需要一个独立的程序计数器。
如果线程正在执行的是一个Java方法,程序计数器记录的是正在执行的虚拟机字节码指令的地址。
如果线程正在执行的是本地(Native)方法,程序计数器记录的值则应为空(Undefined)。
Java虚拟机规范中程序计数器不会出现OOM情况。
(2)Java虚拟机栈
Java虚拟机栈是线程私有的,它的生命周期和线程同生共死。
一.Java方法执行的线程内存模型
每个方法在开始执行时,Java虚拟机会同步创建一个栈帧用于存储:局部变量表、操作数栈、动态链接、方法出口等信息。每个方法的执行,对应着栈帧在虚拟机栈中入栈和出栈的过程。
二.局部变量表里面存放着各种基本数据类型和对象引用
基本数据类型:boolean、byte、char、short、int、float、long、double。对象引用:reference类型,并非对象本身。reference类型的对象引用可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄。
可用-Xss进行虚拟机栈大小的设置:默认为1M。
三.Java虚拟机规范中虚拟机栈有两类异常状态
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。
如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存也会抛出OutOfMemoryError异常。
注意:HotSpot虚拟机的栈容量是不能动态扩展的。
(3)本地方法栈
作用和Java栈一样,本地方法栈保存的是native方法的信息。当一个JVM创建的线程调用native方法后,JVM不再为其创建栈帧。JVM只会简单地动态链接并直接调用native方法,例如Object类的wait方法:
public final native void wait(long timeout) throws InterruptedException;
本地方法栈会在栈深度溢出或栈扩展失败时,分别抛出StackOverflowError和OutOfMemoryError异常。
(4)Java堆
Java中几乎所有对象实例都在堆上分配内存,因为部分对象由于逃逸分析、栈上分配、标量替换而不在堆上分配的。
Java堆是Java开发者需要重点关注的一块区域,因为涉及到内存的分配(new关键字、反射)与回收(回收算法、收集器)。
如果从分配内存的角度看,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区(TLAB)。在Java堆中的这些线程私有的分配缓冲区(TLAB)可以提升对象分配效率,而将Java堆细分的目的就是为了更好地回收内存,或者更快地分配内存。
当前主流的Java虚拟机对Java堆都支持可动态扩展。如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常。
Java堆的相关参数含义:
-Xms:堆的最小值
-Xmx:堆的最大值
-Xmn:新生代的大小
-XX:NewSize:新生代最小值
-XX:MaxNewSize:新生代最大值
(5)方法区
一.方法区概述
方法区(如HotSpot虚拟机中的元空间或者永久代)。方法区和Java堆一样,是共享的区域,所有线程都可以共享这一区域。
方法区主要存放:类信息、常量、静态变量和JIT编译后的代码缓存等。在JVM启动的时候,方法区就被创建为固定大小或可动态扩容的区域。方法区在逻辑上属于堆的一部分,但一些简单的实现不会进行GC回收。因而方法区可看作是独立于Java堆的一块空间。
JDK1.7已经把原本放在永久代的字符串常量池、静态变量等移出。JDK1.8则完全放弃了永久代的概念,由在本地内存中实现的元空间代替,把JDK1.7中永久代还剩余的内容(主要是类型信息)全移到元数据空间里。
JDK1.7及以前:
-XX:PermSize; -XX:MaxPermSize
JDK1.8及以后:
-XX:MetaspaceSize; -XX:MaxMetaspaceSize
二.方法区的内部结构
三.栈、堆、方法区的交互
四.方法区的垃圾回收
方法区的垃圾回收主要包含两部分内容:废弃的常量、不再使用的类。判断一个常量是否废弃:没有任何地方引用该常量。
判断一个类是否不再使用的条件如下:
条件1:该类所有的实例都已被回收,堆中不存在该类及其子类的实例。
条件2:加载该类的类加载器已被回收,通常该条件很难达成。
条件3:该类对应的java.lang.class对象没有在任何地方被引用(如反射)。
Java虚拟机被允许对满足上述三个条件的无用类进行回收,但仅仅是被允许,Java虚拟机规范中不要求在方法区实现垃圾回收。
方法区的无用类仅仅被允许回收,不像对象那样没有引用就必然会回收,可以使用-Xnoclassgc参数控制是否要对类型进行回收。
在大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP以及OSGi这类频繁自定义类加载器的场景中,通常需要JVM具备卸载类的能力,保证不会对方法区造成过大内存压力。
(6)运行时常量池
运行时常量池是方法区的一部分,运行时常量池用于存放编译期生成的各种字面量和符号引用。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table)。
常量池表用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行常量池中。
3.各个版本内存区域的变化
(1)JDK1.6的内存区域
(2)JDK1.7的内存区域
(3)JDK1.8的内存区域
JDK1.8的方法区在运行时数据区消失了,因为永久代(方法区)来存储类信息、常量、静态变量等数据不是个好主意,很容易遇到内存溢出的问题,而且对永久代(方法区)进行调优也很困难,所以JDK1.8将这些信息挪出来放到了元数据空间里。
同时JDK1.8将元数据空间与堆的垃圾回收进行了隔离,避免由于永久代(方法区)引发的Full GC和OOM等问题。
从理论上讲,在JDK1.8之后,这个元数据空间只受限于物理内存的大小,而不会再受限于整个虚拟机所管理的内存大小。
平时使用时,最好还是对元空间进行限制,否则会一直增长直到物理内存满了,导致服务器宕机。
4.直接内存的使用和作用
(1)通过DirectByteBuffer对象使用直接内存
注意:元数据空间不是在直接内存里,而是在本地内存里。直接内存通常在网络通讯的时候使用较多。
直接内存不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。
如果使用了NIO,这块区域会被频繁使用。在Java堆内可以用DirectByteBuffer对象直接引用并操作直接内存。这块内存不受Java堆大小限制,但受本机总内存的限制。可以通过MaxDirectMemorySize来设置直接内存的大小,所以也会OOM。在平时使用时,直接内存最好也进行限制。
(2)直接内存可以避免在Java堆和Native堆中来回复制数据
直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。
在JDK1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用native函数库直接分配直接内存(也可以说是堆外内存),然后通过一个存储在Java堆中的DirectByteBuffer对象来操作其数据,这能在一些场景中提高性能,避免在Java堆和Native堆中来回复制数据。其中这个DirectByteBuffer对象其实可以看成是直接内存的引用。
5.站在线程的角度看Java内存区域
一个线程在一个时刻只能运行一个方法,只能运行一行代码,所以一个线程只需要一个本地方法栈,只需要一个程序计数器。
虚拟机栈、本地方法栈、程序计数器都是和线程同生共死的。Java堆、方法区则是和Java进程同生共死的。GC是不发生在栈上的,GC只发生在堆和方法区上。一个线程调用本地方法的话,会开辟一个虚拟机栈和本地方法栈。
6.深入分析堆和栈的区别
(1)堆和栈在功能上的区别
栈内存会以栈帧的方式存储方法调用的过程和所使用的变量。方法调用过程中使用的变量包括:基本数据类型变量和对象的引用变量。其内存分配在栈上,里面的变量出了作用域就会自动释放。
堆内存用来存储Java中的对象。无论是成员变量、局部变量、还是类变量,这些变量指向的对象都是存储在堆内存的。
类的成员变量存放在堆,类的方法和静态变量存放在方法区。注意:JDK1.7之后,字符串常量和静态变量移出了方法区到堆里去了。
(2)堆和栈是线程独享还是线程共享
栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见。
栈内存可理解成线程的私有内存,堆内存中的对象对所有线程可见,堆内存中的对象可被所有线程访问。
(3)堆和栈的空间大小对比
栈的内存要远远小于堆内存,栈的深度是有限制的,可能发生StackOverFlowError问题。
(4)区别堆和栈的示例代码
public class SimpleHeap {private int id;public SimpleHeap(int id) {super();this.id = id;}public void print() {System.out.println("My id is "+id);}public static void main(String[] args) {SimpleHeap s1 = new SimpleHeap(1);SimpleHeap s2 = new SimpleHeap(2);s1.print();s2.print();}
}
SimpleHeap类的main方法运行时,堆、方法区、Java栈如下图示:main方法中的s1局部变量实际上指向的是s1实例对象的引用,s1局部变量是在栈帧里,而s1实例对象是在堆上。所以可以理解为:对于"A a = new A();",a在栈上,new A()在堆上。
(5)堆中创建出来的类的对象不包含类的成员方法
Java堆是用来存放动态产生的数据,比如new出来的对象,注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法。因为同一个类的对象拥有各自的成员变量,存储在堆中,但是它们共享该类的方法,并不是每创建一个对象就复制成员方法一次。
(6)多个线程时的内存区域描述
7.方法的出入栈和栈上分配、逃逸分析及TLAB
(1)方法会打包成栈帧
一个栈帧至少要包含局部变量表、操作数栈和帧数据区。执行任何一个方法时,方法会打包成一个栈帧。下图展示的是一个方法打包成栈帧的流程:
(2)栈上分配
几乎所有的对象都是在堆上分配的,栈上分配就是虚拟机提供的一种优化技术。其基本思想是将线程私有的对象打散分配在栈上,而不分配在堆上。这样的好处是对象跟着方法调用自行销毁,不需要进行垃圾回收,从而提高性能。
如下test方法里定义了一个对象u,其他线程是访问不到的。那么对于对象u,就可以在栈上进行分配:
public void test(int x, int y) {String x = "";User u = new User();
}
(3)逃逸分析
栈上分配需要的技术基础:逃逸分析。逃逸分析的目的是判断对象的作用域是否会逃逸出方法体。
注意:任何可以在多个线程间共享的对象,一定都属于逃逸对象。在JDK1.8+,逃逸分析默认是开启的。一个线程频繁创建相同对象,就可以使用逃逸分析。
如下的User类型的对象u就逃逸出方法test,这个u作为test方法的返回值传出去了,其他的线程或方法会使用。此时可称u逃出了test方法的作用域,这个u就不能使用栈上分配了。
public User test(int x, int y) {String x = "";User u = new User();return u;
}
(4)如何启用栈上分配
-server:这是JVM运行的模式之一,server模式下才能进行逃逸分析-Xmx10m和-Xms10m:设置堆的大小-XX:+DoEscapeAnalysis:启用逃逸分析(默认打开)-XX:+EliminateAllocations:标量替换(默认打开),标量替换就是通过逃逸分析是否允许将对象打散分配在栈上而不在堆上 -XX:+PrintGC:打印GC日志-XX:-UseTLAB:关闭每个线程的私有内存分配
所以,对栈上分配发生影响的参数有三个:
-server
-XX:+DoEscapeAnalysis
-XX:+EliminateAllocations
关掉任何一个参数都不会发生栈上分配,由于逃逸分析和标量替换默认是打开的,所以JVM的参数只用-server一样可以有栈上替换的效果。
(5)线程本地分配缓冲TLAB
TLAB全称是ThreadLocalAllocBuffer,线程本地分配缓冲。创建对象是在堆上分配的,需要在堆上申请指定大小内存的。一个堆一块区域,线程A进来分配区域a,线程B进来分配区域b。
如果有大量线程同时申请堆上的内存,为避免两个线程申请内存时不会申请同一块内存,需要对申请进行加锁。加锁不仅在并发编程时会有,虚拟机在实现时同样要考虑并发而加锁;如果不加锁,就有可能两个线程同时分配到同一块内存,导致数据错乱。
我们经常会new一个对象出来,所以内存分配是一个非常频繁的动作。因此内存分配时也就需要频繁加锁,而频繁加锁就会影响性能。一旦加锁,这种动作就会变成串行的模式,对性能影响很大。
所以TLAB的作用就是:它会事先在堆里面为每个线程分配一块私有内存,在线程A中new出的对象只在线程A的私有内存上进行分配。
所以TLAB的好处就是:由于线程的堆内存事前分配好了,因此同时分配时就不存在竞争了。从而大大提高了分配的效率,当私有内存用完了再重新申请继续使用。不过要注意的是,重新申请堆内存的动作还是需要保证原子性的。
TLAB涉及到线程私有,每个线程在new对象时会在私有内存上分配内存。尽管线程A在私有内存区域a位置拥有一块私有内存并在上面分配了对象,但是这些对象对所有线程都是可见并可用的。也就是说这些A线程的对象在分配的时候只能在a区域分配而已,B线程、C线程也是可以看见它们并使用它们的。
注意:堆中所有对象对所有线程理论上都是可见的。
(6)栈上分配的效果
同样的User的对象实例,分配100000000次。启用栈上分配,只需7ms,不启用,需要3S:
//启用栈上分配
//-XX:+EliminateAllocations
public class StackAlloc {public static class User {public int id = 0;public String name = "";User() {}}public static void alloc() {User u = new User();u.id = 5;u.name = "mark";}//配置了VM Options之后: //-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations -XX:-UseTLAB //输出如下://[GC (Allocation Failure) 2047K->504K(9728K), 0.0013795 secs]//7mspublic static void main(String[] args) {long b = System.currentTimeMillis();for (int i=0; i<100000000; i++) {alloc();}long e = System.currentTimeMillis();System.out.println((e-b)+"ms");}
}
不启用栈上分配:
//不启用栈上分配
//-XX:-EliminateAllocations
public class StackAlloc {public static class User {public int id = 0;public String name = "";User() {}}public static void alloc() {User u = new User();u.id = 5;u.name = "mark";}//运行配置了VM Options之后: -server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations -XX:-UseTLAB//输出如下://[GC (Allocation Failure) 2519K->471K(9728K), 0.0002605 secs]//...//[GC (Allocation Failure) 2519K->471K(9728K), 0.0002440 secs]//2969mspublic static void main(String[] args) {long b = System.currentTimeMillis();for (int i=0; i<100000000; i++) {alloc();}long e = System.currentTimeMillis();System.out.println((e-b)+"ms");}
}
8.虚拟机中的对象创建步骤
Java程序几乎无时无刻都有对象被创建出来,虚拟机碰到一个new关键字时是如何创建对象的呢?
步骤一:进行类加载
首先检查new指令的参数是否能在常量池中定位到一个类的符号引用,并检查该符号引用代表的类是否被加载、解析和初始化过。如果没有,则执行相应的类加载过程。
步骤二:为对象分配内存
类加载完成后,虚拟机就要为这个新生对象分配内存,也就是把一块确定大小的内存从Java堆中划分出来。
如果Java堆中的内存是绝对规整的,所有用过的内存都放一边,空闲的内存放另一边,并且中间放一个指针作为已用内存和空闲内存的分界点的指示器,分配内存时就把该指针向空闲空间那边移动一段与对象大小相等的距离,这种分配方式称为指针碰撞。
如果Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法进行指针碰撞了,此时虚拟机就必须要维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为空闲列表。
选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。当使用Serial、ParNew等带压缩整理过程的收集器时,系统采用的分配算法是指针碰撞,既简单又高效;当使用CMS这种基于清除算法的收集器时,理论上采用空闲列表来分配内存,但实际为了分配更快也加入指针碰撞。
除如何划分可用空间外,还有另外一个需要考虑的问题是对象创建在虚拟机中是非常频繁的行为。即使仅仅修改一个指针所指向的位置,在并发情况下也不是线程安全的。可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。
解决这个问题有两种方案:
一种是对分配内存空间的动作进行同步处理,实际上虚拟机采用CAS+失败重试的方式保证更新操作的原子性。另一种是把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块私有内存TLAB。
开启使用TLAB本地线程分配缓冲(Thread Local Allocation Buffer)时,在线程初始化时会申请一块指定大小的内存,只给当前线程使用。这样每个线程都单独拥有一个Buffer,如果要分配内存,就在自己的Buffer上分配,这样就不存在竞争的情况。当Buffer容量不够的时候,再重新从Eden区域申请一块内存继续使用。
TLAB的目的是在为新对象分配内存内存时,让每个Java应用线程用自己专属的分配指针来分配内存,减少同步开销。TLAB只是让每个线程拥有私有的分配指针,创建的对象还是线程共享的。当一个TLAB用完了(分配指针top撞上end了),那么就重新申请一个TLAB。
步骤三:为分配的内存空间初始化零值
内存分配完成后,虚拟机就需要将分配到的内存空间都初始化为零值。这一步操作保证了对象的实例字段在代码中可以不赋初始值就直接使用,也就是程序能访问到这些字段的数据类型所对应的零值。
步骤四:设置对象的对象头
内存空间初始化零值后,虚拟机就要对对象进行必要的设置,需要在对象的对象头中设置这些内容:这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。
步骤五:对象初始化
设置完对象的对象头信息后,从虚拟机的视角来看,一个新的对象已产生。但从Java程序的视角来看,对象创建才刚刚开始,所有的字段都还为零值。所以,执行new指令之后会接着把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。
如下是详细的对象创建流程图:
9.对象的内存布局
在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
对象头的第一部分是用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁等。
对象头的第二部分是类型指针,即对象指向它的类的元数据的指针,JVM虚拟机可以通过这个指针确定这个对象是哪个类的实例。
对象头的第三部分是对齐填充,它仅仅起着占位符的作用。因为HotSpot的自动内存管理系统要求对象的大小必须是8字节的整数倍。当对象其他数据部分没有对齐时,就需要通过对齐填充来补全。
10.对象的访问定位
建立对象是为了使用对象,Java程序需要通过栈上的reference数据来操作堆上的具体对象。目前主流的访问方式有两种:使用句柄和直接指针。
一.通过句柄访问对象
如果使用句柄访问,那么Java堆中将会划分出一块内存来作为句柄池。reference中存储的就是对象的句柄地址,句柄中包含了对象实例数据与类型数据各自的具体地址信息。
二.通过直接指针访问对象
如果使用直接指针访问, reference中存储的直接就是对象地址:
这两种对象访问方式各有优势。
使用句柄访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄的实例数据指针,reference本身无需修改,而在垃圾收集时移动对象又是非常普遍的行为。
使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。
对于HotSpot而言,它是使用直接指针访问方式进行对象访问的。
11.堆参数设置和内存溢出示例
使用CGLib技术操作字节码生成大量的动态类可能会导致永久代OOM。Spring和Dubbo会通过反射来生成类实例,刚开始启动不会永久代溢出。但运行时间久了,加载的类越多就越有可能造成永久代溢出。
(1)Java堆溢出的例子
一.出现java.lang.OutOfMemoryError: GC overhead limit exceeded
运行前配置参数:
-Xms5m -Xmx5m -XX:+PrintGC
一般是某个循环里可能性在不停地分配对象,但分配太多把堆撑爆了,如下代码所示:
//在运行之前需要先进行VM Options参数配置, 设置最小最大堆都是5M并打印GC:
//-Xms5m -Xmx5m -XX:+PrintGC
public class OOM {//输出结果如下://[Full GC (Ergonomics) 5048K->5048K(5632K), 0.0189781 secs]//[Full GC (Ergonomics) Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded // 5049K->5049K(5632K), 0.0210863 secs]//[Full GC (Ergonomics) at java.util.LinkedList.linkLast(LinkedList.java:142)// at java.util.LinkedList.add(LinkedList.java:338)// at com.demo.OOM.main(OOM.java:18)// 5066K->374K(5632K), 0.0039215 secs]public static void main(String[] args) {List<Object> list = new LinkedList<>();int i=0;while(true) {i++;//每循环10000次进行一次打印if (i % 10000 == 0) System.out.println("i=" + i);list.add(new Object());}}
}
二.出现java.lang.OutOfMemoryError: Java heap space
一般是分配了巨型对象,如下代码所示:运行前配置参数 :-Xms5m -Xmx5m -XX:+PrintGC
//在运行之前需要先进行VM Options参数配置, 设置最小最大堆都是5M并打印GC:
//-Xms5m -Xmx5m -XX:+PrintGC
public class OOM {//输出结果如下://[Full GC (Allocation Failure) 392K->374K(5632K), 0.0033209 secs]//Exception in thread "main" java.lang.OutOfMemoryError: Java heap space// at com.demo.OOM.main(OOM.java:22)public static void main(String[] args) {String[] strings = new String[100000000];}
}
(2)虚拟机栈和本地方法栈溢出的例子
需要不停调用方法才有可能出现虚拟机栈和本地方法栈溢出,比如出现无限递归的情况时,就可能会出现虚拟机栈溢出。一般的方法调用是很难出现的,如果出现了就要考虑是否有无限递归。
出现java.lang.StackOverflowError异常:
//在运行之前需要先进行VM Options参数配置: -Xss256k
//-Xss:默认为1M
public class StackOOM {//记录递归的深度(同时也是栈的深度)private int stackLength = 1;private void diGui() {stackLength++;diGui();}//输出结果如下://stackLength = 2239//java.lang.StackOverflowError// at com.demo.StackOOM.diGui(StackOOM.java:14)// ...(2239行)//@param argspublic static void main(String[] args) {StackOOM oom = new StackOOM();try {oom.diGui();} catch (Throwable e) {System.out.println("stackLength = "+oom.stackLength);e.printStackTrace();}}
}
在上面StackOverflowError的例子中,栈的深度是2239。假如diGui()函数增加入参,-Xss还是256k,那么栈的深度变为1598。这说明栈帧变大了,因为执行任何一个方法时,方法会打包成一个栈帧。栈帧的内容包括:局部变量表(包括方法的入参)、操作数栈、帧数据区。
//在运行之前需要先进行VM Options参数配置: -Xss256k
//-Xss:默认为1M
public class StackOOM {//记录递归的深度(同时也是栈的深度)private int stackLength = 1;private void diGui(int x, String y) {stackLength++;diGui(x, y);}//输出结果如下://stackLength = 1598//java.lang.StackOverflowError// at com.demo.StackOOM.diGui(StackOOM.java:14)// ...(1598行)//@param argspublic static void main(String[] args) {StackOOM oom = new StackOOM();try {oom.diGui(12, "TestStackOOMLength");} catch (Throwable e) {System.out.println("stackLength = "+oom.stackLength);e.printStackTrace();}}
}
虚拟机栈带给的启示:方法的执行因为要打包成栈桢,所以天生要比实现同样功能的循环慢。所以树的遍历算法中:递归和非递归(循环来实现)都有存在的意义。递归代码简洁,非递归代码复杂但速度快。
(3)直接内存溢出的例子
出现java.lang.OutOfMemoryError: Direct buffer memory异常,这种情况通常发生在NIO通讯上。
//在运行之前需要先进行VM Options参数配置, 设置堆最大为10M, 直接内存最大为10M
//-Xmx10M -XX:MaxDirectMemorySize=10M
public class DirectMem {//代码里分配了14M的直接内存//运行后输出结果如下://Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory// at java.nio.Bits.reserveMemory(Bits.java:694)// at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)// at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)// at com.demo.DirectMem.main(DirectMem.java:8)public static void main(String[] args) {ByteBuffer b = ByteBuffer.allocateDirect(1024*1024*14);}
}
文章转载自:东阳马生架构
原文链接:https://www.cnblogs.com/mjunz/p/18622782
体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构
相关文章:
JVM简介—1.Java内存区域
1.运行时数据区的介绍 (1)运行时数据区的定义 Java虚拟机在执行Java程序的过程中,会把它所管理的内存划分为若干个不同的数据区域,这些区域各有各的用途以及各自的创建和销毁时间也不一样。有的区域会随着虚拟机的进程启动而存在,有的区域则依…...
IPC协议获取签名信息
一:IPC协议获取签名信息详解 目录 什么是IPC协议?签名信息概述IPC协议中签名信息获取的流程相关知识点 数字签名原理常见签名算法数据完整性与认证签名的生成与验证IPC中的安全传输 应用场景总结 什么是IPC协议? IPC(Inter-Pro…...
高校就业管理系统:数据驱动的就业服务创新
1 Java语言 Java语言是目前最流行的语言之一,不仅可以做桌面窗口形式的程序,还可以做浏览器访问的程序,目前最流行的就是用Java语言作为基础,做各种程序的后台处理。Java语言是操作变量的语言,而变量则是Java对于数据存…...
C++中的模板元编程
模板元编程 模板特化: 指的是对某个特定类型或特定类型组合提供模板的定制实现。 示例: #include<iostream> using namespace std;template <typename T> void func(T t) {cout << "Generic template: " << t <…...
rk3568制冷项目驱动开发流程汇总(只适用于部分模块CIF DVP等,自用)
采用fpga输入,3568采集并显示至hdmi RKVICAP 驱动框架说明 RKVICAP驱动主要是基于 v4l2 / media 框架实现硬件的配置、中断处理、控制 buffer 轮转,以及控制 subdevice(如 mipi dphy 及 sensor) 的上下电等功能。 对于RK356X 芯片而言, VICAP…...
EMC——射频场感应的传导骚扰抗扰度(CS)
术语和定义 AE(辅助设备) 为受试设备正常运行提供所需信号的设备和检验受试设备性能的设备; 钳注入 是用电缆上的钳合式“电流”注入装置获得的钳注入; 电流钳 由被注入信号的电缆构成的二次绕组实现的电流变换器; 电磁…...
postgreSql对分钟级的降雨数据进行插值为整小时
postgreSql对分钟级的降雨数据进行插值为整小时 SQL语句实现 SQL语句实现 --核查某个小流域的降雨量小时插值是否正确SELECT tm, sum(drp) as sum, round(sum(drp), 2) as drp2 from(SELECT a.stcd, (TO_TIMESTAMP(time_period, YYYY-MM-DD HH24:MI:SS) INTERVAL 1 HOUR) as t…...
如何安全获取股票实时数据API并在服务器运行?
以下是安全获取股票实时数据 API 并在服务器运行的方法: 选择合适的券商或交易平台 评估自身需求:明确自己的交易策略、交易品种、交易频率等需求,以及对 股票api 的功能、性能、稳定性等方面的要求。调研券商或平台:了解不同券商…...
Android Bootable Recovery 中的 `imgdiff.cpp` 文件解析
Android Bootable Recovery 中的 imgdiff.cpp 文件解析 引言 在 Android 系统中,Recovery 模式是一个非常重要的组成部分,它允许用户在设备无法正常启动时进行系统修复、数据恢复、OTA 更新等操作。其中,OTA(Over-The-Air)更新是 Android 系统中常见的更新方式,它通过网…...
golang学习笔记-变量与常量
1.标识符 在编程语言中标识符就是程序员定义的具有特殊意义的词,比如变量名,常量名,函数名等.go语言中标识符有字母数字和_(下划线)组成,并且只能以字母和_开头 2.关键字 关键字是指变成语言中预先定义好的特殊含义的标识符 break default func interface select case …...
关于变分量子算法的问答
1.零噪声外推如何通过增加误差的过程来改善估计的误差缓解类型? 解释:**零噪声外推(ZNE)**是一种误差缓解方法,通过故意增加噪声并利用这些增加噪声的结果来改进量子电路的估计。其核心思想是在不同的噪声级别下运行量子电路&am…...
小学数学思维训练 一年级 第一周(少儿思维启蒙)
前言 本文主要介绍了通过各种题型和解题方法培养孩子的数学思维能力。通过系统的方法训练一年级学生的数学思维能力,帮助他们学会举一反三,融会贯通地解决各类数学问题。 点击获取小学数学1-6年级思维训练电子版 第一周 比一比 比一比是实际生活中常…...
sqlite 自定以脚本解释器
应用程序使用 libfdt 解析设备树,获取兼容性配置 内核源码支持libfdt 标准设备树语法,不用自己再创造 非常的爽,因为设备树支持预编译 一些可以跑类 BSD 系统的设备也可以使用这样的方法,不仅仅是在linux 系统上跑 有pylibfdt 支持解析设备树,校验设备树是否是正确的…...
动手学深度学习11.2. 凸性-笔记练习(PyTorch)
本节课程地址:72 优化算法【动手学深度学习v2】_哔哩哔哩_bilibili 本节教材地址:11.2. 凸性 — 动手学深度学习 2.0.0 documentation 本节开源代码:...>d2l-zh>pytorch>chapter_multilayer-perceptrons>convexity.ipynb 凸性 …...
go并发模型的详细介绍
Go 语言的并发模型是其一大亮点,它使得并发编程变得简单高效。Go 语言并发模型的核心概念是 goroutines 和 channels。在理解这两个概念之前,我们首先了解并发编程的一些基本概念。 1. 并发与并行 并发(Concurrency):…...
使用FreeNAS软件部署ISCSI的SAN架构存储(IP-SAN)练习题
一,实验用到工具分别为: VMware虚拟机,安装教程:VMware Workstation Pro 17 安装图文教程 FreeNAS系统,安装教程:FreeNAS-11.2-U4.1安装教程2024(图文教程) 二,新建虚…...
FreeSWITCH实现多人电话会议功能
FreeSWITCH实现多人电话会议功能 作者:基于Java与FreeSWITCH的开源呼叫中心系统FreeIPCC FreeSWITCH作为一个开源的电话软交换平台,为企业和运营商提供了构建高效、灵活的语音通信系统的能力。其中,多人电话会议功能是其核心应用之一&#…...
Chromium 中chrome.webRequest扩展接口定义c++
一、chrome.webRequest 注意:从 Manifest V3 开始,"webRequestBlocking"权限不再适用于大多数扩展程序。以"declarativeNetRequest"为例,它允许使用declarativeNetRequest API。除了"webRequestBlocking"之外…...
极乐 15.2.6 | 清爽版简约美观音乐软件,支持网易云歌单导入
极乐是一款使用起来非常轻松的音乐播放软件,它拥有清新简洁的画面,专注于音乐播放功能。最新版本全面升级了64位架构,带来了前所未有的性能提升和更稳定的体验。通过优化内存管理,降低了应用对系统资源的占用,确保设备…...
如何在 Ubuntu 22.04 上安装 Graylog 开源日志管理平台
简介 Graylog 的开源特性、丰富的功能、灵活性和可扩展性使其成为一个流行的日志管理平台。在本教程中,我将向你展示如何在 Ubuntu 22.04 上安装 Graylog,包括配置 Graylog 服务器软件包和访问 Graylog Web UI。 Graylog 是什么? Graylog …...
Wux weapp 组件库的 bug—— wux-picker选择器组件无法正确初始化到选定的value
options的value为Number,组件无法正常使用 解决方案,修改picker-view/utils.js中的getIndexFromValue函数,如下: export function getIndexFromValue(value, col [], fieldNames DEFAULT_FIELD_NAMES) {//return getRealIndex(…...
决策树(理论知识1)
目录 何为决策树决策树的组成决策树的构建 何为决策树 决策树(Decision Tree)是一种分类和回归方法,是基于各种情况发生的所需条件构成决策树,以实现期望最大化的一种图解法。由于这种决策 分支画成图形很像一棵树的枝干,故称决策树。它的运…...
【Spring】获取Bean对象需要哪些注解
阿华代码,不是逆风,就是我疯 你们的点赞收藏是我前进最大的动力!! 希望本文内容能够帮助到你!! 目录 一:Service(服务存储) 1:存储bean的代码 2࿱…...
java的Webclient对象怎解解析400状态码
在Java中使用WebClient处理400状态码,可以通过检查响应状态并根据状态码进行相应的错误处理。以下是几种处理400状态码的方法: 使用onStatus方法判断和处理错误: 你可以使用WebClient的retrieve()方法链中的onStatus方法来检查响应状态码。如…...
【计算机视觉基础CV-图像分类】03-深度学习图像分类实战:鲜花数据集加载与预处理详解
本文将深入介绍鲜花分类数据集的加载与处理方式,同时详细解释代码的每一步骤并给出更丰富的实践建议和拓展思路。以实用为导向,为读者提供从数据组织、预处理、加载到可视化展示的完整过程,并为后续模型训练打下基础。 前言 在计算机视觉的深…...
ubuntu 如何重装你的apt【apt-get报错: symbol lookup error/undefined symbol】
副标题:解决error:apt-get: symbol lookup error: /lib/x86_64-linux-gnu/libapt-private.so.0.0: undefined symbol: _ZNK13pkgTagSection7FindULLENS_3KeyERKy, version APTPKG_6.0 文章目录 问题描述报错分析解决方案:重装你的apt1、查看你的ubuntu版本2、下载适配你的ap…...
Unity 上好用的插件
PlayerMaker BehaviorDesigner Cinemachine Timeline Hybrid Addressable AssetBundle Blower Simple Zoom 大地图上缩放和平移使用ScrollRect的好效果实现...
大数据机器学习算法和计算机视觉应用07:机器学习
Machine Learning Goal of Machine LearningLinear ClassificationSolutionNumerical output example: linear regressionStochastic Gradient DescentMatrix Acceleration Goal of Machine Learning 机器学习的目标 假设现在有一组数据 x i , y i {x_i,y_i} xi,yi&…...
Godot RPG 游戏开发指南
Godot RPG 游戏开发指南 一、基础准备 1. 开发环境 下载并安装最新版 Godot 4.x选择使用 GDScript 或 C# 作为开发语言准备基础美术资源(角色、地图、道具等) 2. 项目结构 project/ ├── scenes/ # 场景文件 ├── scripts/ # 脚…...
c++ 找第一个只出现一次的字符
【题目描述】 给定一个只包含小写字母的字符串,请你找到第一个仅出现一次的字符。如果没有,输出no。 【输入】 一个字符串,长度小于100000。 【输出】 输出第一个仅出现一次的字符,若没有则输出no。 【输入样例】 abcabd【输出样…...
时空信息平台架构搭建:基于netty封装TCP通讯模块(IdleStateHandler网络连接监测,处理假死)
文章目录 引言I 异步TCP连接操作II 心跳机制:空闲检测(读空闲和写空闲)基于Netty的IdleStateHandler类实现心跳机制(网络连接监测)常规的处理假死健壮性的处理假死方案获取心跳指令引言 基于netty实现TCP客户端:封装断线重连、连接保持 https://blog.csdn.net/z92911896…...
【Rust自学】3.6. 控制流:循环
3.6.0. 写在正文之前 欢迎来到Rust自学的第三章,一共有6个小节,分别是: 变量与可变性数据类型:标量类型数据类型:复合类型函数和注释控制流:if else控制流:循环(本文) 通过第二章…...
如何正确计算显示器带宽需求
1. 对显示器的基本认识 一个显示器的参数主要有这些: 分辨率:显示器屏幕上像素点的总数,通常用横向像素和纵向像素的数量来表示,比如19201080(即1080p)。 刷新率:显示器每秒钟画面更新的次数&…...
mysql 基于chunk机制是如何支持运行期间,动态调整buffer pool大小的
mysql 基于chunk机制是如何支持运行期间,动态调整buffer pool大小的 MySQL 的 InnoDB 存储引擎确实支持在运行期间动态调整缓冲池(buffer pool)的大小,但其机制与自定义缓存系统有所不同。InnoDB 通过内部优化和配置参数来实现这…...
梳理你的思路(从OOP到架构设计)_简介设计模式
目录 1、 模式(Pattern) 是较大的结构编辑 2、 结构形式愈大 通用性愈小编辑 3、 从EIT造形 组合出设计模式 1、 模式(Pattern) 是较大的结构 组合与创新 達芬奇說:簡單是複雜的終極形式 (Simplicity is the ultimate form of sophistication) —Leonardo d…...
【Redis】缓存
什么是缓存 https://tech.meituan.com/2017/03/17/cache-about.html Spring Data Redis Spring Data Redis提供了从Spring应用程序轻松配置和访问Redis的功能。 引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>sp…...
基于 PyCharm 和 Navicat 的新闻管理系统
# 用于创建连接池 pip3 install mysql-connector-python # 改变终端打印颜色 pip3 install colorama 1.创建连接池 文件地址:db/mysql_db.py 首先建立一个与 MySQL 数据库的连接池,以便在应用程序中复用连接,提高性能。 如果连接池创建失败…...
<QNAP 453D QTS-5.x> 日志记录: 优化性能 内存管理 修改swap优先顺序 swap放在ssd 网络稳定性 进程出错管理
起因 几个月前,开始重学编程,往 NAS 的 docker 里放了些 containers ,每一个用来跑练习的 App。为了放更多的app,上个月加了 4GB 内存。最近只放了两个,NAS 就会时不时的闪断。codes 全存在网络驱动器上,当…...
一区牛顿-拉夫逊算法+分解+深度学习!VMD-NRBO-Transformer-GRU多变量时间序列光伏功率预测
一区牛顿-拉夫逊算法分解深度学习!VMD-NRBO-Transformer-GRU多变量时间序列光伏功率预测 目录 一区牛顿-拉夫逊算法分解深度学习!VMD-NRBO-Transformer-GRU多变量时间序列光伏功率预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.中科院一区…...
本地部署webrtc应用怎么把http协议改成https协议?
环境: WSL2 Ubuntu22.04 webrtc视频聊天应用 问题描述: 本地部署webrtc应用怎么把http协议改成https协议? http协议在安卓手机浏览器上用不了麦克风本,来地应用webrtc 本来是http协议,在安卓手机上浏览器不支持使…...
React简单了解
原理简化了解 import React from "react" import { createRoot } form "react-dom/client"const element React.createElement(p,{id: hello},Hello World! )const container document.querySelector(#root) const root createRoot(container) root.r…...
基于LabVIEW的USRP信道测量开发
随着无线通信技术的不断发展,基于软件无线电的设备(如USRP)在信道测量、无线通信测试等领域扮演着重要角色。通过LabVIEW与USRP的结合,开发者可以实现信号生成、接收及信道估计等功能。尽管LabVIEW提供了丰富的信号处理工具和图形…...
Docker挂载
目录 数据卷挂载 本地目录挂载 数据卷挂载 宿主机默认的存放所有容器数据卷的目录:/var/lib/docker/volumes nginx容器 静态文件目录:/usr/share/nginx/html 配置文件目录:/etc/nginx/nginx.conf 修改宿主机的内容,进入到容器查…...
使用Java结合经纬度位置计算目标点的日出日落时间
目录 前言 一、应用示例 1、天安门升旗时间 2、湖南省日出日落信息 二、JAVA日出日落计算 1、在线API 2、使用Java进行计算 三、总结 前言 随着城市化进程的加速,城市环境与人类生活的联系日益紧密。城市不仅承载着居住、工作、休闲等多种功能,也…...
八字精批api接口_php获取生成八字和批注的方法研究
八字算命 API 介绍 这个八字算命 API 提供了一种便捷的方式,让用户通过 GET 或 POST 请求获取详细的八字信息。API 返回的数据格式为 JSON,包含多种命理分析和建议,适合对传统命理学感兴趣的用户。 API 功能 五行分析: 提供用户…...
docker run 命令参数
user docker run -it --nameubn18 --gpus all --privilegedtrue --shm-size 8G ubuntu:18.04 /bin/bash-it 是什么意思 4o 在运行 docker run 命令时,-it 是两个选项的组合,用于更好地与容器进行交互: -i 或 --interactive:这个选…...
智能外呼技术如何改变企业营销方式
智能外呼技术如何改变企业营销方式 作者:开源大模型智能呼叫中心系统FreeAICC,Github:https://github.com/FreeIPCC/FreeAICC 在数字化时代,企业营销方式正经历着前所未有的变革。其中,智能外呼技术作为一项前沿的人…...
redis数据转移
可能有时候因为硬件的原因我们我们需要更换服务器,如果更换服务器的话,那我们redis的数据该怎样转移呢,按照一下步骤即可完成redis数据的转移 1.进入redis客户端 2.使用 bgsave命令进行数据的备份,此命令完成后会在你的redis安装目…...
STM32-笔记5-按键点灯(中断方法)
1、复制03-流水灯项目,重命名06-按键点灯(中断法) 在\Drivers\BSP目录下创建一个文件夹exti,在该文件夹下,创建两个文件exti.c和exti.h文件,并且把这两个文件加载到项目中,打开项目工程文件 加载…...
DotNetBrowser 3.0.0 正式发布!
🛠️ 重要消息:DotNetBrowser 3.0.0 正式发布! 我们很高兴向您介绍全新的 DotNetBrowser 3.0.0 版本。此次更新带来了多项重要功能与优化,进一步提升了 Web 开发的效率和体验。 📢 DotNetBrowser 3.0.0 包含哪些新功…...