Repository: kubernetes
Issue: informer and any other things depends on tools/cache.Reflector may suffer unexpected memory consumption
Labels: kind/bug,sig/scalability,sig/api-machinery,triage/accepted

### What happened?

Let's revisit Reflector work flow:
1. `Reflector.ListAndWatch` called `list` https://github.com/kubernetes/client-go/blob/b2097e607c196a94c5e1100cb073fa0e8277fd7e/tools/cache/reflector.go#L254-L257
2. `list` collects `items` via `meta.ExtractList` and called `syncWith` to store all items into the memory kv store https://github.com/kubernetes/client-go/blob/b2097e607c196a94c5e1100cb073fa0e8277fd7e/tools/cache/reflector.go#L441-L446
3. `meta.ExtractList` is using reflect to get each item and copy them to another slice https://github.com/kubernetes/apimachinery/blob/915d89afb4cbcaca892b455a5550495411a37a84/pkg/api/meta/help.go#L169

Here is the problem, `meta.ExtractList` use [`items.Index`](https://pkg.go.dev/reflect#Value.Index) to get each item as a pointer, usually equals to `&podList.Items[i]` or something else, this item is then being put to store by its key, but it still keep  referrence to the whole podList object which may consume lots of memory when in a large cluster(in my case, 10,000 pods consumes 400MiB around), so the store will keep all podList and delta pods from `watch` which may double the memory consumption in some edge case, e.g. whole podLists from `list` each of which has one item still alive in store and nearly whole pods from `watch`, nearly all pods have two copy in store, one from list and one from watch.

I'm using [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime), which depends on [`sharedIndexInformer`](https://pkg.go.dev/k8s.io/client-go/tools/cache#NewSharedIndexInformer), which depends on Reflector so i found this problem but can not fix it easily.

You can verify this memory "leak"(i dont have better word to describe this problem) in this mini program:

```go
package main

import (
	"bytes"
	"fmt"
	"runtime"
	"strings"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/meta"
)

func main() {
	const cnt = 10000
	podList := v1.PodList{
		Items: make([]v1.Pod, cnt),
	}
	list, _ := meta.ExtractList(&podList)
	fmt.Printf("Type: %T\n", list[0])

	str := string(bytes.Repeat([]byte("a"), 20_000))
	for i := 0; i < cnt; i++ {
		podList.Items[i].Annotations = map[string]string{"foo": strings.Clone(str)}
	}
	var memStats runtime.MemStats
	runtime.ReadMemStats(&memStats)
	fmt.Println("podList alive:", memStats.HeapInuse)

	pod := &podList.Items[100]
	podList = v1.PodList{}
	runtime.GC()
	runtime.ReadMemStats(&memStats)
	fmt.Println("podList dead, pod alive:", memStats.HeapInuse)
	runtime.KeepAlive(pod)

	pod = nil
	runtime.GC()
	runtime.ReadMemStats(&memStats)
	fmt.Println("pod dead:", memStats.HeapInuse)
}
```

It will output as below:
```
Type: *v1.Pod
podList alive: 219521024
podList dead, pod alive: 219529216
pod dead: 1220608
```



### What did you expect to happen?

See above full description

### How can we reproduce it (as minimally and precisely as possible)?

See above full description

### Anything else we need to know?

_No response_

### Kubernetes version

The latest master branch client-go affected.

### Cloud provider

<details>

</details>


### OS version

<details>

```console
# On Linux:
$ cat /etc/os-release
# paste output here
$ uname -a
# paste output here

# On Windows:
C:\> wmic os get Caption, Version, BuildNumber, OSArchitecture
# paste output here
```

</details>


### Install tools

<details>

</details>


### Container runtime (CRI) and version (if applicable)

<details>

</details>


### Related plugins (CNI, CSI, ...) and versions (if applicable)

<details>

</details>