1.Algorithm:https://leetcode.com/problems/maximum-product-subarray/
求最大子序列乘积。
方法1 递归遍历每一种子串的序列,每次计算保留最大值在函数中传递。遍历完成得到最大值。
方法2 dp。dp,因为无法确定下一个访问到的是正数还是负数,所以dp方程是二维的,需要记录一个最大值和一个最小值。如果遍历下一个数是负数,用最小值与相乘,如果是正数,则用最大值相乘。同时记录一个最大值,数组遍历完成得到最大值。
2.Review:https://shipilev.net/jvm/anatomy-quarks/5-tlabs-and-heap-parsability/
TLAB与堆可分析性
在 GC 理论中,可靠的收集器需要维护一个重要的属性——堆的可解析性,也就是将堆塑造成可以解析对象、字段等的形式,而且不需要复杂的元数据的支持。如果堆是可解析的,那么我们可以假设已经分配的堆从头到尾是一个连续的对象流。严格来说这并不是一个必要属性,但是这个属性使得 GC 实现、测试和调试更简单。
由于TLAB,我们无法精确的知道TLAB内部内存是如何分配的。所以作者设计了一个实验,在多个TLAB填充数据,分析最后的dump文件来观察TLAB内部的内存分配情况
...........|=================== ]............
^ ^ ^
TLAB start TLAB used TLAB end
我们可以停止线程,然后请求线程在 TLAB 的空闲部分中分配填充对象,使得这部分堆内存可解析:
...........|===================!!!!!!!!!!!]............
^ ^ ^
TLAB start TLAB used TLAB end
实验代码如下:
起很多线程分配自己的 TLAB,然后主线程持续分配对象耗尽 Java 堆内存,最终程序将会以 OutOfMemoryException 崩溃,这会触发堆转储(heap dump)。
import java.util.*;
import java.util.concurrent.*;
public class Fillers {
public static void main(String... args) throws Exception {
final int TRAKTORISTOV = 300;
CountDownLatch cdl = new CountDownLatch(TRAKTORISTOV);
for (int t = 0 ; t < TRAKTORISTOV; t++) {
new Thread(() -> allocateAndWait(cdl)).start();
}
cdl.await();
List<Object> l = new ArrayList<>();
new Thread(() -> allocateAndDie(l)).start();
}
public static void allocateAndWait(CountDownLatch cdl) {
Object o = new Object(); // Request a TLAB
cdl.countDown();
while (true) {
try {
Thread.sleep(1000);
} catch (Exception e) {
break;
}
}
System.out.println(o); // Use the object
}
public static void allocateAndDie(Collection<Object> c) {
while (true) {
c.add(new Object());
}
}
}
为了控制 TLAB 的大小,我们继续使用 Epsilon GC。启动参数为 -Xmx1G -Xms1G -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:+HeapDumpOnOutOfMemoryError
,这样程序将会很快崩溃,并且输出堆转储。
在 Eclipse Memory Analyzer (MAT) 中打开这个堆转储——我非常喜欢这个工具——我们可以看到下述类直方图:
Class Name | Objects | Shallow Heap |
-----------------------------------------------------------------------
| | |
int[] | 1,099 | 814,643,272 |
java.lang.Object | 9,181,912 | 146,910,592 |
java.lang.Object[] | 1,521 | 110,855,376 |
byte[] | 6,928 | 348,896 |
java.lang.String | 5,840 | 140,160 |
java.util.HashMap$Node | 1,696 | 54,272 |
java.util.concurrent.ConcurrentHashMap$Node| 1,331 | 42,592 |
java.util.HashMap$Node[] | 413 | 42,032 |
char[] | 50 | 37,432 |
-----------------------------------------------------------------------
int[]
消耗了最多堆内存!这是我们的填充对象。当然,这个实验有一些注意事项。
首先,我们配置 Epsilon 具有静态的 TLAB 大小。高性能的收集器将会自适应 TLAB 的大小,当线程已经分配了一些对象,但是仍然占用很多 TLAB 内存的时候,这种自适应的机制将会最小化无效内存的占用。这也是不要设置太大 TLAB 的一个原因。如果设置了较大的 TLAB ,那么在一个持续分配对象的线程中仍然可能观察到填充对象,但是这并不是真正的对象。
然后,我们通过配置 MAT 来展示不可达的对象。从定义上来说,填充对象是不可达的。它们出现在堆转储中仅仅是堆可解析属性的副作用。这些对象并不是真的存在,一个成熟的堆转储分析器将会为你过滤掉这些对象——这也就是 900 MB 对象就能耗尽 1G 堆内存的一个原因。
在网上找到一篇译文,翻参考
https://www.jianshu.com/p/22f041895d01
3.Tip:跟着网络编程实战专栏,复习了一下select,poll,epoll。linux网络编程必备底层的知识。