1.年轻代存活的对象太多,老年代了放不下

01.示例代码

public class DemoTest1 {
public static void main(String[] args) {
byte[] array1 = new byte[4 * 1024 * 1024];
array1 = null; byte[] array2 = new byte[2 * 1024 * 1024];
byte[] array3 = new byte[2 * 1024 * 1024];
byte[] array4 = new byte[2 * 1024 * 1024];
byte[] array5 = new byte[128 * 1024]; byte[] array6 = new byte[2 * 1024 * 1024]; }

02.启动JVM参数

-XX:NewSize=10485760 -XX:MaxNewSize=10485760 -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:SurvivorRatio=8  -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=3145728 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log

其中,参数-XX:PretenureSizeThreshold,参数要设置大对象阈值为3MB,也就是超过3MB,就直接进入老年代。

大对象大小是3MB。一旦对象大小超过3MB,不会进入新生代,直接进入老年代。

启动命令:

java  -jar -XX:NewSize=10485760 -XX:MaxNewSize=10485760 -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:SurvivorRatio=8  -XX:MaxTenuringThre
shold=15 -XX:PretenureSizeThreshold=3145728 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log jvm-demo.jar

03.GC日志

启动之后就得到如下GC日志:

Java HotSpot(TM) 64-Bit Server VM (25.151-b12) for windows-amd64 JRE (1.8.0_151-b12), built on Sep  5 2017 19:33:46 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 16703268k(7458748k free), swap 23781156k(9784196k free)
CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=15 -XX:NewSize=10485760 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=3145728 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC
0.174: [GC (Allocation Failure) 0.174: [ParNew (promotion failed): 7457K->8328K(9216K), 0.0046949 secs]
0.179: [CMS: 8194K->6962K(10240K), 0.0033396 secs] 11553K->6962K(19456K), [Metaspace: 2970K->2970K(1056768K)], 0.0089224 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
par new generation total 9216K, used 2130K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 26% used [0x00000000fec00000, 0x00000000fee14930, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
concurrent mark-sweep generation total 10240K, used 6962K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 2976K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 330K, capacity 386K, committed 512K, reserved 1048576K

04.分析GC日志

先看如下代码:

 byte[] array1 = new byte[4 * 1024 * 1024];
array1 = null;

这行代码直接分配了一个4MB的大对象,此时这个对象会直接进入老年代,接着array1不再引用这个对象。

此时内存分配如下:

紧接着就是如下代码

byte[] array2 = new byte[2 * 1024 * 1024];
byte[] array3 = new byte[2 * 1024 * 1024];
byte[] array4 = new byte[2 * 1024 * 1024];
byte[] array5 = new byte[128 * 1024];

连续分配了4个数组,其中3个是2MB的数组,1个是128KB的数组,如下图所示,全部会进入Eden区域中。

接着会执行如下代码:byte[] array6 = new byte[2 * 1024 * 1024];。此时还能放得下2MB的对象吗?

不可能了,因为Eden区已经放不下了。因此此时会直接触发一次Young GC。

我们看下面的GC日志:

0.174: [GC (Allocation Failure) 0.174: [ParNew (promotion failed): 7457K->8328K(9216K), 0.0046949 secs]

这行日志显示了,Eden区原来是有7000多KB的对象,但是回收之后发现一个都回收不掉,因为上述几个数组都被变量引用了。

所以此时,一定会直接把这些对象放入到老年代里去,但是此时老年代里已经有一个4MB的数组了,还能放的下3个2MB的数组和1个128KB的数组吗?

明显是不行的,此时一定会超过老年代的10MB大小。

所以此时看gc日志:

0.179: [CMS: 8194K->6962K(10240K), 0.0033396 secs] 11553K->6962K(19456K), [Metaspace: 2970K->2970K(1056768K)], 0.0089224 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]

此时执行了CMS垃圾回收器的Full GC,Full GC其实就是会对老年代进行Old GC,同时一般会跟一次Young GC关联,还会触发一次元数据区(永久代)的GC。

在CMS Full GC之前,就已经触发过Young GC了,此时可以看到此时Young GC就已经有了,接着就是执行针对老年代的Old GC,也就是如下日志:

CMS: 8194K->6962K(10240K), 0.0033396 secs

这里看到老年代从8MB左右的对象占用,变成了6MB左右的对象占用,这是怎么个过程呢?

很简单,一定是在Young GC之后,先把2个2MB的数组放入了老年代,如下图。

此时要继续放1个2MB的数组和1个128KB的数组到老年代,一定会放不下,所以此时就会触发CMS的Full GC。

然后此时就会回收掉其中的一个4MB的数组,因为他已经没人引用了,如下图所示。

所以再看CMS的垃圾回收日志:CMS: 8194K->6962K(10240K), 0.0033396 secs,他是从回收前的8MB变成了6MB,就是上图所示。

最后在CMS Full GC执行完毕之后,其实年轻代的对象都进入了老年代,此时最后一行代码要在年轻代分配2MB的数组就可以成功了,如下图。

05.总结

这是一个触发老年代GC的案例,就是年轻代存活的对象太多放不下老年代了,此时就会触发CMS的Full GC。

2.老年代可用空间小于了历次Young GC后升入老年代的对象的平均大小

01.示例代码

public class DemoTest1 {
public static void main(String[] args) {
byte[] array1 = new byte[1 * 1024 * 1024];
array1 = null;
byte[] array2 = new byte[1 * 1024 * 1024];
array2 = null;
byte[] array3 = new byte[1 * 1024 * 1024];
array3 = null;
byte[] array4 = new byte[1 * 1024 * 1024];//触发YGC 1MB 1 array1 = new byte[1 * 1024 * 1024];
array1 = null;
array2 = new byte[1 * 1024 * 1024];
array2 = null;
array3 = new byte[1 * 1024 * 1024];//触发YGC Y 1MB O 1MB 2
array3 = null; byte[] array5 = new byte[1 * 1024 * 1024];// Y 2MB O 1MB
array1 = new byte[1 * 1024 * 1024];// Y 3MB
array1 = null;
array2 = new byte[1 * 1024 * 1024];// Y 1MB O 2MB YGC 3 array2 = null;
array3 = new byte[1 * 1024 * 1024];//Y 2MB O 2MB
array3 = null;
byte[] array6 = new byte[1 * 1024 * 1024];//Y 3MB O 2MB
array1 = new byte[1 * 1024 * 1024];//Y 1MB O 3MB YGC 4 array1 = null;
array2 = new byte[1 * 1024 * 1024];//Y 2MB
array2 = null;
array3 = new byte[1 * 1024 * 1024];//Y 3MB
array3 = null;
byte[] array7 = new byte[1 * 1024 * 1024];//YGC 5 }
}

02.启动JVM参数

-XX:NewSize=5M -XX:MaxNewSize=5M -XX:InitialHeapSize=10M -XX:MaxHeapSize=10M -XX:SurvivorRatio=8  -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=2M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log

其中,参数-XX:PretenureSizeThreshold,参数要设置大对象阈值为2MB,也就是超过2MB,就直接进入老年代。

大对象大小是3MB。一旦对象大小超过3MB,不会进入新生代,直接进入老年代。

启动命令:

java  -jar -XX:NewSize=5M -XX:MaxNewSize=5M -XX:InitialHeapSize=10M -XX:MaxHeapSize=10M -XX:SurvivorRatio=8  -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=2M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log jvm-demo.jar

03.GC日志

启动之后就得到如下GC日志:

老年代

年轻代

Java HotSpot(TM) 64-Bit Server VM (25.151-b12) for windows-amd64 JRE (1.8.0_151-b12), built on Sep  5 2017 19:33:46 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 16703268k(7221016k free), swap 23781156k(8613656k free)
CommandLine flags: -XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:MaxNewSize=5242880 -XX:MaxTenuringThreshold=15 -XX:NewSize=5242880 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=2097152 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC
0.121: [GC (Allocation Failure) 0.121: [ParNew: 3155K->512K(4608K), 0.0041165 secs] 3155K->766K(9728K), 0.0042644 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.125: [GC (Allocation Failure) 0.125: [ParNew: 3663K->0K(4608K), 0.0016667 secs] 3917K->1732K(9728K), 0.0017448 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.127: [GC (Allocation Failure) 0.127: [ParNew: 3142K->0K(4608K), 0.0013221 secs] 4875K->2756K(9728K), 0.0013592 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.129: [GC (CMS Initial Mark) [1 CMS-initial-mark: 2756K(5120K)] 4878K(9728K), 0.0004498 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.129: [CMS-concurrent-mark-start]
0.130: [GC (Allocation Failure) 0.130: [ParNew: 3146K->0K(4608K), 0.0005869 secs] 5902K->2756K(9728K), 0.0006362 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.131: [GC (Allocation Failure) 0.131: [ParNew: 3148K->0K(4608K), 0.0007974 secs] 5904K->3780K(9728K), 0.0008262 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
par new generation total 4608K, used 2207K [0x00000000ff600000, 0x00000000ffb00000, 0x00000000ffb00000)
eden space 4096K, 53% used [0x00000000ff600000, 0x00000000ff827f38, 0x00000000ffa00000)
from space 512K, 0% used [0x00000000ffa80000, 0x00000000ffa80000, 0x00000000ffb00000)
to space 512K, 0% used [0x00000000ffa00000, 0x00000000ffa00000, 0x00000000ffa80000)
concurrent mark-sweep generation total 5120K, used 3780K [0x00000000ffb00000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 2976K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 330K, capacity 386K, committed 512K, reserved 1048576K

04.分析GC日志

(1).代码块1

先看如下代码:

  byte[] array1 = new byte[1 * 1024 * 1024];
array1 = null;
byte[] array2 = new byte[1 * 1024 * 1024];
array2 = null;
byte[] array3 = new byte[1 * 1024 * 1024];
array3 = null;
byte[] array4 = new byte[1 * 1024 * 1024];

这段代码直接分配了4个1MB的数组,并且在第4个数组的时候,会因为新生代内存不足触发YGC。

此时内存分配如下:

对应如下GC日志:

0.121: [GC (Allocation Failure) 0.121: [ParNew: 3155K->512K(4608K), 0.0041165 secs] 3155K->766K(9728K), 0.0042644 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

此时,可以看到新生代就只剩512K的对象,这个奇怪的512KB的对象进入Survivor From区。

那么大小为1MB的数组对象去哪里呢?肯定不是这个奇怪的512KB的对象。

这1MB的数组首先肯定是准备进入Survivor From区,可是,在我们设置的JVM参数下,只有0.5MB,明显是不够分配的。根据JVM YoungGC的规则,Survivor区放不下GC之后存活的对象,直接进入老年代

所以,1MB的数组对象是直接进入到老年代了。

此时,内存分配如下:

(2).代码块2

紧接这就是这块代码:

 array1 = new byte[1 * 1024 * 1024];
array1 = null;
array2 = new byte[1 * 1024 * 1024];
array2 = null;
array3 = new byte[1 * 1024 * 1024];

这里再次创建了3个1MB的数组对象,并且会触发一次YoungGC;

对应 GC日志如下:

0.125: [GC (Allocation Failure) 0.125: [ParNew: 3663K->0K(4608K), 0.0016667 secs] 3917K->1732K(9728K), 0.0017448 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

此时,Young GC之后,新生代变成0KB,那么存活的大小为1MB的数组对象去哪里呢?

这1MB的数组首先肯定是准备进入Survivor From区,可是,在我们设置的JVM参数下,只有0.5MB,明显是不够分配的。根据JVM YoungGC的规则,Survivor区放不下GC之后存活的对象,直接进入老年代

所以,1MB的数组对象是直接进入到老年代了。

之前看到的未知的对象512KB也进入到老年代,此时内存分配如下:

(3).代码块3
array3 = null;
byte[] array5 = new byte[1 * 1024 * 1024];
array1 = new byte[1 * 1024 * 1024];
array1 = null;
array2 = new byte[1 * 1024 * 1024];

这里再次创建了3个1MB的数组对象,并且会触发一次YoungGC;

对应的GC日志如下:

0.127: [GC (Allocation Failure) 0.127: [ParNew: 3142K->0K(4608K), 0.0013221 secs] 4875K->2756K(9728K), 0.0013592 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

此时内存分配如下:

(4).代码块4
array2 = null;
array3 = new byte[1 * 1024 * 1024];//Y 2MB O 2MB
array3 = null;
byte[] array6 = new byte[1 * 1024 * 1024];
array1 = new byte[1 * 1024 * 1024];

这里再次创建了3个1MB的数组对象,并且会触发一次YoungGC;并且在这儿,触发Young GC之前触发了一次CMS的Old GC,触发的条件就是老年代可用空间小于了历次Young GC后升入老年代的对象的平均大小。此时新生代大小变成0KB

对应的GC日志如下:

0.129: [GC (CMS Initial Mark) [1 CMS-initial-mark: 2756K(5120K)] 4878K(9728K), 0.0004498 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.129: [CMS-concurrent-mark-start]
0.130: [GC (Allocation Failure) 0.130: [ParNew: 3146K->0K(4608K), 0.0005869 secs] 5902K->2756K(9728K), 0.0006362 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

此时内存分配如下:

(5).代码块5
array1 = null;
array2 = new byte[1 * 1024 * 1024];//Y 2MB
array2 = null;
array3 = new byte[1 * 1024 * 1024];//Y 3MB
array3 = null;
byte[] array7 = new byte[1 * 1024 * 1024];

此时,再创建3个1MB的数组对象,再次触发一次Young GC,执行完YoungGC,此时新生代大小变成0KB;

对应的GC日志如下:

0.131: [GC (Allocation Failure) 0.131: [ParNew: 3148K->0K(4608K), 0.0007974 secs] 5904K->3780K(9728K), 0.0008262 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

此时内存分配如下:

(6).总结

如下GC堆内存日志我们也可以去验证下上面的推测:

此时新生代使用了53%的大小,我们还有一个1MB的数组,可能还存在一些未知对象。

在老年代中使用了大约3MB的空间,应该就是上图中的对象。

Heap
par new generation total 4608K, used 2207K [0x00000000ff600000, 0x00000000ffb00000, 0x00000000ffb00000)
eden space 4096K, 53% used [0x00000000ff600000, 0x00000000ff827f38, 0x00000000ffa00000)
from space 512K, 0% used [0x00000000ffa80000, 0x00000000ffa80000, 0x00000000ffb00000)
to space 512K, 0% used [0x00000000ffa00000, 0x00000000ffa00000, 0x00000000ffa80000)
concurrent mark-sweep generation total 5120K, used 3780K [0x00000000ffb00000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 2976K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 330K, capacity 386K, committed 512K, reserved 1048576K

3.几个触发Full GC的条件

第一:是老年代可用内存小于新生代全部对象的大小,如果没开启空间担保参数,会直接触发Full GC,所以一般空间担保参数都会打开;注:jDK1.8之后已经取消了-XX:-HandlePromotionFailure 机制

第二:是老年代可用内存小于历次新生代GC后进入老年代的平均对象大小,此时会提前Full GC;

第三:是新生代Minor GC后的存活对象大于Survivor,那么就会进入老年代,此时老年代内存不足。

上述情况都会导致老年代Full GC。

第四:就是“-XX:CMSInitiatingOccupancyFaction”参数,

如果老年代可用内存大于历次新生代GC后进入老年代的对象平均大小,但是老年代已经使用的内存空间超过了这个参数指定的比例,也会自动触发Full GC。默认92%

结合代码和内存变化图一步步弄懂JVM的FullGC的更多相关文章

  1. 一文带你弄懂 JVM 三色标记算法!

    大家好,我是树哥. 最近和一个朋友聊天,他问了我 JVM 的三色标记算法.我脑袋一愣发现竟然完全不知道!于是我带着疑问去网上看了几天的资料,终于搞清楚啥事三色标记算法,它是用来干嘛的,以及它和 CMS ...

  2. 一步步弄懂HTTPS

    为什么要使用HTTPS? HTTP采用明文传输,存在被监听.内容被篡改.身份被冒充的可能.为了保证安全性,需要对数据进行加密,这便有了HTTPS. 一步步分析HTTPS 1. 采用哪种加密方式加密数据 ...

  3. 不是吧!做了两年java还没弄懂JVM堆?进来看看你就明白了

    堆的核心概述 一个JVM实例只存在一个堆内存,堆也是java内存管理的核心区域Java堆区在jvm启动的时候被创建,其空间大小也就确定了.是jvm管理的最大一块内存空间.(堆内存的大小可以调节)< ...

  4. 阿里P8Java大牛仅用46张图让你弄懂JVM的体系结构与GC调优。

    本PPT从JVM体系结构概述.GC算法.Hotspot内存管理.Hotspot垃圾回收器.调优和监控工具六大方面进行讲述.图文并茂不生枯燥. 此PPT长达46页,全部展示篇幅过长,本文优先分享前十六页 ...

  5. 46张PPT弄懂JVM、GC算法和性能调优!

    来源:cnblogs.com/cyfonly/p/5807121.html 本PPT从JVM体系结构概述.GC算法.Hotspot内存管理.Hotspot垃圾回收器.调优和监控工具六大方面进行讲述. ...

  6. Golang, 以 9 个简短代码片段,弄懂 defer 的使用特点

    作者:林冠宏 / 指尖下的幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:http://www.cnblogs.com/linguan ...

  7. Pythontutor:可视化代码在内存的执行过程

    http://www.pythontutor.com/visualize.html今天去问开发一个Python浅拷贝的问题,开发给了一个神器,可以可视化代码在内存的执行过程,一看即懂,太NB了!~真是 ...

  8. 解密native代码的内存使用

    前言 无论是从资源使用的角度,还是从发现内存泄漏问题的角度来看,在性能测试或者系统的稳定性测试中,内存的使用情况是一个很重要的监控点.为保证项目的质量前移,输入法内核测试小组的同学分配到了一个新的任务 ...

  9. c++ 汇编代码看内存分配

    汇编代码看内存分配 (1). 程序运行时分为存储区域分为 存储区域 存储内容 extra 代码区 存放代码指令,包括除字符串常量的字面值 静态存储区 存放静态变量和全局变量 执行main之前就分配好了 ...

  10. 必须弄懂的495个C语言问题

    1.1 我如何决定使用那种整数类型? 如果需要大数 值(大于32, 767 或小于¡32, 767), 使用long 型.否则, 如果空间很重要(如有大数组或很多结构), 使用short 型.除此之外 ...

随机推荐

  1. Python:多进程并行编程与进程池

    Python的并行编程可以采用multiprocessing或mpi4py模块来完成. multiprocessing是Python标准库中的模块,实现了共享内存机制,也就是说,可以让运行在不同处理器 ...

  2. ATM购物车

    ATM项目实现思路: ATM架构设计 三层架构 core目录下的src.py(浏览器) (展示层) interface目录下的多个py文件(框架) (核心逻辑层) db目录下db_handler.py ...

  3. Surp Suite入门

    BurpSuite代理工具是以拦截代理的方式,拦截所有通过代理的网络流量,如客户端的请求数据.服务器端的返回信息等.Burp Suite主要拦截HTTP和HTTPS 协议的流量,通过拦截,Burp S ...

  4. virtualenv 配置(windows)

    1.在线安装 virtualenv pip install virtualenv 2.离线安装 下载virtualenv包,解压并进入setup.py所在文件夹中 python setup.py in ...

  5. 使用Typora写博客,图片即时上传

    背景 习惯使用markdown的人应该都知道Typora这个神器,它非常简洁高效.虽然博客园的在线markdown编辑器也不错,但毕竟是网页版,每次写东西需要登录系统-进后台-找到文章-编辑-保存草稿 ...

  6. 跳石头(NOIP2015)

    AcWing 洛谷 解题思路 这题看到最短跳跃距离尽可能长就会想到二分 但是我们二分的\(check\)函数怎么写呢 可以看到限制条件移走的石头最多只能是\(m\)块 我们二分这个最短距离 容易想到一 ...

  7. Hive详解(06) - Hive调优实战

    Hive详解(06) - Hive调优实战 执行计划(Explain) 基本语法 EXPLAIN [EXTENDED | DEPENDENCY | AUTHORIZATION] query 案例实操 ...

  8. 一文读懂Go Http Server原理

    hello大家好呀,我是小楼,这是系列文<Go底层原理剖析>的第二篇,依旧是分析 Http 模块,话不多说,开始. 从一个 Demo 入手 俗话说万事开头难,但用 Go 实现一个 Http ...

  9. djiango框架推导过程,jinja2模板语法,jiango简介,基本操作命令

    djiango框架推导过程,jinja2模板语法,jiango简介,基本操作命令 一.web框架前戏 web 框架可以理解为是基于会联网的web服务端>>>socket服务端 1.w ...

  10. js函数中的this指向

    写代码的时候遇到这个问题了,在这里复习一下 非箭头函数 非箭头函数的this指向比较好理解,就是调用这个函数的对象,举个栗子: var obj = { foo: { bar: 3, foo:{ bar ...