- 921. Minimum Add to Make Parentheses Valid 使括号有效的最少添加
- 915. Partition Array into Disjoint Intervals 分割数组
- 932. Beautiful Array 漂亮数组
- 940. Distinct Subsequences II 不同的子序列 II
虚拟机接收到new指令时,检查这个指令能否在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化;如果都没有,先执行类加载过程。
在类加载通过后,虚拟机为新对象分配内存(把一块确定大小的内存从Java堆中划分出来),内存大小在类加载完成后即可完全确定。
两种分配方式:
指针碰撞(指针加法 bump the pointer):假设Java堆中内存是绝对规整的,即使用过的内存在一边,空闲的内存在另外一边,中间放着一个指针作为指示器,通过移动指针实现内存分配;(如果加法后空余内存指针的值扔小于或等于指向末尾的指针,则代表分配成功)
空闲列表:如果Java堆中的内存并不是规整的,即已使用的内存和空闲的内存相互交错,虚拟机就必须维护一个列表,记录哪些内存块是可用的,通过从列表中寻找空间划分给对象实例来分配内存。
Java堆是否规整由所采用的垃圾收集器是否有压缩整理功能决定。
在虚拟机中创建对象不是线程安全的行为,可能出现在给对象A分配内存,指针还没来得及修改,对象B又使用了原来的指针来分配内存。有两种解决方案:
对分配内存空间的动作进行同步处理,实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;
把内存分配的动作按照线程划分在不同的空间中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Loal Allocation Buffer,TLAB)。
内存分配完成后,需要将分配到的内存空间都初始化为零值,保证对象的实例字段在Java代码中可以不赋初始值就可以直接使用,程序能访问到这些字段的数据类型对应的零值。
设置对象,把对象是哪个类的实例,如何才能找到类的元数据信息,对象的哈希码,对象的GC分代年龄等存放在对象头中。
对象在内存中存储的布局可以分为3块:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)。
对象头(包括两部分信息):
标记字:存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,官方称为Mark Word(非固定的数据结构,根据对象的状态复用自己的存储空间)。
在32位和64位的虚拟机中分别占32bit和64bit *
实例数据:对象真正存储的有效信息,即程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是子类自己定义的,都需要记录;
boolean/byte:1个字节;
short/char:2个字节;
int/float:4个字节;
long/double:8个字节;
String:4个字节;
引用类型(refrece type):4个字节;
对齐填充:不是必然存在,起着占位符的作用,由于HotSpot VM要求对象的大小必须是8字节的整数倍,当对象头和实例数据的大小之和不满足时,通过对齐填充来补全。
计算示例:
首先引入 OpenJDK官方提供的分析java内存布局工具 jol(Java Object Layout):
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
代码示例:
public class ObjectSize {
public static void main(String[] args) {
Person person = new Person("bgy", 10);
System.out.println(ClassLayout.parseInstance(person).toPrintable());
}
}
Java通过栈上的reference数据(局部变量表中的对象引用)来操作堆上的具体对象,reference只规定了指向对象的引用,没有定义怎么去定位,访问堆中的对象的位置。对象访问方式由虚拟机实现。
句柄访问:Java堆会划分出一块内存作为句柄池,reference存储的就是对象的句柄地址,句柄包含了对象实例数据和类型数据各自的地址信息。
优势:reference中存储的是稳定的句柄地址,对象移动时只改变句柄中的实例数据指针,不改变reference。
直接指针:reference中存储的直接就是对象地址,Java堆中放置访问对象类型数据(存放在方法区)的地址。
优势:速度更快,节省了一次指针定位的时间开销,HotSpot是使用直接指针访问。
为什么要分代:
堆内存是虚拟机管理的内存中最大的一块,也是垃圾回收最频繁的区域,程序所有的对象实例都存放在堆内存中;
如果堆内存没有区域划分,所有新创建的对象和生命周期很长的对象都放在一起,随着程序的执行,堆内存需要频繁地进行垃圾回收,而每次回收都要遍历所有对象,遍历这些对象所花费的时间代价是很大的,所以会严重影响GC效率;
分代的优点:
有了内存分代,新创建的对象会在新生代中分配内存;经历多次回收仍活下来的对象和大对象存放在老年代中;静态属性、类信息等存放在永久代中;
新生代中的对象存活时间短,只需要在新生代区域中频繁进行GC;老年代中对象生命周期长,内存回收的频率相对较低,不需要频繁进行回收;
可以根据不同年代的特点来采用合适的垃圾收集算法和垃圾收集器,所以分代收集提升了垃圾回收效率;
Java虚拟机将堆内存划分为新生代、老年代、永久代(也就是方法区的实现,在JDK1.8中,HotSpot虚拟机采用了元数据空间(Metaspace)来实现方法区)。
内存分代示意图:
新生成的对象优先存放在新生代中,新生代对象朝生夕死,存活率很低;在新生代中,常规应用进行一次垃圾收集一般可以回收70%~95%的空间,回收效率很高。
HotSpot虚拟机将新生代划分为三块:一块较大的Eden空间和两块较小的大小相同的Survivor空间;默认情况下,JVM采取的一种动态分配的策略,根据生成对象的速率,以及Survivor区的使用情况动态调整Eden区和Survivor区的比例。也可以通过参数 -XX:SurvivorRatio 来固定这个比例,比如设置为8,则比例为 8:1:1。
划分的目的是因为HotSpot采用复制算法来回收新生代,设置这个比例是为了充分利用内存空间,减少浪费。新生成的对象在Eden区分配,大对象直接进入老年代,当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。
Minor GC的过程:
老年代是存放长期存活的对象,或者大对象;老年代中的对象生命周期较长,存活率较高,在老年代中进行GC的频率相对较低,回收的速度也比较慢。
当老年代需要垃圾回收时,代表堆的空间已经耗尽了,这个时候JVM往往需要做一次全堆扫描,耗时较长。
老年代空间分配担保:
如果在新生代中有大量的对象存活下来,并且Survivor区放不下这些对象的时候,就会寻求老年代进行帮助存放(空间分配担保);
空间分配担保步骤:
在执行任何一次Minor GC之前,JVM都会先检查一下老年代的可用内存空间是否大于新生代所有对象的总大小(因为最极端情况下,在Minor GC之后,所有对象都存活下来了,就会全部进入老年代);如果大于,直接进行Minor GC;
如果老年代的可用内存大小 小于新生代的全部大小,就得看一个JVM参数:“-XX:-HandlePromotionFailure”是否配置;(JDK 1.6 之后已经废弃,默认配置)
如果配置了,就判断老年代的可用内存大小,是否大于之前每一次Minor GC后进入老年代的平均大小;
如果大于这个平均大小,则会冒险尝试一次Minor GC;此时进行Minor GC有几种可能:
Minor GC之后,存活对象 小于Survivor区的大小,则存活对象直接进入Survivor区;
Minor GC之后,存活对象 大于Survivor区的大小,但是 小于老年代可用内存大小,则直接进入老年代;
Minor GC之后,存活对象 大于Survivor区的大小,且也大于老年代可用内存大小,即此时老年代也放不下这些对象了;就会发生“Handle Promotion Failure”的情况,且触发一次 Full GC;
如果小于这个平均大小,直接触发一次 Full GC;
如果没有配置,直接触发一次 Full GC;
对象进入老年代的条件:
永久代存储类信息、常量、静态变量、即时编译器编译后的代码等数据;在元数据空间满了的时候,也会进行垃圾回收;
Minor GC是指发生在新生代的垃圾回收动作,因为Java对象大多都具备朝生夕灭的特性,所以Monir GC非常频繁,一般回收速度也比较快;当Eden区空间不足以为对象分配内存时,会触发Minor GC;
理想情况下,Eden区中的对象基本都死亡了,那么需要复制的数据很少,因此新生代采用 标记-复制算法;Minor GC另外一个好处是不用对整个堆进行垃圾回收;
但是有个问题:老年代的对象可能引用新生代的对象(也就是说,标记存活对象的时候,需要扫描老年代中的对象,如果老年代对象拥有对新生代对象的引用,那么这个引用也会被作为GC Roots,这里不就又做了一次全堆扫描?(JVM使用卡表解决)
这里其实Old GC和Full GC并不相等,Old GC专指老年代的GC,而Full GC会包含新生代、老年代、永久代的所有的GC。
Full GC的速度一般比Major GC慢10倍以上;所以如果系统频繁出现Full GC,会导致系统性能被严重影响,出现频繁卡顿现象;当老年代内存不足或者显示调用System.gc()方法时,会触发Full GC;
因为老年代中的对象要不就是存活时间较长,要不就是比较大,就不能采用复制算法,所以老年代采用的是 标记整理算法;
出现了Full GC经常会伴随至少一次的Minor GC(并非绝对,在Parallel Scavenge收集器的收集策略里就有直接进行Full GC的策略选择过程),因为需要把老年代里没有引用的对象给回收掉,才能让Minor GC后存活的对象进入老年代。
如果要是Full GC后,老年代还是没有足够的空间存放Minor GC后存活的对象,那就会导致“OOM”内存溢出了。
我有一个应用程序,它会抛出 GKSession 并在各种条件下(连接超时、 session 失败等)创建一个新的 GKSession。不过,我遇到了内存泄漏问题,并且有时会在重新连接几次循环后崩溃。
比如我在宿主代码中有一个浮点指针 float *p 是否可以确定他指向的内存类型(设备/主机)? 最佳答案 在 UVA system 中, 运行时 API 函数 cudaPointerGetAttri
我已将项目转换为 .Net 4.0 并且以下代码不起作用: typeof(RuntimeTypeHandle).GetMethod("Allocate", BindingFlags.Instance
当我声明 char ch = 'ab' 时,ch 只包含 'b',为什么它不存储 'a'? #include int main() { char ch = 'ab'; printf("%c"
我对 Disk Sector 和 Block 有疑问。扇区是一个单位,通常为 512 字节或 1k、2k、4k 等取决于硬件。文件系统 block 大小是一组扇区大小。 假设我正在存储一个 5KB 的
假设我有 8 个人和5000 个苹果。 我想将所有苹果分发给所有 8 个人,这样我就没有苹果了。 但每个人都应该得到不同数量 将它们全部分发出去的最佳方式是什么? 我是这样开始的: let peopl
我正在构建的网站顶部有一个搜索栏。与 Trello 或 Gmail 类似,我希望当用户按下“/”键时,他们的焦点就会转到该搜索框。 我的 JavaScript 看起来像这样: document.onk
我有一小段代码: if (PZ_APP.dom.isAnyDomElement($textInputs)){ $textInputs.on("focus", function(){
我观察到以下行为。 接受了两个属性变量。 @property (nonatomic, retain) NSString *stringOne; @property (nonatomic, assign
我正在解决这样的问题 - 实现一个计算由以下内容组成的表达式的函数以下操作数:“(”、“)”、“+”、“-”、“*”、“/”。中的每个数字表达式可能很大(与由字符串表示的一样大)1000 位)。 “/
我有一组主机和一组任务。 每个主机都有 cpu、mem 和任务容量,每个任务都有 cpu、mem 要求。 每个主机都属于一个延迟类别,并且可以与具有特定延迟值的其他主机通信。 每个任务可能需要以等于或
该程序的作用:从文件中读取一个包含 nrRows 行和 nrColomns 列的矩阵(二维数组)。矩阵的所有元素都是 [0,100) 之间的整数。程序必须重新排列矩阵内的所有元素,使每个元素等于其所在
世界!我有个问题。今天我尝试创建一个代码,它可以找到加泰罗尼亚语号码。但是在我的程序中可以是长数字。我找到了分子和分母。但我不能分割长数字!此外,只有标准库必须在此程序中使用。请帮帮我。这是我的代码
我确定我遗漏了一些明显的东西,但我想在 Objective C 中创建一个 NSInteger 指针的实例。 -(NSInteger*) getIntegerPointer{ NSInteger
这个问题在这里已经有了答案: Difference between self.ivar and ivar? (4 个答案) 关闭 9 年前。
我如何将 v[i] 分配给一系列整数(v 的类型是 vector )而无需最初填充 最佳答案 你的意思是将 std::vector 初始化为一系列整数? int i[] = {1, 2, 3, 4,
我想寻求分配方面的帮助....我把这个作业带到了学校......我必须编写程序来加载一个 G 矩阵和第二个 G 矩阵,并搜索第二个 G 矩阵以获取存在数第一个 G 矩阵的......但是,当我尝试运行
我必须管理资源。它基本上是一个唯一的编号,用于标识交换机中的第 2 层连接。可以有 16k 个这样的连接,因此每次用户希望配置连接时,他/她都需要分配一个唯一索引。同样,当用户希望删除连接时,资源(号
是否有任何通用的命名约定来区分已分配和未分配的字符串?我正在寻找的是希望类似于 us/s 来自 Making Wrong Code Look Wrong ,但我宁愿使用常见的东西也不愿自己动手。 最佳
我需要读取一个 .txt 文件并将文件中的每个单词分配到一个结构中,该结构从结构 vector 指向。我将在下面更好地解释。 感谢您的帮助。 我的程序只分配文件的第一个字... 我知道问题出在函数 i
我是一名优秀的程序员,十分优秀!