字节码只是一个二进制文件存放在那里。要想在jvm里跑起来,先得有个运行的内存环境。
也就是我们所说的jvm运行时数据区。
1)运行时数据区的位置
运行时数据区是jvm中最为重要的部分,执行引擎频繁操作的就是它。类的初始化,以及后面我们讲的对象空间的分配、垃圾的回收都是在这块区域发生的。

2)区域划分
根据《Java虚拟机规范》中的规定,在运行时数据区将内存细分为几个部分
线程私有的:Java虚拟机栈(Java Virtual Machine Stack)、程序计数器(Program Counter Register)、本地方法栈(Native Method Stacks)
大家共享的:方法区(Method Area)、Java堆区(Java Heap)

接下来我们分块详细来解读,每一块是做什么的,如果溢出了会发生什么事情
程序计数器(Program Counter Register)
每个线程一个。是一块较小的内存空间,它表示当前线程执行的字节码指令的地址。
字节码解释器工作时,通过改变这个计数器的值来选取下一条需要执行的字节码指令,所以整个程序无论是分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
由于线程是多条并行执行的,互相之间执行到哪条指令是不一样的,所以每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
如果是native方法,这里为空
没有!
在虚拟机规范中,没有对这块区域设定内存溢出规范,也是唯一一个不会溢出的区域
因为它不会溢出,所以我们没有办法给它造一个,但是从class类上可以找到痕迹。
回顾上面javap的反汇编,其中code所对应的编号就可以理解为计数器中所记录的执行编号。


1)栈深度超出设定
如果是创建的栈的深度大于虚拟机允许的深度,抛出
Exception in thread "main" java.lang.StackOverflowError
2)内存申请不足
如果栈允许内存扩展,但是内存申请不够的时候,抛出 OutOfMemoryError
注意!这一点和具体的虚拟机有关,hotspot虚拟机并不支持栈空间扩展,所以单线程环境下,一个线程创建时,分配给它固定大小的一个栈,在这个固定栈空间上不会出现再去扩容申请内存的情况,也就不会遇到申请不到一说,只会因为深度问题超出固定空间造成上面的StackOverflowError
如果换成多线程,毫无节制的创建线程,还是有可能造成OutOfMemoryError。但是这个和Xss栈空间大小无关。是因为线程个数太多,栈的个数太多,导致系统分配给jvm进程的物理内存被吃光。
这时候虚拟机会附带相关的提示:
Exception in thread "main" java.lang.OutOfMemoryError: unable to create native thread
ps: 每个线程默认分配1M空间(64位linux,hotspot环境)
疑问:是不是改小Xss的值就可以得到栈空间溢出呢?
答:根据上面的分析,hotspot下不可以,还是会抛出StackOverflowError,无非深度更小了。
1)代码
package com.itheima.jvm.demo;
/**
* 程序模拟进栈、出栈过程
* 先进后出
*/
public class StackInAndOut {
/**
* 定义方法一
*/
public static void A() {
System.out.println("进入方法A");
}
/**
* 定义方法二;调用方法一
*/
public static void B() {
A();
System.out.println("进入方法B");
}
public static void main(String[] args) {
B();
System.out.println("进入Main方法");
}
}
2)运行结果:
进入方法A
进入方法B
进入Main方法
3)栈结构:
main方法---->B方法---->A方法

1)代码
这个容易实现,方法嵌套自己就可以:
package com.itheima.jvm.demo;
/**
* 通过一个程序模拟线程请求的栈深度大于虚拟机所允许的栈深度;
* 抛出StackOverflowError
*/
public class StackOverFlow {
/**
* 定义方法,循环嵌套自己
*/
public static void B() {
B();
System.out.println("进入方法B");
}
public static void main(String[] args) {
B();
System.out.println("进入Main方法");
}
}
2)运行结果:
Exception in thread "main" java.lang.StackOverflowError
at com.itheima.jvm.demo.StackOverFlow.B(StackOverFlow.java:12)
at com.itheima.jvm.demo.StackOverFlow.B(StackOverFlow.java:12)
at com.itheima.jvm.demo.StackOverFlow.B(StackOverFlow.java:12)
at com.itheima.jvm.demo.StackOverFlow.B(StackOverFlow.java:12)
at com.itheima.jvm.demo.StackOverFlow.B(StackOverFlow.java:12)
3)栈结构:

一直不停的创建线程就可以堆满栈
但是!这个很危险,到32系统的winxp上勇敢的小伙伴可以试一试,机器卡死不负责!
package com.itheima.jvm.demo;
/*
* 栈内存溢出,注意!很危险,谨慎执行
* 执行时可能会卡死系统。直到内存耗尽
* */
public class StackOutOfMem {
public static void main(String[] args) {
while (true) {
new Thread(() -> {
while(true);
}).start();
}
}
}
本地方法栈的功能和特点类似于虚拟机栈,均具有线程隔离的特点
不同的是,本地方法栈服务的对象是JVM执行的native方法,而虚拟机栈服务的是JVM执行的java方法
虚拟机规范里对这块所用的语言、数据结构、没有强制规定,虚拟机可以自由实现它
甚至,hotspot把它和虚拟机栈合并成了1个
和虚拟机栈一样,也是两个:
如果是创建的栈的深度大于虚拟机允许的深度,抛出 StackOverFlowError
内存申请不够的时候,抛出 OutOfMemoryError
与上面的3个不同,堆是所有线程共享的!所谓的线程安全不安全也是出自这里。
在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java世界里“几乎”所有的对象实例都在这里分配内存。
需要注意的是,《Java虚拟机规范》并没有对堆进行细致的划分,所以对于堆的讲解要基于具体的虚拟机,我们以使用最多的HotSpot虚拟机为例。
Java堆是垃圾收集器管理的内存区域,因此它也被称作“GC堆”,这就是我们做JVM调优的重点区域部分。
jvm的内存模型在1.7和1.8有较大的区别,虽然1.7目前使用的较少了,但是我们也是需要对1.7的内存模型有所了解,所以接下里,我们将先学习1.7再学习1.8的内存模型。

Young 年轻区(代)
Young区被划分为三部分,Eden区和两个大小严格相同的Survivor区
其中,Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用
在Eden区间变满的时候, GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到下面的Tenured区间。
Tenured 年老区
Tenured区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young复制转移一定的次数以后,对象就会被转移到Tenured区,一般如果系统中用了application级别的缓存,缓存中的对象往往会被转移到这一区间。
Perm 永久区
hotspot 1.6 才有这货,现在已经成为历史
Perm代主要保存class,method,filed对象,这部份的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到java.lang.OutOfMemoryError : PermGen space 的错误,造成这个错误的很大原因就有可能是每次都重新部署,但是重新部署后,类的class没有被卸载掉,这样就造成了大量的class对象保存在了perm中,这种情况下,一般重新启动应用服务器可以解决问题。另外一种可能是创建了大批量的jsp文件,造成类信息超出perm的上限而溢出。这种重启也解决不了。只能调大空间。
Virtual区:
jvm参数可以设置一个范围,最大内存和初始内存的差值,就是Virtual区。

由上图可以看出,jdk1.8的内存模型是由2部分组成,年轻代 + 年老代。永久代被干掉,换成了Metaspace(元数据空间)
年轻代:Eden + 2*Survivor (不变)
年老代:OldGen (不变)
元空间:原来的perm区 (重点!)
需要特别说明的是:Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间中,这也是与1.7的永久代最大的区别所在。

内存不足时,抛出
java.lang.OutOfMemoryError: Java heap space
1)代码
分配大量对象,超出jvm规定的堆范围即可
package com.itheima.jvm.demo;
import java.util.ArrayList;
import java.util.List;
/**
* 堆溢出
* -Xms20m -Xmx20m
*/
public class HeapOOM {
Byte[] bytes = new Byte[1024*1024];
public static void main(String[] args) {
List list = new ArrayList();
int i = 0;
while (true) {
System.out.println(++i);
list.add(new HeapOOM());
}
}
}
2)启动
注意启动时,指定一下堆的大小:

2)输出
1
2
3
4
5
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.itheima.jvm.demo.HeapOOM.<init>(HeapOOM.java:7)
at com.itheima.jvm.demo.HeapOOM.main(HeapOOM.java:13)
同样,线程共享的。
它主要用来存储类的信息、类里定义的常量、静态变量、编译器编译后的代码缓存。
注意!方法区在虚拟机规范里这是一个逻辑概念,它具体放在那个区域里没有严格的规定。
所以,hotspot 1.7 将它放在了堆的永久代里,1.8+单独开辟了一块叫metaspace来存放一部分内容(不是全部!定义的类对象在堆里)
具体方法区主要存什么东西呢?粗略的分,可以划分为两类:
类信息:主要指类相关的版本、字段、方法、接口描述、引用等
运行时常量池:编译阶段生成的常量与符号引用、运行时加入的动态变量
(常量池里的类变量,如对象或字符串,比较特殊,1.6和1.8位置不同,下面会讲到)
小提示:
这里经常会跟上面堆里的永久代混为一谈,实际上这是两码事
永久代是hotspot在1.7及之前才有的设计,1.8+,以及其他虚拟机并不存在这个东西。
可以说,永久代是1.7的hotspot偷懒的结果,他在堆里划分了一块来实现方法区的功能,叫永久代。因为这样可以借助堆的垃圾回收来管理方法区的内存,而不用单独为方法区再去编写内存管理程序。懒惰!
同时代的其他虚拟机,如J9,Jrockit等,没有这个概念。后来hotspot认识到,永久代来做这件事不是一个好主意。1.7已经从永久代拿走了一部分数据,直到1.8+彻底去掉了永久代,方法区大部分被移到了metaspace(再强调一下,不是全部!)
结论:
方法区是一定存在的,这是虚拟机规定的,但是是个逻辑概念,在哪里虚拟机自己去决定
而永久代不一定存在(hotspot 1.7 才有),已成为历史
1.6:OutOfMemoryError: PermGen space
1.8:OutOfMemoryError: Metaspace
1)原理

在1.6里,字符串常量是运行时常量池的一部分,也就是归属于方法区,放在了永久代里。
所以1.6环境下,让方法区溢出,只需要可劲造往字符串常量池中造字符串即可,这里用到一个方法:
/*
如果字符串常量池里有这个字符串,直接返回引用,不再额外添加
如果没有,加进去,返回新创建的引用
*/
String.intern()
2)代码
/**
* 方法区溢出,注意限制一下永久代的大小
* 编译的时候注意pom里的版本,要设置1.6,否则启动会有问题
* jdk1.6 : -XX:PermSize=6M -XX:MaxPermSize=6M
*/
public class ConstantOOM {
public static void main(String[] args) {
ConstantOOM oom = new ConstantOOM();
Set<String> stringSet = new HashSet();
int i = 0;
while (true) {
System.out.println(++i);
stringSet.add(String.valueOf(i).intern());
}
}
}
3)创建启动环境

4)异常信息:
...
19118
19119
19120
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at com.itheima.jvm.demo.ConstantOOM.main(ConstantOOM.java:19)
1)到了1.8,情况发生了变化
可以测试一下,1.8下无论指定下面的哪个参数,常量池运行都不会溢出,会一直打印下去
-XX:PermSize=6M -XX:MaxPermSize=6M
-XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M
2)配置运行环境

3)控制台信息
不会抛出异常,只要你jvm堆内存够,理论上可以一直打下去

4)为什么呢?
永久代我们加了限制,结果没意义,因为1.8里已经没有这货了
元空间也加了限制,同样没意义,那说明字符串常量池它不在元空间里!
那么,它在哪里呢?

jdk1.8以后,字符串常量池被移到了堆空间,和其他对象一样,接受堆的控制。
其他的运行时的类信息、基本数据类型等在元空间。
我们可以验证一下,对上面的运行时参数再加一个堆上限限制:
-Xms10m
-Xmx10m
运行环境如下:

运行没多久,你会得到以下异常:
……
84014
84015
84016
84017
84018
84019
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.lang.Integer.toString(Integer.java:403)
at java.lang.String.valueOf(String.java:3099)
at com.itheima.jvm.demo.ConstantOOM.main(ConstantOOM.java:18)
说明:1.8里,字符串inter()被放在了堆里,受最大堆空间的限制。
5)那如何才能让元空间溢出呢?
既然字符串常量池不在这里,那就换其他的。类的基本信息总在元空间吧?我们来试一下
cglib是一个apache下的字节码库,它可以在运行时生成大量的对象,我们while循环同时限制metaspace试试:
附:https://gitee.com/mirrors/cglib (想深入了解这个工具的猛击左边,这里不做过多讨论)
package com.itheima.jvm.demo;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* jdk8方法区溢出
* -XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M
*/
public class ConstantOOM8 {
public static void main(final String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOM.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(objects,args);
}
});
enhancer.create();
}
}
static class OOM{
}
}
6)运行设置

7)运行结果
Caused by: java.lang.OutOfMemoryError: Metaspace
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
结论:
jdk8引入元空间来存储方法区后,内存溢出的风险比历史版本小多了,但是在类超出控制的时候,依然会打爆方法区
为便于大家理解和记忆,下面我们用一个案例,把上面各个区串通起来。
假设有个Bootstrap的类,执行main方法。在jvm里,它从class文件到跑起来,大致经过如下步骤:


1)独享/共享的角度:
2)error的角度:
3)归属:
本文由
传智教育博学谷教研团队发布。如果本文对您有帮助,欢迎
关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。转载请注明出处!
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
我正在使用ruby1.9解析以下带有MacRoman字符的csv文件#encoding:ISO-8859-1#csv_parse.csvName,main-dialogue"Marceu","Giveittohimóhe,hiswife."我做了以下解析。require'csv'input_string=File.read("../csv_parse.rb").force_encoding("ISO-8859-1").encode("UTF-8")#=>"Name,main-dialogue\r\n\"Marceu\",\"Giveittohim\x97he,hiswife.\"\
在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/
exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby中使用两个参数异步运行exe吗?我已经尝试过ruby命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何rubygems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除
我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r
Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/
有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳
GivenIamadumbprogrammerandIamusingrspecandIamusingsporkandIwanttodebug...mmm...let'ssaaay,aspecforPhone.那么,我应该把“require'ruby-debug'”行放在哪里,以便在phone_spec.rb的特定点停止处理?(我所要求的只是一个大而粗的箭头,即使是一个有挑战性的程序员也能看到:-3)我已经尝试了很多位置,除非我没有正确测试它们,否则会发生一些奇怪的事情:在spec_helper.rb中的以下位置:require'rubygems'require'spork'